Reusable Region as a Modal Page

"clone" icon

On a number of pages throughout my application, I needed to build a region containing a fairly complex set of items, along with dynamic actions and other controls to provide a friendly editing experience for the user. This non-trivial set of items with their accompanying dynamic actions and conditions would be needed on several different pages, and in some cases, multiple times on the same page.

Copying all this all over the place would have created a maintenance headache, so I would much prefer to build them only once, and then re-use the same component throughout my application. Unfortunately, APEX does not at this stage support the concept of a reusable region. An idea might be to allow a region to “subscribe” to another region – although this would be tricky because somehow the item names, dynamic action names, etc. would need to be unique but predictable.

Why not use a plugin?

One approach is to build the whole region as a plugin; this would be ideal as the plugin can then be maintained separately and deployed wherever it’s needed; this would have the benefit that it could be reused in multiple applications.

The downside is that I would not be able to use the declarative features of APEX to define the items and dynamic actions within the region; I would have to code most of that in custom HTML, JavaScript and AJAX calls for database interaction. This would then provide a different maintenance challenge for my successors.

Why not put the region on the Global Page?

Another approach would be to build the region on the Global Page; a condition could be used to show it if it’s needed by the current page.

The downsides to this approach include: (a) you can’t reuse it multiple times on a single page; (b) it may be tricky to integrate it on the pages it needs to return data to (although this could be done with some JavaScript); and (c) you have little control over where on each page the region would be shown.

The Global Region idea might work better if is implemented as an Inline Dialog; with some JavaScript it could be made to pop up wherever it’s needed. I haven’t tried this approach, however.

Use a Modal Page

Instead, the approach I took was to use a modal page. This is a page that will pop up as a layer on top of the calling page, making the calling page visible but non-responsive until the user closes the popup. I can then define all the items needed, along with their conditions and dynamic actions, in the one modal page, and then add buttons throughout my application wherever it was needed.

The calling page needs to pass the current value of one or more items to the modal page; these values are not in the database (yet) because the user may be in the middle of editing them, so their current value on screen may be different to the value stored in the table. This means I can’t have the modal page reading the value from the table, and I can’t just pass the value using the link attributes because these are set in stone when the page is rendered.

In order to open the modal page, then, I need to use a dynamic action.

Note that you can’t build the URL for the modal page in JavaScript, because the client-side code cannot calculate the checksum required by the modal page. Instead, I pre-calculate the URL for the modal page using apex_page.get_url which generates the checksum automatically.

When the user clicks the “Edit” button, it needs to first copy the current value of the item into the session state for the modal page; I do this by making the Edit button Defined by Dynamic Action. On click, it executes two actions: (1) Server-side Code to submit the current value of the text item and set the modal item’s value; then (2) JavaScript Code to redirect to the URL I calculated earlier.

The modal page is then shown, allowing the user to make changes to the value. When they click the “OK” button, the modal page closes and returns the value via Items to Return.

Note that the modal page itself never saves any changes to the database, since on the calling page, the user might decide to cancel.

Back on the calling page, the new value is copied back into the page item via a Dialog Closed dynamic action. This sets the value based on the Dialog Return Item.

I’ve built a “dummy” sample app to demonstrate this technique. You can try it out, and download the sample app definition, from here: https://apex.oracle.com/pls/apex/jk64/r/demo-reusable-modal/home

Details

Here is my main page definition, with two regions. Each region has an item that we want to pass to/from our modal page.

Page designer showing two regions defined, "Region 1" and "Region 2"

Each region needs a unique Static ID.

Page designer showing the Static ID for "Region 1" is set to "region1"
Page designer showing the Static ID for "Region 2" is set to "region2"

Each region has a visible Value item, an Edit button, and a hidden item to precalculate the URL for the modal page.

Page designer showing Region 1 has an item "P1_VALUE1", a button "EDIT1", and a hidden item "P1_EDIT_URL1". Region 2 similarly has an item "P1_VALUE2", a button "EDIT2", and a hidden item "P1_EDIT_URL2".

There are no special attributes on the value item(s); they could be a simple text field, a text area, a readonly item, a combination of various item types, or they could be hidden. Typically they would be based on database column(s) and saved in the record being edited.

The “EDIT URL” hidden items are precalculated using an expression, and set to Always, replacing any existing value in session state.

Page designer showing the PL/SQL Expression as the source for P1_EDIT_URL1.

The other edit URL is similar.

Page designer showing the PL/SQL Expression as the source for P1_EDIT_URL2.

The call to apex_page.get_url is used to pass some static values (that are not changed by the page at runtime) to the modal page. These values may be used by the modal page to customise it for the context it was called from.

apex_page.get_url(
    p_page => 'modal',
    p_items => 'P2_ID,P2_OPTION',
    p_values => :P1_ID || ',' || 'Region 1',
    p_triggering_element => '$(''#region1'')'
)

Note that the value of the item is not passed in the URL.

Note that p_triggering_element is a string, constructed to be a jQuery selector referring to the Static ID that was set on the region, so that the right Dialog Closed event will fire (since we may have multiple Edit buttons on the same page).

Tip: if your modal page doesn’t need them, you can omit the p_items and p_values parameters.

The Edit buttons are set to “Defined by Dynamic Action“.

Page designer showing EDIT1 with a dynamic action "on click edit1".

The Server-side Code simply copies the current value of the item into the modal page’s item. This sets the session state on the server, which is then loaded when the modal is opened.

Page designer showing the Execute Server-side Code action runs the code ":P2_VALUE := :P1_VALUE1;", with Items to Submit set to "P1_VALUE1".

The JavaScript Code redirects to the modal page using the URL we calculated on page load.

Page designer showing the Execute JavaScript Code action.
apex.navigation.redirect("&P1_EDIT_URL1.");

The JavaScript Code for Region 2 is the same except it refers to P1_EDIT_URL2.

On page 2, the modal page, I have contrived an example “calculator” which simply breaks the string value into two “parts”, and allows the user to edit each “part” separately; when they click OK, the concatenated value gets returned to the calling page.

Page designer for page 2 ("modal"). It has two editable items "P2_PART1", "P2_PART2", as well as a "hidden items" region with "P2_VALUE", "P2_ID" and "P2_OPTION". The page also has a button region with "CANCEL" and "OK" buttons.

The two “PART” items are calculated on page load with some PL/SQL:

The page has an After Header process "init" which computes some value for P2_PART1 and P2_PART2 based on the value of P2_VALUE. (The expression itself is not important.)

Note that this code is being executed based on the value of P2_VALUE which was set in session state by the calling page.

Just for the sake of the demo, my “calculator” merely sets the value of the hidden P2_VALUE item based on concatenating the two “parts”:

Dynamic action on change of P2_PART1 and P2_PART1 executes some JavaScript which sets the value of P2_VALUE based on the entered values of P2_PART1 and P2_PART2.

Note: you would define whatever items, dynamic actions or other components that you need.

This modal page never saves any changes to the database; that’s the role of the calling page.

The OK button simply closes the dialog, returning the new value of P2_VALUE to the calling page.

Page designer showing the "on Click OK" dynamic action. It runs the Close Dialog action. The Items to Return is set to P2_VALUE.

Back on the calling page, each region has a dynamic action defined on Dialog Closed.

Page designer on page 1, within Region 1, a dynamic action "on close modal1" on the event Dialog Closed.

The Set Value action copies the Dialog Return Item value into the appropriate item on the page.

Page designer on page 1, the "on close modal1" dynamic action runs a Set Value to set the item P1_VALUE1 to the Dialog Return Item, P2_VALUE.

Summary

To use my special modal page in my application, I need to:

  1. Set a unique Static ID on the region
  2. Add an Edit button with a dynamic action
  3. Add a hidden URL item based on an expression
  4. Add a dynamic action to the region on Dialog Closed

The outcome is that the modal page provides a user-friendly experience involving any complex items, lists, dynamic actions, conditions, etc. maintained in one place, which can be re-used anywhere needed in the application.

If you would like to examine in detail the demo app, you can download it from here: https://apex.oracle.com/pls/apex/jk64/r/demo-reusable-modal/home (click the “Download this demo app” link). You may then install this in your own workspace and check out how it all works.

Have you had a similar requirement in your apps? Comment below and describe how you implemented it.

I didn’t change anything… yet I get “Unsaved changes”?

Leave a Reply

Your email address will not be published / Required fields are marked *