APEX 5.0 Dialogs

My guess is that if asked what APEX 5 UI feature is the most complex, most people would say Page Designer. It is true that Page Designer is big, as in a lot of code, but it is also very modular and you can understand each part and then how they work together. Another good guess would be Universal Theme because it is very broad in scope, has fancy CSS that must work cross browser and some nifty behaviors such as collapsible side bars and sticky headers. However, given the title of this article, it should be no surprise that in my opinion the most complex UI feature of APEX 5 is the modal dialog page. Here is why.


(If you don’t care about why skip to the end for some dialog tips.)

An APEX modal dialog page is contained in an iframe inside a jQuery UI dialog widget. This was the simplest and best way to ensure that all the assumptions that APEX makes about the life cycle and environment of pages still hold even when they are in a dialog that is part of another APEX page. Trying to combine multiple APEX server side page definitions into a single DOM page in the browser is very complicated. The APEX Mobile UI manages this because of the magic of jQuery Mobile combined with simplifying assumptions about the APEX pages. (For example you have to be careful not to use the same element id or overwrite a global JavaScript variable from different pages). For the desktop UI modal dialog pages we wanted full isolation with no simplifying assumptions and couldn’t rely on jQuery Mobile.

Even though an iframe is the best solution it is not without its challenges. When a modal dialog page opens, because of the iframe, you have a nested browsing context. This means that there is a copy of all the JavaScript libraries and two different documents. Even though the two pages are from the same origin there is still a boundary to cross when calling functions and triggering or handling events. When an event is triggered you need to know which context you are running in (which copy of the jQuery and APEX JavaScript code is running) and which context you need to be affecting. It can get mind bendingly complex.

Dialogs are not a built in feature of HTML. It requires using CSS to position an element on top of others, and for modal dialogs an overlay element between the dialog and the rest of the page to keep users from clicking on stuff they shouldn’t as well as managing focus so it doesn’t leave the dialog, etc. The jQuery UI dialog widget is a good foundation to start with because it handles all this basic, but non-trivial, functionality of a dialog. To integrate with APEX and handle issues related to putting an iframe in the dialog requires more work. Here are some of the important details that we needed to handle.

All dialogs, even nested dialogs must be created in the top level page context. If this were not the case a nested dialog would be confined to the bounds of the parent dialog iframe. So from one iframe context we need to reach out to the top level APEX context and create the nested dialog from there.

To avoid annoying scrolling of the main page while a modal dialog is open, we freeze scrolling in the main page until the dialog is closed.

Special handling of some keyboard events is needed. The escape key event from inside the iframe needs to be able to close the dialog which is in the top level context. The tab key needs to cross the iframe boundary to include the close button in the dialog title bar and still keep the focus within the dialog. In this case the tab stops are on both sides of the iframe. These may seem like minor things but they are important for accessibility.

When the dialog resizes the iframe needs to be resized so that the page contained in the dialog sees its window size changing.

We added code to force dialog CSS position to be fixed so that it doesn’t jump by the outer page scroll offset when moved.

When a dialog page is submitted all normal APEX page processing is available but unlike many normal pages rather than branching to a page it is more likely desirable to close the dialog. For this to work the response to the post request contains JavaScript code to close the dialog. This code is running in the context of the iframe but it must trigger an event on an element in the parent page.

To support wizards in dialogs, we had to handle the case where a new APEX page would reuse the same dialog rather than close a dialog and open a new one. This is accomplished with the Dialog Chained property. When set to yes, we automatically reuse the current dialog if any.

The developer toolbar became more complicated because it has to keep track of the stack of dialogs so that it’s actions apply to the currently active dialog page. The natural alternative of having the developer toolbar inside the dialog iframe just wasn’t very usable because of the smaller dialog size and confusion over having two toolbars. In addition we had to make use of the jQuery Dialog widget _allowInteraction feature so that it is possible to interact with the toolbar while a dialog is open.

The APEX popup menus also use _allowInteraction because they float above dialogs and are therefor outside the dialog. If we didn’t handle this you would not be able to use menus in any dialog.

Universal Theme even has some code to help ensure that dialogs fit on the page.

Modal dialog pages are not the only dialogs in APEX. There is popup item help which is a non-modal dialog. There is also the “Inline Dialog” region template in Universal Theme. Both of these also use jQuery UI dialog and there are interesting interactions between all of these types of dialogs. To access popup item help, you must first define help text for the item. Then run the page and with focus on the item press Alt+F1 or click the (?) icon. Although Universal Theme comes with a built-in region template for dialogs, the implementation is in libraries/apex/theme.js so you can easily add inline dialog regions to other themes.

Because the item help dialogs are non-modal they can be opened from a dialog page. So like a nested dialog page the help dialog needs to be opened in the context of the top level APEX page. If this were not done, the item help dialog could not be moved out of the dialog iframe. To keep item help dialogs from hanging around when the items they apply to are no longer present, we close them whenever a dialog opens or closes.

Inline dialog regions do not open in the top level context. For this reason you may not want to use them in modal dialog pages because they are confined to the iframe. We possibly could have made inline dialogs always open in the top level context but would not be easy. It just didn’t seem worth the effort given that nesting dialogs is not done that frequently anyway.

Inline region dialogs do a few of the things described above that dialog pages do, such as freezing the main page scrolling, but they don’t have to deal with the same iframe issues. One key issue particular to inline dialogs is related to dialogs moving to the end of the DOM. When a dialog is created it is moved to the end of the DOM. This means that any page items in that region are no longer part of the form. If the page is submitted, it will not contain any page items from that region and this will hopelessly confuse the APEX engine. In the past, the common technique used by packaged apps and some other APEX dialog plugins was to move the dialog region immediately back to where it was (inside the form) after it was created (and it wasn’t created until it was used). This worked because older versions of jQuery UI used the z-index to ensure you can interact with the dialog. With the newer version of jQuery this technique no longer works (more about this shortly). The APEX 5 dialog regions now create, but don’t open, dialogs as soon as the page loads and then just before the page is submitted put the region back where it came from. This has the advantage of moving the region before page items are initialized. If the region is moved after items are initialized, it would likely break any items (or plug-ins) that make use of an iframe (this includes the ckeditor based Rich Text Editor item type) for reasons to be explained next.

In case your brain isn’t already spinning, I left the most complicated issue to the end. When an iframe is moved from one place in the DOM to another, its content is reloaded. This is an unavoidable fact of the way browsers work. If this were to happen to an APEX dialog page, it would mean that the whole dialog page would be reloaded (as if the user pressed the reload button) and any changes they made in the dialog would be lost. Sounds bad, better make sure that iframes don’t move in the DOM. Unfortunately the way jQuery UI dialog manages the active dialog can result in moving them in the DOM (and this means that the content, including the iframe, also moves).

In older versions of jQuery UI the dialogs were moved to the end of the document when first created and never moved again. The z-index property was changed to enable interacting with the active dialog. The new version of jQuery UI that APEX 5 is using changed this behavior. Now it keeps the z-index the same but moves the active dialog to the end of the DOM. Both methods have their pros and cons. Take a look at this for details of how it was, is, and how it is going to change again when APEX takes up the next version of jQuery UI. If you use the APEX dialog features then the burden is on us to make sure things keep working as we uptake the latest jQuery UI version in a future APEX version.

When only modal dialogs are used, and they are created and destroyed on each use, there is no way that they will move during their lifetime. However when there are non-modal dialogs in the mix, the non-modal dialog can cause the modal ones to move in the DOM. If you recall that item help dialogs are non-modal, then you can see there is a problem. The solution was to create a container div just after the top level APEX form element and use the appendTo option to put the item help dialog in there. The item help dialog is then given a z-index that puts it above other dialogs. This creates a layer for the non-modal help dialogs that makes them visible but keeps them from affecting the DOM order of any other dialogs. If you ever have pages that can have a mix of non-modal dialogs (using inline dialogs) and modal pages you will need to use this same layering technique.

It is not that important for you to know any of the above. Most people can remain blissfully unaware of these details but hopefully it gives some appreciation for the effort that goes into making things super easy for you the APEX developer. Many people worked on the dialog features in APEX 5. Mostly Hilary. My contribution was mainly limited to iframe issues and inline region dialogs. I’m sure there are many interesting details on the server side that I am blissfully unaware of.

Here are some practical tips for using dialogs:

  1. The normal pattern for using dialogs is that it is OK for the calling or parent page to know about the dialog page, but not the other way around. The dialog should not know the context from which it is used. If a dialog page knows about the page it is called from then it limits how the dialog can be reused. This is not APEX specific; it is a common UI framework pattern but is often ignored in web apps. This means that if you are trying to take data entered in a dialog and insert or use it in the parent page, this should be done from event handlers or dynamic actions on the parent page not from code on the dialog page. You should not try to use JavaScript on the dialog page to try to manipulate the parent page.

    APEX gives you the Dialog Closed dynamic action event for this purpose (the event is apexafterclosedialog if you are implementing your event handler from JavaScript). The event is only fired if the dialog was closed using the Close Dialog process or the Close Dialog dynamic action. In both cases the dialog can make data available to its parent/caller using the Items to Return property. The values of the dialog items can be accessed from the Set Value action with set type Dialog Return Item or from a JavaScript action in this.data.

    There is a small way in which APEX modal pages break this normal pattern. The Chained property is an example of the dialog specifying how the parent is going to use the dialog. In a traditional desktop framework it would be the caller that would decide if the dialog should open as a nested dialog (not chained) or replace the current dialog (chained). In APEX it was far more important to be able to open dialogs with just the existing metadata (the page number). The impact is that it is not possible to use the same dialog page as a step in a wizard in some cases and as a nested dialog in other cases. The need to do this should be very rare.

  2. APEX has both dialog pages and inline region dialogs, so you may be wondering when to use one or the other. Using a modal dialog page provides the full functionality of APEX page processing including validations and processes. But it also comes with some overhead. Depending on the size and complexity of your dialog page, you may see the frame of the dialog pop up right away and then see a spinner while you wait for the page to load. As was mentioned above, there is a completely new page being fetched and rendered, including all external resources like CSS and JavaScript. Even though all the external resources should be cached by the browser, there is still a fair amount of work for the browser to do. In most cases the benefits of the normal APEX page processing far outweighs this overhead.

    Inline dialogs are part of the parent page and its markup is downloaded at the same time as the page (it is there even if the dialog is never opened). It is just like any other region except that it is hidden initially and turned into a dialog. This means there is no additional request to fetch UI when the dialog is shown. You may however use dynamic actions to fetch data to display in the dialog each time it opens but this request should be small and fast. For this reason inline dialogs are almost always faster to display. The down side is that you don’t have the full power of APEX page processing to submit the dialog. (A submit button in an inline dialog submits the whole page.)

    Use an inline dialog when:

    • The content is mainly static such as a confirmation or notification message
    • The content is for display only. You don’t need validation or other page submit processing. It may include data that is already on the page or use data on the page, such as a key, to dynamically fetch more data from the server.
    • It is just for navigation. The content is a list of links used to navigate to other application pages.
    • The dialog is used frequently and must display quickly

    In other cases it is best to use dialog pages.

  3. Dialog pages offer a number of configuration options such as width and height. You can also pass options directly to jQuery UI Dialog using the Dialog Attributes property. The help refers you to the jQuery UI documentation but not all of the options make sense. The most useful options are resizable, and draggable. Other useful options are maxHeight, minWidth, minHeight, hide, show, and position. To be clear what you are entering for attributes needs to be a valid fragment of JavaScript that goes in an Object literal. If you make a syntax error you will get a JavaScript error as soon as you try to open the dialog.

    These jQuery UI Dialog options don’t make sense:

    • buttons is not useful because you should use APEX buttons on the page
    • autoOpen should not be specified because APEX page dialogs must always open when created and are destroyed when they close
    • appendTo seems unlikely to be useful
    • closeOnEscape should not be changed from its default for best accessibility
    • closeText could be used but you probably want it to be localized, which APEX already does
    • dialogClass is not needed because (at least for the dialog page templates in Universal Theme) the Dialog CSS Classes attribute is added to both the body element inside the iframe and the outer dialog element, which is what dialogClass sets.
    • modal should not be changed but to be honest I have not tried setting it to false. It will likely cause problems with iframe movement as described above unless a layer is created and also confuse the dev toolbar.
    • title is already set by the dialog page template

    Dialog Attributes should not be used to add event handler functions. For one thing it is a text field not a code editor property so there isn’t much room to add code. It is not very common to need to handle these events anyway. But if you need to handle any of the dialog events this is how to do it.

    The event has to be handled by the top level APEX page because that is where the dialog widget is created. If you are adding the event handler to a normal page then it will be the top level APEX page and you can just create a normal event handler (from the page’s Execute when Page Loads property) such as:

    
    $("body").on("dialogresizestop", ".mydialog", function(e, ui) {
        console.log("my dialog resized!");
    });
    

    Note that in this case I gave the dialog a Dialog CSS Classes property of “mydialog” so that I could target that specific dialog. You could also use a dynamic action with a custom event type.

    If you need to handle the event from a dialog either the dialog page being opened or a parent dialog page you need to use the apex.util.getTopApex function (undocumented). This function returns the top most APEX context, which is where dialogs are created. Most of the time this returns the same thing as top.apex but not always for example if the main APEX page is itself embedded in a frame. Because you need to use getTopApex in this case it is not possible to use a dynamic action.

    Here is what a handler for the close event would look like:

    
    apex.util.getTopApex().jQuery("body")
        .off("dialogclose.myHandlers")
        .on("dialogclose.myHandlers", ".ui-dialog--apex", function(event) {
            console.log("A dialog page closed");
            apex.util.getTopApex().jQuery("body").off("dialogclose.myHandlers");
        });
    

    In this case it will detect all the APEX modal page dialogs because of the “.ui-dialog--apex” selector. If you add other dialog event handlers in this fashion, then it would be good to also add a dialogclose handler to cleanup the event handlers as the close handler cleans up after itself. This is where event namespaces like the “myHandlers” shown here comes in handy. If you don’t, it will continue to live on the main page and handle the events even after the dialog that initially created the event is gone.

    Setting the Dialog attributes lets you customize a single dialog page but what if you want to change the settings for all dialogs? One way is to create a new page template and change the Dialog Initialization Code to add your settings. There are two problems with this. One is having to create a new template or unlock the theme and edit the dialog page template. The other is that the Dialog Initialization Code by design doesn’t allow you to enter a lot of text. You can get around that by moving the bulk of the code into a function you add to a JavaScript file, that you then include with the app or theme.

    Another option is to use a dialog create event handler and set the options there. This handler could also be added to a JavaScript file to be included on all pages or just added to specific pages.

    The following example will add slide up and slide down effects for opening and closing all APEX page dialogs.

    
    $("body").on("dialogcreate", ".ui-dialog--apex", function(e) {
        $(this).children(".ui-dialog-content")
            .dialog("option", "hide", {effect: "slideUp", duration: 1000})
            .dialog("option", "show", {effect: "slideDown", duration: 1000});
    });
    
  4. Now more than ever it is a good idea to use the APEX_UTIL.PREPARE_URL function. APEX makes opening a dialog page as simple as specifying the page number anywhere you can create links to an APEX page (such as lists and buttons). You can get away with constructing your own APEX page URL in some cases but dialog pages need to use PREPARE_URL. Just because your page is a normal page today doesn’t mean it will stay that way. Someone, even you, could change the page mode to Modal Dialog in the future. For this and other reasons it is good to get in the habit of using PREPARE_URL.

I realize there is a lot of advanced material here. If you are just getting started check out Creating Dialog Pages, in the “Application Express Application Builder User’s Guide”. Also check out the ‘Sample Dialogs’ packaged application available with 5.0, which contains an example of both inline dialogs and modal dialog pages.

2 thoughts on “APEX 5.0 Dialogs”

  1. Thanks for sharing. Nice addition to the documentation which is mostly only about standard DA’s for the new modal dialogs.

Comments are closed.