Tuesday 23 May 2017

APEX 5.1 IG toolbar customization

APEX 5.1 IG customization

Hacking APEX Interactive grid

IG toolbar custom configuration changes


APEX 5.1 brought us some cool features and among all the mighty interactive grid. After a day or so working on a project client ideas come into the play. 

This is where these customization tips might be of help. 

Idea here is: you want to use IG but want to be able to control what will be available to the users in terms of toolbar items and menus. APEX by default offers an ability to turn the toolbar on and off but sometimes this might not be enough.

In an image below you see how standard look and feel of your IG toolbar looks like. You have a section to filter column followed by Actions menu button then Edit, Save and Add Row buttons. 

What if I only want to use Save and/or Add Row or any combination that you suits you.  

Again I can not emphasize how helpful was John Snyders blog to understand IG inside out so thank you John.


If you read through John's blog you will pick up all necessary details for you to be able to construct your own IG toolbar

There is nothing more than
function(config) {

    var toolbarActions = $.apex.interactiveGrid
                        .copyDefaultToolbar()
                        .toolbarFind
("actions1");
    var toolbarSearchIcon = $.apex.interactiveGrid
                            .copyDefaultToolbar()
                            .toolbarFind("column_filter_button");

    config.toolbarData = [
        { //SEARCH TOOLBAR FUNC
            groupTogether: true,
            controls: [
                toolbarSearchIcon,
                {
                    type: "TEXT",
                    id: "search_field",
                    enterAction: "search"
                },
                {
                    type: "BUTTON",
                    action: "search"
                }
            ]
        },
         { //SAVED REPORT select LOV 
                controls: [{
                    type: "SELECT",
                    action: "change-report"
                }]
            },
        //ACTIONS DOES NOT WORK
        toolbarActions,
        { //SAVE BUTTON  
            //   align: "end",
            controls: [{
                type: "BUTTON",
                action: "save",
                iconBeforeLabel: true,
                hot: true
            }]
        },
        { //ADD ROW BUTTON 
            //  align: "end",
            controls: [{
                type: "BUTTON",
                action: "selection-add-row",
                iconBeforeLabel: true
            }]
        },
        { //RESET BUTTON
            align: "end",
            controls: [{
                type: "BUTTON",
                action: "reset-report",
                iconBeforeLabel: true
            }]
        },
        /*{ // FILTER BUTTON from Action menu directly
            controls: [
                {
                    type: "BUTTON",
                    action: "show-filter-dialog",
                    iconBeforeLabel: true
                }
            ]           
        },*/

        { //MY CUSTOM BUTTON
            controls: [{
                type: "BUTTON",
                action: "my-refresh-region",
                label: "Cancel"
            }]
        }
    ];

    //adding action to custom button
    config.initActions = function(actions) {
    // can modify state of existing actions or add your own
    // can also pass in an array of actions to add
        actions.add({
            name: "my-refresh-region",
            action: function(event, focusElement) {
                apex.region("event_docs").refresh();
            }
        });
    }
    return config; 
    }

This should give you ability to construct your own IG toolbars without digging into another option which is to use CSS to show/hide elements of your interactive grids. 

Please note line of code highlighted in yellow. With this functionality you can find and inject commands and controls as you want them. Isn't this great. 

Also I added a little Cancel functionality to replace a simple Hello World example with something more interesting. 

All that is missing is to to add this JS code into JavaScript Code section of your Interactive grid.



Hope this helps. If anyone knows how to manually construct action menus and Search Icon menu this would complete the whole picture. Or do we go to John again? ;)
 
Thanks,
SLino

Wednesday 17 May 2017

APEX 5.1 Interactive grid row processing and dynamic actions

APEX 5.1 IG processing

Interactive grid row processing and dynamic actions

IG row based DA tips


You started working with IG and wanted to implement dynamic actions within your IG report region.

Idea here is: You have a column in IG that triggers a change and DA fires and retrieves data from database before displaying them in your row columns.

There is a mini catch that I had issues figuring out - why and where hence this post. 


If you start googling about APEX IG most likely you will end up in good hands of John Snyder and his "how to hack APEX IG" series. Excellent material and heaps of useful things you might need along the way. 

The one that was of most help to me was Christoph's blog. Gives you a basic idea how to manipulate IG rows and change values.

Now that you have all the material it is time for implementation. 

First thing is to create your interactive grid (including a static ID). Second step select a column on which you want a DA to trigger. Create a DA with JS below. Third step is creating a page process that will return values from database. 

My process GETDOCINFO is: 
DECLARE
   r_doc_info varchar2(150);    
BEGIN
-- This should be getting the cached document info
r_doc_info:= get_info(APEX_APPLICATION.g_x01);
     
        --BUILD JSON OBJECT
        apex_json.open_object;
        apex_json.open_array('item');
        apex_json.open_object;
        apex_json.write('id', 'DOC_ID');
        apex_json.write('docID',
r_doc_info);
        apex_json.close_object;
        apex_json.close_array;
        apex_json.close_object;
  
EXCEPTION when others then
   htp.p('"ERROR"');
END;

As you can see it is pretty basic example. 

Now to the JavaScript bit.
//SET IG ROW details to be used in a fnc
//if this is removed IG row/model can not be retrieved
var p_element = this.triggeringElement;
var p_rowId = $(this.triggeringElement).closest('tr').data('id');

var dID;

apex.server.process(
    "GETDOCINFO", {
        x01: this.triggeringElement.value //PASSING IN doc_ref
    }, {
        success: function(data) {
            //console.log(data)
            var i, item;
           
  // this handles the page items to return set by the server process
 // data result is object with property item which is an array of page item id, value pairs
            if (data && data.item) {                  
                for (i = 0; i < data.item.length; i++) {
                    item = data.item[i];
                    dID = item.docID;
                }
            } //end if        
           
            //Update IG columns
            set_IG(dID);
        } //success
    }
);



//notice how this is different from example in blog   
function set_IG (pID){
    //Get the link element that was clicked
    var $te = $(p_element);
   
    //Identify the particular interactive grid
    var ig$ = apex.region("event_docs").widget();
    //here you have to change to use static ID of your region
    

    //Fetch the model for the interactive grid
    var model = ig$.interactiveGrid("getViews","grid").model;
    

    //Fetch the record for the particular rowId
    var record = model.getRecord(p_rowId);
   
    model.setValue(record,"DOCUMENT_ID", pID);   
}


Only thing to keep in mind is in yellow lines. Without keeping a reference to a row and element triggering this DA you may start receive errors in your browser console toString property not found. Comparing to Christopher's example there is very little difference but original version simply was not working for me when being called from ajax callback as information about the row and model would be lost along the way

As example this JS below would not work: 
apex.server.process(
    "GETDOCINFO", {
        x01: this.triggeringElement.value //PASSING IN doc_ref
    }, {
        success: function(data) {
            //console.log(data)
            var i, item;
            if (data && data.item) {
                var dID,dName,dUrl;
                for (i = 0; i < data.item.length; i++) {
                    item = data.item[i];
                    dID = item.value;                   
                }
                set_IG(dID);
            } //end if          
           
        } //success
    }
);

function set_IG (pID){

    //Get the element that was changed
    var $te = $(this.triggeringElement);
    
    //Get the ID of the row
    var rowId = $te.closest('tr').data('id');
    
    //Identify the particular interactive grid
    var ig$ = apex.region("event_docs").widget();
    
    //Fetch the model for the interactive grid
    var model = ig$.interactiveGrid("getViews","grid").model;
    
    //Fetch the record for the particular rowId
    var record = model.getRecord(rowId);
   
    model.setValue(record,"EVENT_DOCUMENT_ID", pID);   
   
}


Alternative approach would be to base your JS on rows selected and do processing from there. Something like: 

var view = apex.region("event_docs").widget().interactiveGrid(" getViews", "grid");
var records = view.getSelectedRecords();
//console.log(records);
//console.log(records[0][1]);
//difference between input type and object like LOV
for ( i = 0; i < records.length; i++) {
    //if dealing with LOV for example
    console.log(view.model.getValue(records[i], "DOC_REFERENCE").v);
   
    //if dealing with regular text type for example
    console.log(view.model.getValue(records[i], "DOCUMENT_ID"));
}


Please note that there are heaps of different ways how to access data in IG model aim was only to bring to attention and what to keep an eye on.

This is all for now. Over and out.
 
Thanks,
SLino

Wednesday 10 May 2017

APEX 5.1 migration mobile application

APEX 5.1 migration

Our experience going 5.1

Mobile Migration issues

 

The day has come to upgrade your system to APEX 5.1. This is a short post about things we encountered along the way. 

This post covers Mobile application version migration errors. There is a aeparate post for Desktop version.

Idea is to show case that even though it seems simple there might be more to it in terms of upgrade plans to 5.1.1 than you and I initially thought. You have been warned :)

1. problem - apex.server.process
Simple page when rendered would give this error
Uncaught TypeError: Cannot read property 'pageItems' of null
at i (mobile.min.js?v=5.1.1.00.08:10)
at h (mobile.min.js?v=5.1.1.00.08:10)
at Object.a.process (mobile.min.js?v=5.1.1.00.08:10)
at toggleFavourite (f?p=106:5:21::NO:RP,10:P5_STATION_CODE, P5_STATION_NAME,P5_SOURCE_PAGE:BA, Badgingarra,1:200)
at <anonymous>:1:1

The core of the problem was this JavaScript:
<script> function toggleFavourite() {
apex.server.process (
"togglefavourite",
null,
{
dataType: 'text',
success: function(isFavourite) {
if(isFavourite=='Y') $('#favouritebutton').addClass('favourite');
else $('#favouritebutton').removeClass('favourite');
}
} );
}
</script>
Solution: remove null, line and things worked again.

2. problem Invalid JSON.

Same process as in Problem 1. toogleFavourite contained this code:
begin
    htp.prn('Y');
end;
Solution: this now became an invalid JSON as characters need a quotes htp.prn('"Y"'); Once this was replaced things worked again.

3. problem
- Map error

Uncaught ReferenceError: google is not defined
at HTMLDivElement.eval (eval at globalEval (jquery-2.2.3.min.js?v=5.1.1.00.08:2), <anonymous>:39:20) at HTMLDivElement.e (jquery-2.2.3.min.js?v=5.1.1.00.08:3)
at HTMLDivElement.dispatch (jquery-2.2.3.min.js?v=5.1.1.00.08:3) at HTMLDivElement.r.handle (jquery-2.2.3.min.js?v=5.1.1.00.08:3)
Solution:

By changing reference to a a Google JS library from inline on page to page template

https://maps.googleapis.com/maps/api/js?key=&GOOGLE_API_KEY.&v=3.26&libraries=weather
sorted the problem. This probably has to do with JavaScript and exact timing of when a certain libraries are expected to be loaded. This was working fine in 5.0.

4. problem - Menu Error

In this application bigSlide menu JS library was used to implement a simple drop down menu. This was customization to the default apex template.

Menu did not work after Save
was clicked or any navigation happened in the application.

Solution: 


Problem was in a way APEX template work (since 4.2 version). If you are on page ID 1 and navigate to page ID 5 menu stops working because your page DOM holds more elements with same ID.

Why? Inspect your DOM when you navigate from page to page. You will notice that DOM things are being added as you move along.


<div id="P1" data-role="page" data-apex-page-transition="none" data-apex-popup-transition="none" data-theme="c" data-url......
<div id="P5" data-role="page" data-apex-page-transition="none" data-apex-popup-transition="none" data-theme="c" data-url......


We had a button on page 0 called Menu with static ID = rightpanel-link.   JavaScript was triggering on ID click which was now broken as multiple existed on same page.  

Work around apply logic but with the use of a classes.....

instead of ID based selectors
$('#rightpanel-link').bigSlide(
use class based selectors
$('.rightpanel-link').bigSlide(
5. problem - Menu display condition error
Server-side Condition was set to page is not in comma separated list of values that included 101 - Login page which cause the main menu not to be shown once you logged in. Menu would slide to the left but no content would be there.

Solution was to simply remove this condition and menu would work fine.


6. problem APEX Mobile link bug?

Page with a simple list that had a link to a certain page passing in few parameters. You would click on a link that takes you on next page but the URL of the new page is invalid as it was encoded by the engine. If you try refreshing the page you get APEX error. 

I tested this on apex.oracle.com and the same issue was there. This only happens in mobile version of UI. Not sure if this is a known thing or not. Currently we do not have a workaround.  

This is all for now. Over and out.

https://www.slideshare.net/LinoSchildenfeld/oracle-apex-migration-to-51-our-experience 
 
Thanks,
SLino