Thursday, July 15, 2010

Modal Pop Up with Dynamic Actions

You have all seen it in the more "modern" web applications, pop-up windows displayed inline, and not in a separate window. With JQuery embedded into Oracle APEX 4.0, and JQuery UI equally at your disposal (at least in the new themes I have tested), creating this type of dialogs is well withing reach. The JQuery UI dialog is already in use in the application templates (the tool tip dialogs).

I wanted to use Dynamic Actions to create a modal pop-up dialog of APEX-pages, and this is what I came up with. It is not as streamlined as I would have liked it to be, but a big leap in the right direction nonetheless.

Demo Application
I have created a demo application which you can test here: http://apex.oracle.com/pls/apex/f?p=45420:2. You can also download the application here: http://apex.oracle.com/pls/apex/f?p=45410:DOWNLOAD.

Identifying Triggering Elements
The trick with the Dynamic Actions is again to identify which elements you would like to use to trigger the dialog. In my example I have a plain SQL-report, but I have edited the primary key column to display an edit icon, and under the Column Link properties, I have set Link Attributes to include id="callModalDialog#EMPNO#". This gives me the possibility to identify the links using a JQuery selector expression.

I created a Dynamic Action on the click event, with Selection Type JQuery Selector with value a[id^=callModalDialog]. This will identfy all items with attribute id starting with callModalDialog, which, incidentally, is the same id I used on my edit links in the report.

To ensure that the event triggers after refresh of the report, I set the Event Scope to live under Advanced (as opposed to bind, which is normal). This saves me the effort of re-binding the click-events when the report is refreshed.


Calling the Dialog
When the user clicks the edit links, I want to do two things: suppress the default browser behavior, and call the modal dialog. I achieve this by adding a true Action of type Execute Javascript Code.

This contains the javascript code to do both the things I want to do:
/* prevent default behavior on click */
var e = this.browserEvent;
e.preventDefault();
/* Trigger JQuery UI dialog */
var horizontalPadding = 30;
var verticalPadding = 30;
$('<iframe id="modalDialog" src="' + this.triggeringElement.href + '" />').dialog({
   title: "Edit Employee",
   autoOpen: true,
   width: 700,
   height: 300,
   modal: true,
   close: function(event, ui) {apex.event.trigger('#P2_AFTER_MODAL','select',''); $(this).remove();},
   overlay: {
       opacity: 0.5,
       background: "black"}
}).width(700 - horizontalPadding).height(300 - verticalPadding);
return false;            
Some explanation for this is in order. The first part is just to prevent the default browser behavior (user click a ink, and the browser naturally wants to open that page). The second part is by far more complex, and contains all the code needed to start the JQuery UI dialog.

The edit link points to an APEX application page, and the code above invokes the page in an IFRAME, and uses the JQuery UI dialog to show the IFRAME inline in a modal dialog. I extract the link from the triggeringElement, set the title (this could also have been extracted from the triggeringElement), set various attributes for the dialog and IFRAME. In short the code above says open the HREF of the triggeringElement in an IFRAME, show the IFRAME in a modal dialog and open the dialog immediately.

Returning from the Dialog
If you notice the code above, the close configuration for the dialog looks quite complex. The reason for this is that I want to execute a Dynamic Action when the dialog is closed. There are two problems with this; (as far as I know) I cannot execute a Dynamic Action directly from Javascript, and I have nowhere to attach the Dynamic Event anyways. Write som javascript function you say? Yes I could do that, but the Dynamic Action is too good to pass up. Let us say that I want to refresh a report, set some item values and execute some PL/SQL when returning, with Dynamic Actions that is a walk in the park.

This is where I cheat :-)

The dialog does not exist when the page renders, so I have nowhere to attach my Dynamic Action to execute when returning from the dialog. My (kludgy) solution to this was to create a hidden item in the report region, and attach the Dynamic Action to the item. This way I can see all that happens in the Builder (easier to maintain), and if I give it sensible names, I can even discern what it does. In my case I created an item called P2_AFTER_MODAL, and attached a Dynamic Action called Refresh Report.

So, now I have defined what to do after returning, but how to trigger the event? And I cheat again... I attach the Dynamic Event to P2_AFTER_MODAL with the select event. The select event on a hidden item does not get triggered very easily. In effect this gives me a way to control that the Dynamic Action only executes when I programmatically tells it to. That is exactly what I tell it to when closing the dialog, using the APEX javascript API apex.event.trigger.

I also destroy the dialog after use, so I can create a new on the fly when the user wants to edit another employee, $(this).remove(); takes care of that (removing the IFRAME in effect, I could not get the dialog.destroy-method to work properly).

Closing the Dialog
When creating an APEX page that is to be a pop-up window, you need to take special care on the branching. In this case, you want to take care to close the dialog, both on cancel and after processing.

To handle cancel, let the cancel button redirect to URL, and set the URL target to: javascript:parent.$('#modalDialog').dialog('close');.

Notice the use of parent, the dialog is spawned by the original web page, and the dialog and its method belongs to the parent.

When branching after processing you have to be a bit more creative, create an unconditional branch to PL/SQL Procedure that runs the following code:
BEGIN
--closes this popup window
htp.p('<body>');
htp.p('<script type="text/javascript">');
htp.p('parent.$(''#modalDialog'').dialog(''close'');');
htp.p('</script>');
htp.p('</body>');
END;
This is in effect the same as the URL target behind the cancel button, but rendered by htp.p in PL/SQL.


Layout of the Dialog Page
In the sample application have have copied the Printer Friendly template, called it Modal Dialog, and used that as a page template for the dialog page. The modifications is basically just removing the navbar, and overriding the default "min-height" and "min-width" css attributes by explicitly setting style-attributes for the body-tag and div with id="body". This will vary, depending on the theme you are using. The goal is to strip the dialog of all unnecessary layout clutter.

Some Notes
I have created a few modal dialogs in the previous versions of APEX, and the javascripting involved is a pain. Maintaining the code even more painful. Even with the simple trick (or cheat, if you will) of attaching the Dynamic Action to an item in a declarative way, helps me getting control over the classic "what happens where, when and why".

Even though the basics is demonstrated in the sample application, there is a bit of work left. When you return from the dialog and refresh the report, you might want to take care to maintain the pagination, and if the user closes the dialog with cancel or window close, you might not want to refresh the report, etc., etc.

Enjoy :-)

20 comments:

  1. Hello Håvard,

    Very nice blog post.
    You can also use the following javascript function to refresh a region after the dialog has been closed:
    apex.event.trigger(region_static_id, 'apexrefresh')

    Kind regards,
    Guido Zeelen

    ReplyDelete
  2. Håvard,

    Great Post! I can see this being so useful. Great how to documentation too!

    Thanks,
    Todd

    ReplyDelete
  3. @Guido
    Cool! That is nice to know, even if I would prefer to invoke the refresh declaratively with Dynamic Actions in most cases. Makes me wonder... Are there any other "named" events I can trigger like that?

    @Todd
    Thank you :-)

    ReplyDelete
  4. Hi Håvard,

    Nice Post! This will really useful for those who are looking to use jQuery Dialog rather than Apex Popup window.

    I have implemented and it worked GREAT!!

    I need little help from you, it would be nice if you can also include CREATE functionality (to create new EMP record in jQuery Modal Dialog). Then it would be a complete demo example for anybody!

    Your help would be appreciated.

    Kind Regards,
    Bhavin
    London, UK

    ReplyDelete
  5. @Bhavin
    I'm a bit short on time, so I'll sketch a quick'n'dirty approach for now.

    Place an anchor-tag anywhere on the page, and include the following attributes in the tag:
    id="callModalDialogCreate"
    href="f?p=&APP_ID.:4:&SESSION.::NO::P4_EMPNO:"

    This will be included in the JQuery Selector of the existing Dynamic Action, and the APEX page in the dialog will pop-up ready to create a new employee (P4_EMPNO is null). The dialog in the sample application is not set up to handle create and delete, but that is easy to fix.

    What you really need to do (for a more generic and durable solution), is a bit more complex.

    I'm out of time for now, sorry :-(

    ReplyDelete
    Replies
    1. I have issue in same subject.Can u help me?
      ""
      I want to open inline dialog region using anchor tag which is in same page. but my code is not working ..can u help?

      Delete
  6. @Bhavin

    I have posted a new entry on the subject that may help: More on Modal Pop Ups

    ReplyDelete
  7. Silly question here, but could you have this modified to instead of hard-coding the background color & such have it pickup the theme being used and use that information for the colors & such?

    ReplyDelete
  8. There are no silly questions!

    There are no hard-codings of css in this example. APEX picks the style for me.

    I have not investigated this much, but it seems the jQuery UI base theme style comes as a default (no matter what APEX theme). The most obvious way I can see to overrule this is to select No for page property "Include Standard JavaScript and CSS", and include some other UI theme instead. Be careful to include all needed files though.

    ReplyDelete
  9. Thank you for great example ! It really helped me a lot.
    What I only need to solve now is the case when the window is closed without any changes - I don't want to refresh anything. Is there any way how could I pass some parameter if the refresh should take place or not ?

    ReplyDelete
  10. Hi Kristiansen,
    I like your example, I used with calendar region it works very well, but refresh parent page not work when I close the popup widow

    ReplyDelete
  11. Hi KRISTIANSEN!!!
    Congratulations for yoy post!!! I like it!! thank you very much!!

    ReplyDelete
  12. Hi, this was useful, but i also need to dissapear the close icon on the modal dialog, in order to control that all the information is complete. How can I do it

    ReplyDelete
  13. Hi,

    i've almost implemented the Modal Dialog. But, I in FireFox, I have a white background color. After one hour of trying to fix this, I tried IE and Opera. There, I get the message that the owner of the homepage doesn't allow to show its content in iframe. How can this issue be fixed?

    ReplyDelete
    Replies
    1. Hello!
      all_exed, have you fixed you problem? I faced same problem. I've done everything as it is in the post and I've alse downloaded application and copied all parts from it. But still it is not working. I get the message: "The owner of the homepage doesn't allow to show its content in iframe."
      Thank you in advance.

      Delete
  14. Nice post!

    Quick question: within your popup dialog, were you able to add validations? I've applied your technique above. One of my item in my popup is mandatory so I've setup a validation process for that item. But when I click "Create", the validation process is not executed and the popup disappears.

    Keep me posted!

    ReplyDelete
  15. This comment has been removed by the author.

    ReplyDelete
  16. Hy great solution for me to apply in cases i can't use the skillbuider plugin.
    May be you know how to cache the succes message of the dialog and to display in the parent. Thank you

    ReplyDelete
  17. Hi

    I have implemented this functionality for the calling the modal, and it works great !

    I have an issue though, in my report, I am calling this modal on the edit of the record and it does bring up the modal and I make updates/changes in the modal page and close/return to my report page.
    I also have option copy to copy/clone the record in the same report.

    My issus is , lets say for example I copy/clone a record, and click on edit which brings up the modal and I return to the report page as the page is getting refreshed with the refresh dynamic action...it creates/adds another record without me clicking on the copy option.

    How do I restrict that it does not create another record.

    please let me know.Thank you !

    R

    ReplyDelete
  18. This comment has been removed by the author.

    ReplyDelete