Passing Data in and out of APEX Dialogs

How to pass data into and return data from an APEX dialog is something that comes up from time to time on the APEX forum. I have not seen a comprehensive treatment of this topic so decided to work up an example of the various possibilities.

[Updated 23-May-2017 to reflect 5.1.1 and add detail for when dialogs are opened from navigation menu list]

I created an app Dialog Demo to go along with this article. You can try it here or download and install it in your own workspace. Dialog pages were introduced in APEX 5.0 and did not really change in 5.1 (there was just a minor internal change related to the warn on unsaved changes feature). I wrote about dialogs before. This app is a 5.1 app but most of the techniques shown here should be applicable to 5.0 as well.

An APEX dialog page is like any other APEX page except that it runs in an iframe in a dialog inside another APEX page. So in general the mechanisms for sharing data between pages should be the same. One page can access data entered on another page if that data has been saved in session state or an APEX collection or if it has been committed to a database table.

What should not be done is the dialog page directly accessing the DOM or JavaScript context of its parent page to read or write data. The reason is that a dialog is a reusable component that shouldn’t care or know anything about what page it is opened from. This is a general principal of dialogs in any GUI system. Also a dialog should not directly manipulate data of the caller because generally there is a Cancel button and if the user chooses to cancel then any changes made should have no effect.

Passing data in

We will look at the input case first; that is passing data from the current page into a dialog page.

Input Method 1:

For normal pages one way to pass data is to submit the page and then use a branch to redirect to the next page. This enables validations and processing to be applied. Data can be passed to the next page through collections, tables or by explicitly setting session state items in the next page. There are some problems with this when the next page is a dialog to open.

The biggest problem is that you can’t branch to a dialog page. A branch redirect replaces the current page in the browser so it just doesn’t make sense to redirect to a dialog. Dialog pages always open within and “on top of” the current page. So dialog pages are typically opened from buttons that redirect to a dialog page. The URL in this case is actually code that calls apex.navigation.dialog. More on this later.

Note: Anyone working with APEX 5.0 and dialogs for a while now should know that you can’t use a server side branch to redirect to a dialog page. You may be surprised to find that in 5.1 it does work in some cases. If the page attribute Reload on Submit is Only for Success then it will work. Before you think that this is some cool new 5.1 feature let me tell you this is a mistake; an unintended consequence of how reload on submit only for success works. I was surprised to find that this works. You should not rely on it working in the future. So to be clear do not branch to dialog pages. [Update 4-Aug-17 some Q&A on this subject on the APEX forums.]

I don’t think this use case is all that common. From a user’s perspective they don’t expect their data to be saved (persisted) when a dialog opens. But just because a page is submitted doesn’t mean data is persisted; consider a set of wizard pages. It can just be an implementation detail; a way to set session state or do validation. So if you really need to run validations and page processes this is what can be done.

The general idea is to use a normal submit button. Add any validations or other processing. Then add a branch to the same page but before the branch set a hidden page item to indicate that the dialog should be opened as soon as the page is loaded. Then add a dynamic action on page load to open the dialog.

To see this in action run the Dialog Demo app and on the home page enter values for Input 1 and 2 and press the Open Dialog (after submit) button. The page is submitted and reloaded and then opens the dialog where you will see that the two inputs on the main page have been set on the dialog page.

There is nothing special about the submit button. For demonstration purposes there is a validation on P1_INPUT_2 that it must not contain spaces. A PL/SQL process called Copy data is used to copy the inputs on this page to items on the dialog page. It also sets a hidden item on this page to ‘Y’, which is used to open the dialog.

Here is the Copy data process:

BEGIN
    APEX_UTIL.SET_SESSION_STATE('P2_INPUT_1',:P1_INPUT_1);
    APEX_UTIL.SET_SESSION_STATE('P2_INPUT_2',:P1_INPUT_2);
    APEX_UTIL.SET_SESSION_STATE('P1_OPEN_DIALOG','Y');
END;

Then there is a branch to the same page (page 1). Even though there is only one button that submits the page, just to be safe all this processing is conditional on the SUBMIT button.

If you are thinking why bother using a PL/SQL process to copy session state when you could just pass those values in the URL to the dialog page you have a good point. The process represents the general case of processing that could involve updating collections or tables etc. We will shortly see a better way to pass data to the dialog using the URL.

On the rendering side there is a hidden item called P1_OPEN_DIALOG and it is set to static value ‘N’ by default. There is an After Footer computation to set this session state item back to ‘N’ as soon as it is rendered to make sure it is only ‘Y’ one time after the page is submitted. To actually open the dialog there is a dynamic action that fires on page load. It has a client side condition P1_OPEN_DIALOG = Y so that it only happens after submit. There is a JavaScript action to open the dialog. As has already been mentioned the URL that opens a dialog page is actually code. The code includes a URL that has a checksum that can only be created by the APEX_UTIL.PREPARE_URL function. When you create a button (or a list item) that targets a “Page in this application” PREPARE_URL is called internally for you and if the page is a dialog page the URL contains code. We want to call this code from the on page load dynamic action. The trick is how to get this code on the page in a way we can use without having to use eval. There are a few ways to do that. One that I think is the simplest is to create a hidden button that has an action to redirect to the dialog page. That is the purpose of the AUTO_OPEN button. This button is given a Static ID = autoOpenBtn so it can be easily referenced from the JavaScript code. Make sure the button has the Warn on Unsaved Changes attribute set to Page Default, otherwise the onclick attribute will be removed.

With all that explanation out of the way the JavaScript action looks like this:

$("#autoOpenBtn")[0].onclick();

Note: I use the above code rather than $("#autoOpenBtn").click(); because I just want to run the code assigned to that attribute. I don’t really intend to simulate clicking that button. There is no need for any other event handlers that may exist on or above the button element to run. This is a subtle but important distinction.

Note: This same technique of using a hidden button to programmatically open a dialog is used in the Sample Interactive Grids app Add Toolbar Button page.

The P1_OPEN_DIALOG hidden item and the AUTO_OPEN button were put in a region in the Inline Dialogs display position simply as a way to keep it all hidden.

As you can see this works but is very inefficient. Each time the dialog is opened the main page is submitted and the client is redirected to the same page which must be rendered by the server and returned to the client and then the client makes another request for the dialog page. That is 3 requests just to open a dialog! Also from a usability perspective any implicit state on the client such as current selections is lost or will take extra effort to make sure it is persisted.

I don’t recommend this method. Use it only if you absolutely must run validations and processes before opening the dialog.

Input Method 2:

If you need to do some processing on the server but don’t need the full functionality of page submit processing you could call a page process using ajax or use an Execute PL/SQL dynamic action.

The general idea here is to put your processing in a Execute PL/SQL dynamic action (or an ajax callback process) and to use a dynamic action on the button that opens the dialog that first calls the server side processing and then opens the dialog.

To see this in action again use the home page of the Dialog Demo app. Enter values for the inputs and then press the Open Dialog (after ajax) button.

The implementation of this method is much simpler than method 1. The button uses a dynamic action that has two actions. The first executes PL/SQL code (this uses ajax) passing in the two inputs. The PL/SQL code simply copies the input items to the dialog page session state items. It could do any other processing you need. Make sure Wait for Result is Yes so that the next action doesn’t run until the first is done. The second step opens the dialog using the same hidden button as used in method 1. See the open dialog after pl/sql process dynamic action for details.

This method is a little more efficient. It requires two requests to the server but an ajax request is lighter weight than posting and redirecting to the same page. It also doesn’t disturb the state of the main page.

One drawback compared to method 1 is that no validation of the inputs is done. This may or may not be desired. It may be reasonable that the dialog will be doing the validation. It is possible to do client side and/or server side validation with this method as well. The Dialog Demo app has another button Open Dialog (validate) that has a dynamic action similar to the one on Open Dialog (after ajax). The difference is that it has an extra first JavaScript action that validates the page. Here is the JavaScript code:

var ok = apex.page.validate();
if (!ok) {
    apex.message.alert( apex.lang.getMessage( "APEX.CORRECT_ERRORS" ), 
        function() { /* nothing to do */ } );
    return false;     // fail the DA
}  // otherwise go on to next action

The next two actions are exactly the same as before. This example used client side validation. The general idea can be extended to call another PL/SQL action to do server side validation. The new apex.message API will come in handy if you go down this road.

Input Method 3:

If you don’t need to do any server side processing then we can do much better by directly opening the dialog using a dynamically created URL with the data passed in the URL. It is common to branch to an APEX page supplying values in the URL. This can be done with a button that has Action = Redirect to Page in this Application (or for normal pages a branch process). For example:

Link Builder Target Dialog

The trouble is that if you try something like that to open a dialog, values such as &P1_INPUT_2. will use the current value from session state not the value that the user has entered. You can see this in action in the Dialog Demo app by clicking on the Open Dialog button. Notice that the dialog doesn’t get the input values that the user has just entered it gets whatever is currently in server session state. Sometimes this is exactly what you want; to use the values from the server. But if you need to use the current client side values in the dialog then the URL must be created on the client.

At first this may not seem possible because as has already been said the server side function APEX_UTIL.PREPARE_URL is the only way to generate the URL that includes the checksum and code to open the dialog. Another question that has come up on the APEX forum is how can I use the client side apex.navigation.dialog function if the URL must include a server generated checksum. This is really the same issue. We wouldn’t document an API (apex.navigation.dialog) if there were no way to use it so there must be a way. There is but it is not very intuitive. The checksum must be generated by PREPARE_URL but the API call (the code) need not be. The problem is that APEX currently provides no easy way to separate the two. I have already shown the general solution in APEX 5.0 Custom Menus but here I provide a refinement using a couple of reusable functions.

The general idea is to call APEX_UTIL.PREPARE_URL on the server and then strip out just the URL that includes the checksum and send it to the client in the rendered page. Here the URL is put in hidden item P1_DIALOG_URL. (In the APEX 5.0 Custom Menus article it was rendered as part of an Interactive Report.) Then on the client the URL is fixed up a little to unescape Unicode characters and passed to apex.navigation.dialog. The URL can have page item values added to it on the client.

To make this easier I added a PL/SQL function dialog_demo.prepare_dialog_url. This takes a URL that can contain $n$ style tokens to be substituted on the client. This function is used as the PL/SQL Function Body Source attribute for the P1_DIALOG_URL item:

begin
    return dialog_demo.prepare_dialog_url( 'f?p=' || v('APP_ID') || 
        ':2:' || V('APP_SESSION') || 
        '::NO:RP:P2_INPUT_1,P2_INPUT_2:$0$,$1$' );
end;

The definition of the prepare_dialog_url function can be found in a supporting object install script and is:


-- use $0$, $1$ ... for client side value substitutions
function prepare_dialog_url (
    p_url in varchar2 )
    return varchar2
is
begin
    return regexp_substr(
           apex_util.prepare_url( p_url ), 'f\?p=[^'']*');
end prepare_dialog_url;

On the client side I added (in an application file loaded on the home page) function dialogDemo.makeDialogUrl as follows:


    makeDialogUrl: function(url, args) {
        var i;
        args = args || [];

        // replace unicode escapes
        url = url.replace(/\\u(\d\d\d\d)/g, function(m,d) {
            return String.fromCharCode(parseInt(d, 16));
        });
        // %24 is $
        for ( i = 0; i < args.length; i++) {
            url = url.replace("%24" + i + "%24", encodeURIComponent(args[i]));
        }
        return url;
    }

The Open Dialog (with dynamic url) button invokes a dynamic action on click with this JavaScript code.

var url = dialogDemo.makeDialogUrl( $v("P1_DIALOG_URL"), [ $v("P1_INPUT_1"), $v("P1_INPUT_2")] );
apex.navigation.dialog(url, 
  {title:'Dialog In Out',height:'auto',width:'720',maxWidth:'960',modal:true,dialog:null},
  "t-Dialog-page--standard", "#btnOpenDialog");

Notice how the server side provides a URL to prepare_dialog_url with $0$ and $1$ placeholders and the client side substitutes values from the args array passed into makeDialogUrl.

The other two arguments to apex.navigation.dialog are normally generated by PREPARE_URL but they don't need to be. Here you can see that hard coded values are used. In theory the dialog options object could be extracted from the result of PREPARE_URL and sent to the client similar to how the URL is handled here. However this is not easy to do because the options object is JavaScript and not JSON. The triggering element is natural for the client to provide.

Click the Open Dialog (with dynamic url) button to see this in action. This is the most efficient method of passing data to the dialog. There is only one request for the dialog page itself. This is the method I recommend. This method is also a general example of how you can programmatically open APEX dialog pages. The functions makeDialogUrl and prepare_dialog_url can be copied to your own applications and used for a variety of use cases.

This technique can be used in place of the hidden button technique to open a dialog from JavaScript code. It is a little more involved but much more flexible.

It should be clear that you can also do validation with method 3 in the same way it was done for method 2.

Returning data

Now that we have covered each of the ways of passing data into a dialog page we turn our attention to returning data from the dialog to the page that opened it.

Returning the value of page items from the dialog to the parent page is built into the dynamic action Close Dialog action and the Close Dialog process. In both cases just set the Items to Return attribute to the list of page items.

Doing something with the data returned is accomplished with a dynamic action on the parent page. Create a dynamic action on the Dialog Closed event. If all you want to do is set page items in the parent page use Set Value actions where the Return Item is an item from the dialog page that was returned and Affected Elements specifies the parent page item to set. If you want to do something more custom with the returned data you can use a JavaScript action and reference the returned data using this.data.

Take a look at the Dialog Closed with OK dynamic action in the Dialog Demo app. You can find it on the Dynamic Actions tab of page designer. Because the dialog in this demo app can be opened from various different buttons I choose to listen for the dialog close event on the body element. This works because events bubble up the DOM. This dynamic action has 3 actions. One is a JavaScript action that just logs the returned values to the console but this demonstrates that you can do anything you want with the data. The other two actions are Set Value actions that update the display only output fields.

Note: I generally recommend listening for the Dialog Closed events on the document body. [Update 23-May clarification: if the dialog is opened from the global navigation menu list then the closed event is should be (once a bug is fixed) triggered on the document (apex.gPageContext$) so you may want to put the handler there] One reason for putting the Dialog Closed dynamic action on the triggering element (button in this case) is if different buttons open different dialogs. Then you can tell which dialog was closed. It is still possible to do this when handling the event on the document body. The Dialog Closed event has the page number in this.data.dialogPageId. (Currently this is only true if the dialog is closed with the Close Dialog process but it should be fixed someday to work with the action as well.) [Update 23-May as of version 5.1.1 the dialogPageId property is available for both the CloseDialog process and DA action.] You can gate the dynamic action using a JavaScript Client-side Condition such as this.data.dialogPageId == "2". It is also possible to detect the specific dialog by giving it a class in attribute Dialog > CSS Classes and using Client-side JavaScript Condition such as: $(this.browserEvent.target).parent().is(".myDialog")

To see data returned using the Dialog Demo app open the dialog using any of the buttons and enter values for Output 1 and Output 2. Then click either of the OK buttons. The values entered will be shown in the corresponding fields of the parent page.

If you are using a Close Dialog dynamic action to close the dialog (and not using any of the features of an APEX page such as submit processing etc.) you may want to consider if using a region dialog would be more efficient.

As you can see returning data entered in page items of the dialog page is very easy. Another possibility is that the dialog page made a change to the persistent state of the application that affects the parent page. An example is inserting or deleting records from a table that has records being displayed on the parent page. In this case once the dialog is closed you will want to refresh one or more regions (for regions that support being refreshed) or possibly even reload the parent page.

The Sample Dialog app (you can install this from Packaged Apps) has a good example of refreshing an Interactive Report from a Dialog Closed dynamic action. In 5.0 it would refresh the whole page because that was the only way to get the success message of the dialog page displayed. Now in 5.1 the sample has been updated to use the new client side messaging API so that just the interactive report region can be refreshed.

The code in the Dialog Closed dynamic action should look like this (I made some improvements):

if ( this.data.successMessage ) {
    // use new API to show the success message if any that came from the dialog
    apex.message.showPageSuccess(this.data.successMessage.text);
}
// Refresh the report region
apex.region("department_report").refresh();

If, as a last resort, you need to refresh the parent page you can use code similar to what the Sample Dialog app used to do in 5.0.

var lSuccessMsg = this.data.successMessage.urlSuffix,
    lUrl = 'f?p=&APP_ID.:1:&SESSION.::&DEBUG.:::';

if ( lSuccessMsg ) {
   lUrl += lSuccessMsg;
}
setTimeout(function() {
    apex.navigation.redirect(lUrl);
}, 0);

Why not just have the dialog page branch to the parent page? Because that breaks the rule stated in the beginning about dialogs not knowing from where they were opened.

I hope this article covers all the situations you may encounter for passing data into and returning data from APEX dialog pages. If not let me know in the comments. There are some other methods similar to what a single page app might do but they are more suitable to region dialogs and are not covered here.

6 thoughts on “Passing Data in and out of APEX Dialogs

  1. Great write up. Really good techniques.

    The situation I am facing is that from Page 1, I have to open Dialog Page 2 (for search item a) and then Dialog Page 3 (for search item b). I used Input Method 2. However, when Dialog 3 closes, I loose the value in search item a (which was set by Dialog Page 2). Is there an easy way to address this?

    Thanks for your help.

  2. @kevin Sorry I don’t have a 5.0 version of the demo app. You can easily request a free workspace at apex.oracle.com and you can upload the demo app there to see how it works. This is a fun easy way to get to see the new 5.1 features.

  3. Do you have a APEX5.0 version for the download of this demo application? It seems the one only work for the APEX5.1. I don’t have APEX5.1 here.

    Again, this is a super useful thread. Thank you!!!

  4. Really useful post. Ideally it would be a little bit easier to do some of these things but perhaps in a future apex version.

    Input method 3 is potentially something I will need to use. If I understood correctly, it is possible to calculate the dialog checksum first and substitute page item values afterwards? I had assumed the full url must be constructed with item values before preparing the url?

    Or are page checksum and this dialog checksum unrelated? Under what circumstances would the dialog checksum need to be recalculated?

Comments are closed.