Monday, August 2, 2010

More on Modal Pop Ups

I previously wrote a post on how to use the new APEX 4.0 native features to conjure modal inline dialogs. It was followed by a brief (very brief on my part, to say the least) discussion on how to achieve the same functionality for a create button. In this post I will elaborate a bit on how to do exactly that.
The solution sketched out below definitely has potential for improvement, but can serve as a diving board for the interested.

Demo Application
If you are curious, or just down right bored with long posts, then I have a running copy of the demo application here: http://apex.oracle.com/pls/apex/f?p=45420:3. The demo application can be downloaded here: http://apex.oracle.com/pls/apex/f?p=45410:DOWNLOAD. To keep the examples as clean as possible, I have created a new page with the Create-button, so you have both alternatives available.

The Quick Solution
And what is wrong with a quick solution? Personally I feel the we programmers (me definitely included) are very good at complicating things. The rest of this post is dedicated to a more generic approach, but in my original comment I suggested this:
  • Create an anchor-tag (link) anywhere on the page
  • Set href attribute to the URL of your edit form page, let primary key item values remain blank (but be sure to pass them)
  • Set the id attribute to callModalDialog
In my sample application that would be: <a id="callModalDialog" href="f?p=&APP_ID.:4:&SESSION.::NO::P4_EMPNO:">Create Employee</a>.

This would work just like the edit links in my original example, but ready to insert a new employee. Like magic :-)

The Elaborate Solution
The elaborate solution uses more Dynamic Actions, a bit more javascripting and a couple of dirty tricks to accomplish the same as the quick solution. The goal is to make a normal create button to have the same behavior with inline modal dialog, as the edit links does.

The explanation below is based on the original modal dialog page in my sample application. To follow the example and repeat the steps, you need to start off with a copy of page 2 in my sample application.

To follow the example, start by creating a region button with the following values (mostly just accept defaults):
Button name: CREATE

The button should take you to the create/edit page, but without the fancy modal dialog.

A Note for Later
If you name your button to something else than Create (or Text Label/ALT-property for the page button in APEX), you must adjust the following JQuery selectors accordingly for it to work. You may also expect some issues when using an image button (the javascript onclick function extraction may fail).

Removing the Original onclick-Event
APEX uses a javascript function to redirect the browser (also when using anchor button template). There are two things that needs to be done when the page loads; store the original create link, and remove the original button onclick event. I use JQuery and javascript native regexp capabilities to achieve this.

To create a Dynamic Action which fires when the page has finished loading, do the following:
Create a new Dynamic Action at the page level

Choose Advanced

Give it a sensible name ("On Page Load")

Choose Event Page Load

Choose Action Execute Javascript Code, and paste in the javascript code below:

/* get original onclick event */
var origAction = $('button[value=Create]').attr('onclick').toString();
/* get link from original onclick event using regular expression */
var link = origAction.match(/(redirect\((\'|\"))([^\'\)|\"\)]*)/)[3];
/* Remove original onclick event */
$('button[value=Create]').removeAttr('onclick');
/* store link as title attribute of button */
$('button[value=Create]').attr('title', link);
Looks a bit Greek? Even if I have actually included comments? If you are not familiar with JQuery, it definitely will. If you are not familiar with regular expressions, even more so. If you are not familiar with javascript at all, you are allowed to test the code, but not use it in production unless you have truly understood what it means :-) There are very good sources on the web for all the knowledge required.

And why, oh, why store the link value in an element attribute definitely not meant to hold a link value!?! It will overwrite any existing title-values, and makes both setting and retrieving code hard to read. On the other hand, it will keep the value with it's element, and support more than one create button on any given page. There are more than one alternate way of doing this, but hey, feel free to bring suggestions :-)

Anyway, click Create and you are done.

If you run your page now, clicking the Create button will not do anything (the onclick event was removed, but not replaced).

That was the hard part!

Calling the Dialog
The edit dialog Dynamic Action is already in place, all that has to be done is to adapt the existing Dynamic Action to include the new create button.
Edit the Modal Dialog Dynamic Action
Include: button[value=Create] as the JQuery Selector expression (total expression will now be: a[id^=callModalDialog],button[value=Create])

Edit the True Action Javascript Expression to be:
/* prevent default behaviour on click */
var e = this.browserEvent;
e.preventDefault();
/* Find page link */
var link;
if (this.triggeringElement.tagName=='A') {
   link = this.triggeringElement.href;
} else if (this.triggeringElement.tagName=='BUTTON') {
   link = this.triggeringElement.title;
}
/* Trigger JQuery UI dialog */
var horizontalPadding = 30;
var verticalPadding = 30;
$('<iframe id="modalDialog" src="' + link + '" />').dialog({
   title: "Edit Employee",
   autoOpen: true,
   width: 700,
   height: 300,
   modal: true,
   close: function(event, ui) {apex.event.trigger('#P3_AFTER_MODAL','select',''); $(this).remove();},
   overlay: {
       opacity: 0.5,
       background: "black"}
}).width(700 - horizontalPadding).height(300 - verticalPadding);
return false;            
The difference from the original javascript code is the extra bit extracting the link to use, which differs from a normal anchor element, and our button with the special id attribute.
Click Apply and you are done.

The Rest?
Is the same as detailed in my last post. I have updated the dialog page in the sample application to include Create and Delete buttons, but that is it.

Quick or Slow?
Quick sound promising! Simplicity rules! There are some draw-backs, of course. Like where to put the link to find it later (a Display Only item properly named perhaps?)? Like the need to style a link as a button? Like the need to place the link in a region template position? It all adds up.

The generic (more elaborate) approach will work with any create button (well, not with the anchor template). Drop the Dynamic Actions onto the page, and it will work. On the other hand, the generic approach involves more code, more code is harder to maintain and more prone to breaking. Generic code requires a delicate hand (ie takes time), and is generally harder to read than code created for a specific task. This is all in the eye of the beholder, but on more than one occasion I have had the pleasure of revisiting my own old code and though: I didn't need to do that... I digress, I know. Besides, that is too great a topic to just be delegated to a digression :-)

So, should you use the quick link (pun intended :-))? My answer (being a consultant) is a definitive: It depends!

Enjoy :-)

44 comments:

  1. Good read as usual! Based on this and previous posts, it seems like you are on your way to becoming the "Dynamic Action Guru" :-)

    ReplyDelete
  2. I have more than 1 button and it calls different pages. useing your e.g. I can call only one page, I can not open other page.

    e.g Button1 link to page 4
    button2 link to page 5

    ReplyDelete
  3. @Prasanta

    You can open as many as you like, but you have to edit the jQuery Selector expression to include all buttons where you want the dialog.

    If you see in my demo application, the last part of the jQuery selector (for the "Modal Dialog - Open" dynamic action) chooses all buttons with value Create. You can create similar selectors for ALL your buttons.

    If you have buttons with value Button1, Button2, etc, include them in the jQuery selector: button[value=Button1],button[value=Button2],etc, or maybe button[value^=Button] (for all buttons with value starting with Button).

    Good luck!

    ReplyDelete
  4. Thank you very much for the info but the problem is , on page load code it is not changing the button title, it is only changing for one button, because button2 is conditional and not visible on the first time.

    var origAction = $('button[value=Create]').attr('onclick').toString();

    ReplyDelete
  5. @PKP

    If you look at the onload dynamic action, it fires once on page load. If the button does not exist at that time, it will not be picked up.

    You can move the true event from page load action to onclick action to execute the code there. Or maybe attach it to the event that creates the new button in the first place. Then you probably can also tap into the triggeringElement variables and make the javascript more generic.

    When I look at the code I wrote in the demo application, I see it's more than a little crude (and naive). If I have some time later, I will update it.

    ReplyDelete
  6. hi,
    I imported the app in my workspace but, it does not work in IE!! i got error in "var origAction = $('button[value=Create]').attr('onclick').toString();" , telling me that .attr(.) null
    thank you

    ReplyDelete
  7. @moufix

    I'm guessing you are using IE6? It is not tested with IE6. It works in IE8.

    I do not have the time now to change the code for IE6 compatibility, but it should be possible.

    ReplyDelete
  8. I was on IE8. However, I clear the IE cache, and it works now perfectly.
    thank you dud/

    ReplyDelete
  9. I love your blog entry on doing Modal windows and I am able to get it to work for existing items to be edited using my set of themes. However, getting the create button to work is proving an issue..

    If you have a few free minutes could I have you look at my sample page and see if anything stands out for you?

    Please e-mail me and I will send along temporary account access information..

    Thank you,

    Tony Miller
    Webster, TX

    ReplyDelete
  10. This comment has been removed by a blog administrator.

    ReplyDelete
  11. Hi everyone.
    Please help me.
    When I close the popup window, my report refreshes but if I choose other item, the popUp window doesn't work and the link sends me to the page.
    What could be the solution?.
    Thanks.

    ReplyDelete
  12. @Mery

    It works the first time, but not after refresh.

    This is typical if you forgot to set "live" for the Event Scope (under Advanced) for the Dynamic Action.

    After the report refreshes, jQuery looses all triggers, unless you tell it to continualy poll for all matching trigger objects.

    ReplyDelete
  13. Hi Håvard Kristiansen
    Thank you so much for your help. It works now.
    Thanks

    ReplyDelete
  14. Looks great!

    One quick question...How do you manage validations? A validation error usually requires a page refresh. Won't the popup be gone when the page refreshes or does the iframe manage that?

    ReplyDelete
  15. @yodahart

    Validation in a popup window works just as in an ordinary page. If a validation error occurs, the branch that closes the popup will not be reached.

    Be sure to test it with failed validations, because you are dealing with a limited amount of space in a popup, and inline messages tend to lead to user scrolling.

    ReplyDelete
  16. Hi and thank you so much for ur helpful post
    I have a question.In my page I need to do this for several table.Let's say I need to create employee, department, order.So in one page I will have several create buttons.how can make 3 of them working?Thank you so much in advance

    ReplyDelete
  17. @Zahra

    This is not a problem. You just have to make sure that your jQuery selector catches all the buttons. See the second and third comment on this post.

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

    ReplyDelete
  19. Dear Havard,
    Thank you for ur replay.This is weird problem that I have.Is it possible due to having different template I get errors?
    For instance : if I click on buttons on the page no matter if they r in same or various regions I get alert of their address due to
    function redirect (url) {
    alert(url);}

    or if I don't comment background: "black" when I click on button nothing will happen.i am sorry if my questions are too basics

    Thank you in advance.

    ReplyDelete
  20. Hi Havard,

    I tried this and this is gr8 :). But can we put a validation in the page which is popping up as modal page. I tried to add a validation, but it is not working.

    Thanks,
    Jyothish

    ReplyDelete
  21. @zahra

    I am very unsure of what you are trying to do, or what is wrong with your page. I suggest you set up an example on apex.oracle.com and ask for help on the APEX form on forums.oracle.com. Best I can do for now, sorry about that.

    @TJ
    Page validation on the parent page will not occur when launching a modal page using this method. You can create any validations necessary on the modal page though. Both on render time, and on page processing.

    ReplyDelete
  22. Hello,

    I have imported the application and I have to use some features and it worked perfectly in Firefox, but in IE the pop up is not launched. I only receive an alert message with the link. Could you help me to sole the problem?

    Thank you,

    Ruxandra

    ReplyDelete
  23. @Ruxandra

    The pop up in the sample app works in IE8. If you have modified it, and it does not work, then import again and redo your changes one at a time until it fails.

    jQuery is very good at handling different browsers, and jQuery is responsible for this pop up code. My guess is the problem lies somewhere else.

    If you get stuck, set up an example at apex.oracle.com and ask for help with it.

    Good luck :-)

    PS: Beware of console.log, it does not work in IE (unless you have the IE developer console open). This has caught me off guard more than once...

    ReplyDelete
  24. Hi again,
    Just a question. This example is done using a standard APEX report. How hard would it be to modify it to work with an Interactive Report?

    ReplyDelete
  25. @tony

    Should be no sweat with an Interactive Report.

    Remember: The key is to make your jQuery selector to include all anchors/buttons you want to affect, the rest just works.

    ReplyDelete
  26. Ok, just a comment or concern.. In using your dynamic actions, I have a standard report calling a form. When the report initially loads I have pagination going from page 1-15 and so on. After I return from the form (cancel/create/save) my report is defaulting to a pagination of 1-10 items, as apposed to 1-15..

    Any ideas as to what in the dynamic actions could be doing this?

    ReplyDelete
  27. Found it!! The standard report had the perform partial page refresh checked. As soon as I removed this the page is formatting fine.. Any ideas?

    ReplyDelete
    Replies
    1. Tony, I had a problem like that once. You are using Delta theme, right? The standard Delta theme had a bug in that it reset lines per page to 10 after a PPR regardless of the original setting. Many a happy hour was spent tracking that down!

      Monkey, great post, very instructive, but I haven't been able to work out the best way to pass back the ID of an entity created in the modal page. What would be your best practice recommendation for doing that?
      Many thanks

      Delete
  28. @Tony

    Sounds like the APEX session repository is not refreshed properly to reflect your report changes. This should work with Enable Partial Page Refresh.

    ReplyDelete
  29. This method is great, and I've made use of it throughout my application. My problem is that my service provider just upgraded to 4.1 and the HTP.P close mechanism isn't working anymore. I was hoping to test out your simple app, but the download link isn't working.

    ReplyDelete
  30. I have no idea what is up with 4.1 and closing, but the download should work now.

    ReplyDelete
  31. Hi,

    Just wondering why I can't download the demo?
    I get this:

    Error Error processing branch.
    ORA-01653: unable to extend table KOMPETANSEGRUPPE.SAMPLES_LOG by 128 in tablespace FLOW_2906


    Thanks

    ReplyDelete
  32. Monkey, great post, very instructive, but I haven't been able to work out the best way to pass back the ID of an entity created in the modal page. What would be your best practice recommendation for doing that?

    And for the record... nothing wrong with the Quick Solution philosophy, I reckon. The "great" is the enemy of the "good".
    Many thanks

    ReplyDelete
  33. great help! could you check your download though. Each time I have tried to download, I receive app 115 but it does not contain the MODAL DIALOG WITH CREATE page. Thanks!

    Karen

    ReplyDelete
  34. Uploaded a new version, should work now.

    This sample is getting quite old, and always was a bit of a clunky hack. I would check out more recent samples or plugins.

    ReplyDelete
  35. Thank you!

    I just downloaded. Sadly, I still cannot get my button to open a modal pop-up...though the link works perfectly. The button redirect to a page, that is defined as popup...but alas. I will keep fiddling. thanks again for your help.

    ReplyDelete
  36. Hi Håvard - love the solution, but I have an IE8 issue that's driving me wild. I thought you might be able to provide some direction. The thing that really bothers me though is your apex.oracle.com app does not reproduce the issue.

    This seems like a common complaint:
    'attr(...)' is null or not an object
    I've triple checked my example against yours; checked the browser source produced at runtime; tried some alternatives... I just can't beat it.
    It's stopping the button action from opening the modal dialog, instead it redirects the page.

    Any thoughts or suggestions are appreciated! cheers

    ReplyDelete
  37. Hi Scott,

    Sorry for the late response, haven't had much time lately.

    To start on the basic side:
    - You are sure there are no caching issues involved (as moufix commented on above)?
    - Can you catch anything in an alert? console.log is pretty poor option when leaving page :)
    - If you change the button link to something trivial like #, can you catch the triggering event?

    Just a few thoughts...

    Good Luck!

    ReplyDelete
  38. Please help me, very good post, but when saved in the popup does not update the region

    ReplyDelete
  39. Hi Havard,

    I've done everything as it is in the post and I've also downloaded application and copied all parts from it. Your application is working perfect. But mine is not working. I get a message: "The owner of the homepage doesn't allow to show its content in iframe." And I have a link to a page I want to open in the iframe.
    Could you help me with it? Is it because of version of script or may be APEX version?
    I'm using APEX 4.1.0.00.32
    Thank you in advance.

    ReplyDelete
    Replies
    1. Your problem is probably caused by a new security setting in 4.1. Look in application properties, security tab, browser properties section, and adjust your setting for "Embed in frames".

      Delete
    2. Thank you, Rumpelstiltskin! Thank you a lot!

      I spent 2 days to fix it. It is working now!!!

      Delete
  40. I've used this tutorial for normal page to create an employee, it is working fine. But when I tried to use Tabular form for pop-up page it is not working. Who knows what is the problem? is it because of tabular form or I made some mistake? If it is because of tabular form could anybody help to show how to use tabular form as a pop-up page? Thank you inadvance!

    ReplyDelete