Thursday, 15 June 2017

APEX Universal theme auto hide success message

APEX 5.1 auto hide success message

Auto Client side error messages handling

Follow up post on few posts out there


Recently I noticed few blog posts on this problem and lazy me said cool something interesting and useful that I would want to reuse. Then I noticed few issues and wanted to write this blog in order to complete the picture.

Situation 
We have form or interactive grid where we can edit and save some data. Excellent but why does not this success message that confirms all went well auto retrieves. How do we sort that?

 

 As you see user has to click in order to close the mesage.

Solution 
There are few blog posts out there dealing with the same issue but none were complete or better said they worked perfectly in certain situation. At least we were getting still some issues so we decided to merge them into one. :)

Marko's blog explains in detail how to sort this out on IG type of page which is awesome. But what happens if you by accident set this up

apex.message.setThemeHooks({
  beforeShow: function(pMsgType, pElement$){
    setTimeout(function() {
      $('.t-Alert').fadeOut('slow');
    }, 3000);  
  }
});

to be executing on page load on your Page 0 global page. It breaks some of modal page functionality if modal page gives throws any error. So make sure you use
if (pMsgType=='success'){
condition. Also it did not seem to sort issues with regular Form submits.

Other one was from Mark

Which on the other hand sorts the problem of saving the regular form by auto dismissing the message. But it would get stuck if user would click on 'X' trying to manually close the message.

All credentials go to original posters of course. For my future reference this was what worked for me

apex.jQuery(document).ready(function() {
    
    var opt = {
        autoDismiss: true,
        duration: 3000 // Optional. Default value is 3000
    }

    // this only applys configuration when base page has a process success message ready to display
    apex.theme42.configureSuccessMessages(opt);

    if (apex.theme42.configureSuccessMessages.options === undefined) {
     

        apex.theme42.configureSuccessMessages.options = opt;
    }

 //Marko code
 apex.message.setThemeHooks({
   beforeShow: function(pMsgType, pElement$){
   if (pMsgType=='success'){ 
     setTimeout(function() {
    $('.t-Alert').fadeOut('slow');
     }, 3000);
   }   
    }
 });
 
 
 //Additional
 $('.t-Button--closeAlert').click( function(){
   $('.t-Alert').fadeOut('slow');
         //console.log('clicked'); 
    });
 
 
});
Only in this combination things seems to work.

Happy Apexing,
SLino 

Tested on internal and apex.oracle.com on APEX version 5.1.1.00.08 

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"
                }
            ]
        },
        //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

Sunday, 23 April 2017

AUSOUG APEX 5.1 webinar series

Oracle APEX 5.1 webinar series

Australia/New Zealand ORACLE APEX webinars

Great speakers from USA, EU, AU and NZ


I am pleased to announce there is a huge interest not only among the speakers but also from the audience for this years 2017 AUSOUG APEX webinar series.
There is plenty of time for you to join us, please check AUSOUG Calendar for more details. First session is starting 27/04/2017 - registration.

Some of the speakers already confirmed


  • Dimitri Gielis, APEX RnD, Belgium
  • Tyson Jouglet, Skillbuilders, USA  
  • Scott Wesley, Sage Computing, Australia
  • Roeland Van den Eynde, APEX RnD, Belgium
  • Niels de Bruijn, MT AG, Germany
  • Oliver Lemm, MT AG, Germany
  • Aljaz Mali, Abakus Plus, Slovenia
  • Menno Hoogendijk, Qualogy, Netherlands
  • Matt Nolan, FOEX GmbH, Austria
  • .....more to come soon
in no particular order :)


More information on ausoug.org.au. Hope to see you there.
 
Happy APEXing,
SLino

Monday, 10 April 2017

APEX 5.1 migration - compatibility tips

APEX 5.1 migration

Our experience going 5.1

Compatibility mode 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 for Desktop application template version errors that uses compatibility mode 4.2. 

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. 

For mobile template errors there will be a separate post. 

Please note that I will be updating it as things pop up so watch the space.

1. First on the menu:

Ajax JS calls causing:
Ajax call returned server error ORA-20987: APEX - Session state protection violation: This may be caused by manual alteration of a URL containing a checksum or by using a link with an incorrect or missing checksum. If you are unsure what caused this error, please contact the application administrator for assistance. - Contact your application administrator. for .

If you dig into debug mode you would get some more details where APEX was listing what page item this problem occurred for. Bottom of the problem was some page items had Security attribute Session State Protection set to Check sum required - Session Level and were used in AJAX calls which now cause security errors to be shown. Solution of the problem was to change this as:




2. problem had to do with Old Custom Theme template and application that was running in 4.x compatibility mode.

Apex 4.x compatibility apps have an issue with the home breadcrumb not substituting values rendered URL looks like this:
http://xxxx.xxx.xxx:9004/apex/eds/f?p=&APP_ID.:HOME:&SESSION.

How to fix this? One way is to chance this breadcrumb template from:

To this:
There are other ways how to do call this JS fix but for clarity of the blog I used "After Last" section.
3. problem - Dialogs for applications running in compatibility mode 4.x version

Front dialog not at correct z-index – unable to click on it due to modal click blocker being in front. Selenium tests still worked fine.
How to fix this? Add CSS style to your template
body .ui-dialog.ui-front{z-index:1001 !important} 

4. problem - Interactive report Filter Issue for 4.x compatibility mode
When you select Action-> Filter, Chrome console throws this error and it does not display filter options dialog screen:

First hint was this was related to old Classic Date Picker functionality that has been decommissioned.

This was confusing as on this IR there was no usage of Classic Date Picker or anything like that nor did the page contain any page items that were Date Pickers. Plus some IR of the same application were working perfectly fine while few others failed. This indicated that APEX 5.1 LS libraries were not the cause of the problem otherwise it would not have worked at all, right?

Solution: After significant amount of hours investigating, what narrowed it down was a desperate page export and comparison between working and non working IR page.

Core cause of all problems had nothing to do with Classic Date Picker nor with any JQuery conflict. It came down to bad programming practice and simple region Static ID set by developers. Pages where Filter action had an issue had ID set something similar to: 




I guess you see the problem straight away. What this invalid ID did it eventually broke JS functionality of IR as it was trying to use ID to handle user interactions. Once we updated IDs to include no spaces and less characters things were back to normal. All IR Filter actions started working again.


Hard lesson learned here. Only hint to APEX would be to add a check to static ID that it has to be a valid HTML ID for DOM element. Please note that same IDs in APEX 5.0.4.00.12 worked perfectly fine. 

5. problem - apex.server.process error

Uncaught TypeError: Cannot read property 'pageItems' of undefined
at i (desktop.min.js:10)
at h (desktop.min.js:10)
at Object.a.process (desktop.min.js:10)
at Object.handleJson [as success] (map_rbgassessment.js:232)
at i (jquery-2.2.3.min.js:2)
at Object.fireWith [as resolveWith] (jquery-2.2.3.min.js:2)
at z (jquery-2.2.3.min.js:4)
at HTMLScriptElement.c (jquery-2.2.3.min.js:4)
at HTMLScriptElement.dispatch (jquery-2.2.3.min.js:3)
at HTMLScriptElement.r.handle (jquery-2.2.3.min.js:3)

Scenario: simple application page using this AJAX call

apex.server.process ("deletePins");

For deletePins application process:
delete RATE_COLLECTION. TMP_PIN_NUMBERS;
Solution was to rewrite the AJAX call:

apex.server.process ( "deletePins", {
x01: "test",
pageItems: ""
}, {
success: function( pData ) {
console.log("(done) deletePins ");
console.info(data);
}
} );
Seems like call back needs to have a success handler.

6. problem - Download is showing additional column with URL details

Report on a page had SQL query for the source

  SELECT '<a href="javascript:apex.confirm(''Do you want to return this organism as a member?'', ''REVIVE_' || CARRIER_ID
  || ''');"><img src="#WORKSPACE_IMAGES#red-delete.svg" alt="revive" title="Revive Member"></a>'............

Solution
Export / Printing
Include In Export / Print
Yes/No -> set to NO defaulted to YES

7. problem - Internet explorer issue with Interactive reports and tabs

<meta http-equiv="X-UA-Compatible" content="IE=8" />
Check if your templates include the line above. This caused IE application to show heaps of related issues in DOM inspector.

Object doesn't support property or method 'submit'
and few others. By removing the line in yellow things were back to normal. Application was running template 24 in 5.0 compatibility mode. 

8. problem - Minor CSS class/DOM changes between APEX 5 and APEX 5.1

This caused some testing scripts to fail. Once you inspect the DOM you would notice small DOM changes and all you have to do is update your test scripts.

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


Last Update 27/04/2015
https://www.slideshare.net/LinoSchildenfeld/oracle-apex-migration-to-51-our-experience


Latest update 03/05/2017