8 Understanding Page Flow & Session State#

Your app helps end users get their jobs done, and they expect easy access to all tasks they can perform. Your navigation menu shows them what business functions are available, and your navigation bar list lets them access app options.

Often a task they initiate requires visiting multiple pages to complete. You define this "flow" of pages using branches that link one page to another. Breadcrumbs or a Wizard Progress list help users understand where they are in a flow and how to go back. Modal dialogs provide another option to perform related actions and appear on top of the page that branches to them. In addition to these facilities to define and access page flows, APEX also dramatically simplifies their development by managing user sessions and the temporary storage of data values across pages. To cement your understanding of these fundamental APEX concepts, this section explores a three-step wizard flow and a typical list/edit flow.

8.1 Navigating to Business Functions#

After end users log in, they expect to see a list of tasks they can do.

You define Navigation Menu list entries to give users this task-focused guidance. The figure below shows six entries on the collapsible Navigation Menu on the left. Clicking any of the choices navigates the user to the page that initiates the selected business function. For example, the business functions listed for this Exampletown HR application are Post Job Application, Enter Timecard, Onboard New Employee, Approvals Inbox, Review Vacation Balance, and Request Vacation.

When you use the Create Page wizard, you choose whether to add a navigation menu entry for your new page. You can also define a new entry any time in Shared Components > Navigation Menu. For more information, see Managing Navigation Menus in Oracle APEX App Builder User’s Guide.

Figure 8-1 Navigation Menu Lets Users Initiate Business Functionality

8.2 App Options in the Navigation Bar#

Users look for settings and sign out options in a dropdown menu under their username. Your Navigation Bar List entries define the items that appear there.

The figure below shows three entries under the logged-in user's name: Settings, About this Application, and Sign Out.

The Create Application wizard sets up an initial Sign Out entry, and other features like About Page, and Push Notification add entries, too. You can define a new entry in Shared Components > Navigation Bar List. For more information on this shared component, see Managing Navigation Bars in Oracle APEX App Builder User’s Guide.

Figure 8-2 Navigation Bar List for Application Options and Logout

8.3 Configuring a Wizard Page Flow#

A wizard is a multi-step UI pattern that helps users complete a task intuitively.

A wizard guides users through a sequence of smaller, structured actions in a logical order, one page at a time. The user sees a "route map" at the top. It identifies the "stops" along the way to the destination. For example, onboarding an new employee involves entering their identifying info, job role, and pay details. The figure below shows a simple example of a three-page employee onboarding wizard. Users expect to enter data across the steps and go back to review data in a previous step if needed before completing the task by clicking (Finish) on the last step.

Your application's multi-step wizard contains multiple pages linked by branches for forward and back navigation. Progress indicator list entries define the wizard step titles.

Figure 8-3 Three Step Wizard for Onboarding an Employee

8.4 Additional Page Flow Features#

In addition to getting rendered and submitted, APEX pages also uses background data requests and support redirects to navigate without submitting data. Breadcrumbs help visualize the navigation path of drill down page flows. Modal dialogs offer an alternative to present related information without losing the context of the calling page.

8.5 Understanding Session State Management#

When an end user submits a page, sometimes you save their input to application tables immediately. Other times, to improve usability or manage file uploads, you need to store values temporarily and save them later.

The APEX engine manages this temporary storage across each user session, including application values, value collections, and uploaded files. These stored values are collectively known as "session state".

8.6 Studying a List and Edit Flow#

The Create Page wizard builds list and edit pages for any data source in a few clicks. Understanding how those pages work together helps you use them well.

The wizard relies on declarative features you can reuse when customizing generated pages or building a Blank Page. The same ideas apply to any list page paired with an edit form.

8.3.1 Creating a Multi-Step Wizard#

The Create Page wizard automates making your app's wizard. It generates the wizard's multiple pages linked by branches, with progress indicator list entries that define the wizard step titles.

As shown below, you need to create three pages for this Onboard New Employee wizard. Here they are numbered 43, 44, and 45. Each page includes a wizard progress indicator at the top showing the steps involved and the current step. This area includes appropriate buttons to proceed to the next step, return to the previous step, finish the task, and cancel the flow. Each page also contains page items for entering data appropriate to the step at hand.

Figure 8-4 Wizard Container on Each Page Shows Steps in a Flow

The Create Page wizard sets up this multi-step page flow. Choosing the Wizard tile in the Create Page dialog and clicking (Next >) shows the Create Wizard step shown below. There you define the name of the wizard, the step labels and page numbers, and pick a navigation menu icon. Clicking (Create Wizard) creates the pages and opens the first page in Page Designer.

Figure 8-5 Defining the Wizard Steps in the Create Wizard Dialog

8.3.2 Multi-Step Wizard in Page Designer#

After the wizard creates the initial pages, use Page Designer to add step-specific content to each one.

The figure below shows page 44 in Page Designer after adding the three page items into the Job Role region the wizard created. Notice this region is selected in the Rendering tab and that the Property Editor shows it's a List region. The Job Role list region uses the Wizard Container template option and the Wizard Progress list template. This choice renders the list as a sequence of "train" stops. It is one of 13 useful ways a list region can display a list of pages. In the example below, the shared List component named Onboard New Employee provides the page list. The list region's Previous, Close, and Next slots contain buttons that the Wizard Container template shows in line with the steps.

Figure 8-6 Wizard List Region Shows List of Pages as "Train" Stops

8.3.3 Shared List Defines Wizard Pages#

The page entries in a shared component list define the wizard "train" stop titles and their sequence.

The figure below shows the definition of the Onboard New Employee shared component list. It contains the three page entries for pages 43, 44, and 45, with appropriate names that the List region displays as step titles of the "train" stops. The Wizard Progress list template configured on the list region in each wizard page determines how the list entries render.

Figure 8-7 List Shared Component Defining Wizard Step Pages and Titles

8.3.4 Branches Link Pages to Define Flows#

You establish the page navigation flow an end user can follow by defining branches.

In Page Designer's Processing tab, you create a branch to identify a target page the user should see next, optionally passing along parameter values. By setting the When Button Pressed property on the branch to the name of a button, you indicate the branch should happen only when the user clicks that button. As shown below, branches connect the three pages of the Onboard New Employee wizard:

  • Page 43 has a branch for the
    • (Next) button that targets page 44.
  • Page 44 has a branch for the
    • (<) previous button that targets page 43, and
    • (Next) button that targets page 45.
  • Page 45 has a branch for the
    • (<) button that targets page 44, and
    • (Finish) button that targets a home page (1).

Each of the three pages configures its (Cancel) button to redirect to the home page (1) and this button's Warn on Unsaved Changes property to Do Not Check. A redirect acts like a hyperlink. It navigates the user to another page without submitting any data from the current page for processing. Later sections explain how branches and redirects differ. The figure shows the three wizard pages and the branches their buttons trigger to navigate between pages.

Figure 8-8 Branches Connect Three Pages in Onboard New Employee Flow

8.3.5 Render, Submit, Branch, Repeat#

At runtime, the model-driven APEX engine breathes life into your page and component definitions using a simple lifecycle: render, submit, branch, and repeat.

8.4.1 Understanding AJAX and Redirect#

In addition to the page render and submit interactions with the APEX engine, your page can also run business logic on the server and refresh regions to show the latest data without re-rendering the entire page.

This involves a web application technique called an "AJAX" request, where your page quietly talks to the server in the background to get or send information without refreshing the whole page. APEX makes the process a no code experience. When appropriate, your page can also navigate to another page without submitting user changes to the server. This is known as redirecting to another page, instead of submitting and branching to another page.

As shown below, during an AJAX request APEX sends data from the page to the server and it receives a response containing data to update the page. Two common features that use AJAX requests are executing server-side business logic and refreshing a region. APEX handles the lower-level communications with the server while you focus on what your application needs to accomplish.

The Execute Server-side Code dynamic action lets you invoke server-side PL/SQL or JavaScript. You configure the Items to Submit and Items to Return properties that inform APEX what to include in the data sent to the server and what to return back to the page. You author only the application-specific logic that only you could write.

When you use a Refresh dynamic action to make a region show the latest data, APEX performs an AJAX request to do the job. In this case, the region's Page Items to Submit property determines the data to send. The response includes the HTML markup and data for the partial page update that APEX applies automatically to refresh just that area of the page.

When one of your pages redirects to another, it can pass along parameter values like a branch does. The key difference is that any end user data changes on the current page are not submitted to the server, so they do not update user session state values. By default, if there are unsaved changes on a page, the user sees a warning before your app redirects to another page. You can control this warning using the Warn on Unsaved Changes property at page level and on individual page items as needed. For example, if a page item is used as a query-by-example filter and you've configured it to have session state storage of Per Request (Memory Only), then the fact that the end user changed the filter value does not matter. The diagram below illustrates the difference between the default render, submit, and branch lifecycle and the AJAX and Redirect mechanisms. The database table in the figure shows the session state values the APEX engine manages automatically.

Figure 8-13 AJAX Requests and Redirects Complement Render, Submit, and Branch

8.4.2 Breadcrumbs Visualize Drilldown Path#

Users often drill down from a list row to view or edit details. If that page shows related records, they can keep following those relationships. Breadcrumbs show their place in the flow and let them return to higher levels.

Consider the data model shown below with employees having one or more dependents. The diagram shows the familiar EMP table and a related EMP_DEPENDENTS table that stores the name and birthday of an employee's family members.

Figure 8-14 Data Model for Employees Having One or More Dependents

The figure below shows how four pages are linked by redirects to create a multi-level drilldown page flow. The user starts in the Manage Employees interactive report page 3. Then they can drill down to view the details of a particular employee like SMITH in Employee form page 6. There they can click a (Dependents) button to redirect to the list of SMITH's dependents in the Dependents page 7. Finally, the user can drill down to view the details of one of SMITH's children Janie in the Dependent form page 9.

Figure 8-15 Four-Level Drill Down Page Flow for Employees and Dependents

Every page includes a breadcrumb region that is based on the same breadcrumb shared component. It defines a hierarchy of page breadcrumb entries that the breadcrumb region uses to render the navigation path based on the current page in the hierarchy. As shown below, the breadcrumb entries reflect the four level page hierarchy using the Parent Entry setting in each entry definition. Notice an entry like Employee page 6's can reference &P6_ENAME. to include the employee name in the breadcrumb short name, and the Dependent form page 9 uses &P9_NAME. for the dependent name.

For the P6_ENAME to render correctly for subsequent pages in the flow, it's necessary to configure its storage to be Per Session (Persistent). This is also how the P6_EMPNO is configured to ensure navigating back to page six uses the right employee number value. The diagram above reflects these session state values in the database table.

Figure 8-16 Editing Breadcrumbs Shared Component to Reflect Navigation Hierarchy

At runtime, the end user selects Manage Employees from the navigation menu and sees page 3 as shown below. Notice the breadcrumb region renders the current page's breadcrumb short name Manage Employees in a large font. The interactive report region shows the clerks the user has filtered. Employees SMITH is one of these.

Figure 8-17 Manage Employees Page Redirects to Employee Form Page for Edit

If the user clicks on the edit icon next to SMITH, they drill down to the Employee form page 6. The breadcrumb region renders a link for the Manage Employees page in a smaller font, and the current page's breadcrumb entry in a larger font. Recall that page 6's breadcrumb short name was &P6_ENAME. so this substitution renders as SMITH in the breadcrumb.

Figure 8-18 Drilling Down on Employee to See Their Dependents

If the user clicks on the (Dependents) button for employee SMITH they drill down to the Dependents page 7. Since P6_EMPNO is in session state, and the user only visits page 7 after first going to page 6, you can reference the current employee number in the interactive report's WHERE clause:

EMPNO = :P6_EMPNO

This ensures the page only shows the dependents for the current employee being edited. Notice in the fugure below the breadcrumb now shows two levels of links to navigate back to, and again shows the Dependents current page breadcrumb shortname in a larger font. The table shows SMITH's dependents Janice and Jimmy with their birthdays.

Figure 8-19 Viewing Employee SMITH's Dependents and Drilling Down Again

Finally, when the user clicks the edit icon next to SMITH's dependent Janice, they end up in the Dependent page 9. The Dependents page passes the ID value of the current dependent to the edit form. Notice the breadcrumb region now shows three levels of navigation links. The user can click on any of the links to navigate back to a previous level of the drill down page flow.

Figure 8-20 Viewing SMITH's Dependent Janie: Breadcrumb Shows Path "Home"

8.4.3 Modal Dialogs and Drawers#

A modal dialog appears centered above the page that opens it. By default, it sizes to its content, but you can set its width and height or make it fill the available space. Configured as a drawer, it slides in from the start or end edge.

You create a modal dialog page by setting its Page Mode property to Modal Dialog. While it displays, the user can't interact with the calling page below. When the user cancels or closes the dialog, it disappears and the calling page becomes active again. The figure below shows an Employee edit page as a modal dialog above an Employees interactive report page.

Figure 8-21 Modal Dialog Form Page Called from a List Page

If you set the page's Dialog Template property to Drawer, you opt for a different behavior. The drawer shown below is an alternative way to present a modal page. It occupies the full height of the window and slides out from either the start or end edge of the window.

Figure 8-22 Modal Drawer Slides Out from the Start or End of the Window

In either case, you can close the dialog page using the Close Dialog dynamic action or the Close Dialog page process. These close dialog methods can optionally return one or more page item values to the calling page. A Dialog Closed dynamic action event handler can reference these Dialog Return Items back in the calling page. You can cancel the dialog using the Cancel dynamic action.

If your modal dialog branches or redirects to another modal dialog of the same kind, set the Chained property to On to have the called page reuse the current modal dialog's window. Setting this property to Off causes the called page to open in another modal dialog window on top of the current modal dialog.

8.5.1 Use Cases for Temporary Storage#

Use temporary storage for multi-page tasks, query-by-example searches, temporary data collections, and file uploads.

Four key use cases that require temporary storage include:
  • Tasks Involving Multiple Pages

    User-entered values need to "hang around" while a user visits multiple pages to work on a task. Your app saves these temporary values only once the user confirms they have completed the job. A three-page Onboard New Employee wizard is a perfect example.

  • Query-by-Example Search Pages

    Users may appreciate their filter criteria "staying sticky" across multiple uses of a particular business function during their session. Their criteria values are not meant to ever be saved in an application table, but their temporary storage during the user's session improves usability.

  • Temporary Data Collections

    Imagine a shopping cart page flow where the end user repeats a sequence of searching for an item, adding it to their cart, and adjusting the quantities they want to purchase. Until the user clicks (Check Out) to make a purchase, the set of product codes and quantities in their cart is a temporary collection. During order creation, your app iterates through the shopping cart items to create the permanent order lines of the new order.

  • File Uploads

    In a page flow where a user requests a medical insurance reimbursement, they may need to upload one or more attachments to support their claim. The uploaded files need temporary storage until your business logic can move their contents to appropriate application tables when a task completes.

8.5.2 Session Context for Temporary Values#

A user session is the span of time during which a particular end user interacts with your web application.

It begins when the user first navigates to a page and logs in, and ends when they sign out or remain idle long enough to trigger a session timeout. At any moment, multiple users may be using your app, each working on different tasks. To streamline the experience for all concurrent users, APEX assigns a unique session ID to each one. This gives the context for each user’s interaction with the app and for the session state the APEX engine manages for each user session.

8.5.3 How APEX Manages User Session State#

For the Onboard New Employee wizard page flow shown below, you only want to insert the new employee row in the EMP table when the user clicks (Finish) on the last page.

The user expects to click (Next) and (<) previous between the pages without losing any of their data entry when returning to review previous pages. You can configure the page items in pages 43, 44, and 45 to use APEX's automatic session state management to temporarily store the user's submitted values so you can decide when to save them permanently in your application tables. To do this, simply configure its Session State > Storage property to Per Session (Persistent). The diagram illustrates the page render, submit, and branch flow of the three-page Onboard New Employee wizard flow. The session state values the APEX engine managed for the current user session display in the table, tagged with the application ID, page ID, and item name. For simplicity, the session ID of the current user is not shown in the figure, but all session state values are partitioned and secured based on that context value as well.

Figure 8-23 Automatic Session State Management

When the APEX engine renders a page, it automatically uses any page item values from session state before your Pre-Rendering computations, processes, or branches execute. Then when rendering is complete, it stores the values of any page items whose Session State > Storage property is set to Per Session (Persistent) into session state.

When the user submits a page, the APEX engine lets your computations, processes, and branches reference the values of all page items. When submit processing is complete, the it saves the values of any Per Session (Persistent) page items from the current page. This occurs before it follows a branch to a new page or re-renders the existing page.

It "forgets" any page items whose Session State > Storage property is set to Per Request (Memory Only). If your processes do not permanently store these page item values, or pass them along to the next page to render as parameters in the link, then they are no longer available.

The APEX engine stores the application id, page number, page item name, and value for every session state item. It also ensures complete isolation between user sessions so one user session can only see its own session state values.

The figure shows the contents of the user session state after a user has visited each of the three pages in the Onboard New Employee wizard and returned back to view previous steps as well. All page items in pages 43, 44, and 45 have Per Session (Persistent) storage. When the user clicks (Finish) on the last page, the page process related to that button passes the values from the relevant page items in pages 43, 44, and 45 to the ADD_NEW_EMPLOYEE procedure that permanently stores the new employee's data in the EMP table.

8.5.4 User-Specific Temporary Storage#

To store a page item's value as a user preference that spans multiple sessions, set its Session State > Storage property to Per User (Persistent).

This is useful for query-by-example page item values or other options you want to remain exactly as the user left them the next time they visit your application. While the APEX_UTIL package has the SET_PREFERENCE procedure and the GET_PREFERENCE function, setting a page item's storage to Per User (Persistent) is an even simpler way to enable a user preference.

8.5.5 Clearing Session State#

The APEX engine clears session state values for a user session when it ends.

This includes page items, collections, and temporary files. The cleanup happens when the user signs out of the app, or when they are idle long enough to trigger a session timeout. There are also two declarative ways to clear selective session state values during the session.

8.5.6 Application Items#

An application item is a named value holder that persists for the duration of the user session.

You define them in Shared Components > Application Items. A typical use case is a USER_ID item storing the numerical id of the currently logged-in user.

Many enterprise apps have some form of a USERS table storing the username, email address and other information about users. Other tables in the app data model reference the ID primary key of this USERS table. However, users authenticate to the application using their username. To avoid having to lookup the ID value from the USERS table using the logged-in username over and over throughout the app, define a USER_ID application item and lookup its value at login time. Then any page in the application can reference the application item's value using #USER_ID#, &USER_ID., or :USER_ID depending on the context.

The application item is the value holder. To compute its value after the user authenticates, use a companion application computation shared component. As shown below, the app computation references the USER_ID app item name, chooses a Computation Point of After Authentication, and sets a value using one of various supported approaches. Here a SQL query computes the value by referencing the :APP_USER bind variable that provides the logged-in username:

select id
  from users
 where username = lower(:APP_USER)
Figure 8-27 App Computation Sets the Value of App Item After Authentication

Any SQL or PL/SQL throughout your app can reference the value of an application item using an :ITEM_NAME bind variable and PL/SQL code you write in a page can assign a new value to it. To assign a new value from PL/SQL functions or procedures, use the SET_VALUE procedure in the APEX_SESSION_STATE package.

8.5.7 Using Temporary Collections#

Collections let APEX store one or more rows of temporary information. At runtime, you create a named collection with the APEX_COLLECTION package. A session can contain multiple collections, and APEX clears them automatically when the session ends.

8.5.8 Temporary Files#

Both the APEX File Upload and Image Upload page items provide a Storage Type setting of Table APEX_APPLICATION_TEMP_FILES. Both support uploading either single or multiple files.

When you configure their Purge File at setting to End of Session, then the uploaded files remain queryable in the APEX_APPLICATION_TEMP_FILES table for the duration of the session. Only files pertaining to the current session appear in the table.

The value of the NAME column uniquely identifies each temporary file. This is different from the FILENAME column in the table that reflects the actual name of the file. For a File Upload or Image Upload item that supports a single file, you can query the uploaded filename, MIME type, and binary contents with a query like:
select blob_content,
       mime_type,
       filename
  from apex_application_temp_files
where name = :P1_SINGLE_FILE_UPLOAD
In contrast, if the page item allows multiple files, then its value will be a colon-separated list of unique uploaded file names. So the query needs to handle that list like this:
select blob_content,
       mime_type,
       filename
  from apex_application_temp_files
where name in (select column_value
                 from apex_string.split('P1_MULTI_FILE_UPLOAD',':'))
You can proactively clear the table using a regular SQL statement like:
DELETE FROM APEX_APPLICATION_TEMP_FILES

To only remove selected temporary files, just add an appropriate where clause.

8.5.9 Inspecting Session State for Debugging#

When running your app from App Builder, the developer toolbar appears along the edge of the browser window and offers options useful for debugging.

Clicking the developer toolbar's Session > View Session State option opens a window with the session state browser. The figure below shows developer LUCY browsing her current session state for page 44 of the Onboard New Employee wizard. It shows the values of all page items stored persistently for the active user session.

Figure 8-28 Browsing Page Item Session State for Page 44

As shown below, you can also view application items, collections, and background executions. All of this information is useful when trying to understand why your APEX application is not working as you expected. The figure shows the Session tab of the session state viewing page, with the View select list open containing session state types you might need to inspect.

Figure 8-29 Browsing Application Items and Other Kinds of Session State

8.6.1 Creating List and Edit Pages#

The Create Page wizard's Interactive Report flow features an Include Form Page switch. Other report types offer a similar option.

Enabling it lets you name a companion form page. In this example, you create a Manage Employees report page 3. By enabling Include Form Page, you also generate an Employee form page 4. By default, its Form Page Mode is a modal Drawer that appears on top of the list page by sliding in from the side.

This page combo lets your end users find existing rows to update or delete, as well as insert new ones. This example uses the EMP table in the local database, but the pages work the same way with any supported data source.

Figure 8-30 Create Page Wizard Choices for List and Edit Pages on the EMP Table

Scrolling the dialog contents to reveal the wizard's Navigation options, as shown below, you can opt to include a breadcrumb entry and region on the new page, to add a new navigation menu entry, and to customize the page icon by picking an icon name like fa-users-alt from the list of APEX icons. After you click (Next >), confirm the EMPNO column as the primary key, and click (Create Page). App Builder creates the two new pages.

Figure 8-31 Configuring Breadcrumb, Navigation Menu Entry, and Icon

8.6.2 Running the List and Edit Pages#

Running your app, as shown below, the new navigation menu entry appears with the fa-users-alt icon you chose.

The figure shows the new Manage Employees entry in the Exampletown HR app's menu.

Figure 8-32 Navigation Menu Entry for Newly Created List Page

Clicking on Manage Employees in the navigation menu, you see the new list page you created shown below. It has a (Create) button to add a new employee. You can immediately see, search, and filter the employees. Since you are running the page from App Builder, notice the developer toolbar also appears at the bottom to provide quick access for iterative page development, inspecting session state, enabling debug tracing and viewing debug logs, as well as interactively customizing theme style settings.

Figure 8-33 New Interactive Report Page Showing Employees

If you click the edit icon in the link column next to one of the employees, as shown below, the modal drawer page slides in from the right on top of the employees list to let you edit or delete the selected employee. If you prefer to have the developer toolbar stay mostly hidden until you need it, you can enable its Auto Hide setting from the "gear" settings icon in the toolbar. As shown below, in that mode it stays largely offscreen until you click on it to "pop" it up.

Figure 8-34 Interactive Report and Form Modal Drawer Pages in Action

8.6.3 Linking to the Edit Page#

The wizard configures the Link Column feature to target the form page it creates. This sets up the drill down behavior to let the user view details or edit a row in the report.

After selecting the interactive report region in the Rendering tab and viewing its Property Editor Attributes tab, notice Link Column is set to Link to Custom Target. Clicking on the Target button labeled Page 4, the Link Builder dialog opens. You see the wizard configures a link targeting page 4: the number of the newly-created Form page. In the Set Items section, it adds the name of the target page's P4_EMPNO primary key page item, and the #EMPNO# substitution as the value to pass as a parameter. This expression references the value of the EMPNO column in the current row of the report.

Notice the Value \#EMPNO#\ expression includes surrounding backslashes. This annotation tells the APEX engine to use value verbatim, even if it contains delimiter characters like a colon (:). Strictly speaking, the backslashes are not required to pass a simple EMPNO value that contains no delimiters. However, the wizard does this to ensure the link parameter value is passed correctly for any column.

Figure 8-35 Examining Wizard-Configured Link Column Settings

Notice the value of the Link Icon property is the following HTML markup:

<span role="img" aria-label="Edit" class="fa fa-edit" title="Edit"></span>

The class attribute value indicates which icon to display. It uses a space-separated value containing fa to signal a Font APEX icon type and fa-edit, the native APEX icon name for a square with a pencil. The title attribute provides the text "Edit" an end user sees when hovering their mouse over the icon. The aria-label attribute ensures accessibility by specifying a label screen readers recognize.

The net result is that a native APEX fa-edit icon displays in the link column. When the user clicks it they are redirected to the modal drawer page 4, passing along the EMPNO primary key value of the clicked-on EMP row as the value of page 4's P4_EMPNO page item.

8.6.4 Retrieving Existing Row by Primary Key#

The Form page has a page process in the Before Header phase of the Pre-Rendering section to retrieve the row to edit by primary key.

As shown below, in the Rendering tab, it uses the native Form - Initialization process type. This performs the lookup by primary key of a row to display, edit, or delete. Its Form Region property associates it with the Employees form region on the page. The page process uses this related form region to discover the data source to query and which columns comprise its primary key.

Figure 8-36 Form Initialization Page Process Retrieves Row by Primary Key

The figure below shows how the wizard configures the form region's data source. With the Employee region selected in the component tree, the Table Name property in the Source group shows EMP.

Figure 8-37 Form Region Using EMP Table as Data Source

In a similar way, each page item in the form region has a corresponding Source column name configured. For example, the figure below shows the wizard configures the P4_EMPNO page item to use the EMPNO column as its source and indicates that it is the primary key.

Figure 8-38 Form Initialization Page Process Requires a Primary Key

When the Form - Initialization page process runs, it checks the values of the page items in its related form region that are marked as primary keys. If all primary key page item values have a non-null value at page render time, then this page process queries its related form region's data source to retrieve a row by primary key. In this EMP example, if P4_EMPNO has a value when the page renders, then the page process implicitly performs a SQL statement like the one below to retrieve all columns related to form page items by primary key and uses the values to populate the respective page items.

SELECT ENAME, MGR, HIREDATE, SAL, COMM, DEPTNO
INTO :P4_ENAME, :P4_MGR, :P4_HIREDATE, :P4_SAL, :P4_COMM, :P4_DEPTNO
FROM EMP
WHERE EMPNO = :P4_EMPNO

In contrast, if the P4_EMPNO primary key does not have a value when the page renders, the Form - Initialization page process does nothing.

Recall that the list page's Link Column target page link passes the value of the EMPNO column of the selected row as the value of the P4_EMPNO parameter. Before the Pre-Rendering phase occurs, the APEX engine assigns the value of the P4_EMPNO page item the value it finds in the P4_EMPNO parameter in the link URL. This ensures the primary key of the Employees form region has a value before the Initialize form Employee page process executes, which triggers its lookup of the existing employee's data to populate the form region's page items. This explains why clicking on one of the edit links for an employee row results in displaying the existing data for that employee.

8.6.5 Memory Only Storage for Form Page Items#

The wizard creates the form region's page items with a Storage setting of Per Request (Memory Only).

This means the APEX engine does not save their values in user session state after page processing is complete. This is not required because this form page saves any changes submitted by the user to the EMP table before the dialog closes. A side-effect is that the form region's page item values always start out null when the page renders before any Pre-Rendering computations or page processes execute.

Figure 8-39 All Form Region Page Items Use "Memory Only" Storage

The figure below shows how the wizard configures the link target of the list page's (Create) button. Notice it passes no parameters to the target edit page.

Figure 8-40 List Page's (Create) Button Passes No Parameters to Edit Page

This explains why the form page opens with empty fields when they click on the list page's (Create) button. The button's link target passes no value for the primary key page item P4_EMPNO, and it is one of the form region's page items with Memory Only storage. When the Form - Initialization process executes in the Pre-Rendering phase, it performs no lookup and the Memory Only page items remain empty. This leaves the page ready for the end user to enter data for a new employee.

8.6.6 Saving Changes with a Page Process#

The wizard configures a page process to automatically handle inserting, updating, or deleting the row in the form region.

The Process form Employee page process shown below use the native page process type Form - Automatic Row Processing (DML). If the button that submits the page has a Database Action setting of INSERT, UPDATE, or DELETE, this process performs the appropriate DML operation to save the end user's changes. If the submitting button has no Database Action configured, the page process performs no action.

Notice the page process specifies the associated Form Region and uses default values for the other settings to:
  • Perform the DML against the form region's source,
  • Prevent lost updates by checking if another user changed the row before the current user did,
  • Lock the row being updated or deleted, and
  • Return the primary keys into corresponding page items after insert.

The Target Type settings support two other modes that let you target the automatic DML at a different table or view from the region's source, or alternatively to perform custom DML handling yourself by using PL/SQL. The figure shows the highlighted page process that handles the DML operation for the Employee form region and its declarative settings.

Figure 8-41 Declarative Form DML Handling with Native Page Process

8.6.7 Conditional Database Action Buttons#

The wizard configures several important button properties affecting runtime behavior.

On a button whose Action is set to Submit Page, the Database Action setting indicates which SQL operation the Form Automatic DML page process executes. Failure to set this property results in no DML operation. The wizard configures four buttons on the page as follows:

Table 8-2 Wizard-Created Form Page Button Settings

Scroll horizontally to view the full table
Button Behavior Execute Validations DB Action Server-side Condition
CREATE Submit Page Yes INSERT P4_EMPNO is null
SAVE Submit Page Yes UPDATE P4_EMPNO is not null
DELETE Submit Page No DELETE P4_EMPNO is not null
CANCEL Dynamic Action No    
The server-side conditions on the four buttons result in the following behavior:
  • (Cancel) always displays
  • (Create) displays when the primary key page item is null
  • (Save) and (Delete) display when the primary key page item has a value

As shown below, the wizard also configures the (Delete) button to require a confirmation message to be shown in the Danger style. It uses the special substitution string &APP_TEXT$DELETE_MSG!RAW. to include the translatable text message with key DELETE_MSG. The !RAW modifier lets the text include HTML markup without being escaped. APEX defines a default runtime message for this message key, but you can customize it by creating a shared component text message with the same key having its Used in JavaScript property enabled.

Figure 8-42 Wizard-Created Form Page Delete Button

In a modal form page the wizard configures a Cancel Dialog triggered action to fire as shown in the figure below. If you opt for a normal edit page instead of a modal page, then the wizard configures the (Cancel) button to submit the page, and a branch with its When Button Pressed condition set to the CANCEL button to return to the list page.

Figure 8-43 Wizard-Created Modal Form Page Cancel Button

8.6.8 Refreshing List On Dialog Close#

The wizard configures a native Close Dialog page process sequenced after the DML process.

Since the (Create), (Apply Changes), and (Delete) buttons should all close the dialog, it uses an appropriate server-side condition to achieve this. The APEX engine stores the name of the submitted button in the REQUEST substitution string. The process executes when REQUEST is one of the three button names in the comma-separated list containing CREATE, SAVE, or DELETE. The figure shows the Server-side Condition settings of the Close Dialog page process in Page Designer.

Figure 8-44 Wizard-configured Close Dialog Page Process

When the modal dialog closes, the end user might have inserted, updated, or deleted an employee so they expect the list page to reflect that change. As shown below, the wizard configures a dynamic action in Employees list page to handle the Dialog Close event by refreshing the interactive report region. Using the Selection Type and Region properties, it sets the handler to listen for any Dialog Closed events that were from a dialog originally opened by the Manage Employees region.

Figure 8-45 Dynamic Action Event Handler for the Close Dialog Event

When this event occurs, you refresh the interactive report region using a native Refresh dynamic action step as shown below and indicating the desired region as the affected element.

Figure 8-46 Dynamic Action Step to Refresh the Interactive Report

8.3.5.1 Wizard Example Page Lifecycle#

When a user clicks Onboard New Employee in your app's navigation menu, the APEX engine renders page 43 based on its page definition. As the user enters data and clicks buttons in the page, branches control what page to show next. Optional computations, validations, and processing let you create flows of pages for any business process.

It sends appropriate data, HTML markup, CSS style rules, and JavaScript to the user's browser based on the regions, template options, page items, and buttons the page contains.

Before rendering the page, however, it executes any computations or processes you define in the page's Pre-Rendering section of the Page Designer Rendering tab. These optional elements influence the data initially shown to the user. The net result: the user sees the first page of the Onboard New Employee page flow in their browser. The diagram below illustrates the Onboard New Employee wizard pages lifecyle.

After entering the employee's identifying information, when the user clicks on the (Next) button in page 43, as shown below, the browser submits the page to the APEX engine for processing. It includes the values of all page items. When APEX engine receives this page submission, if you defined any computations, validations, processes, or branches in the Processing tab in Page Designer, then it executes these elements in the order you sequenced them. You can optionally configure a server-side condition on any of these elements, or a When Button Pressed condition. This lets you control exactly when each element executes.

Page 43 defines a branch for the (Next) button the user clicked. This determines page 44 shows next, and the process repeats itself. Page 44 renders to the browser.

From the user's perspective, they:
  • click a navigation menu link to start the wizard,
  • enter some data on the Identification step of the wizard and click (Next), and
  • see the Job Role step of the wizard.

If there they click the (<) previous button, they return to the Identification step.

Figure 8-9 Page Renders to Browser, then User Submits Page for Processing

For more information, see Controlling Navigation Using Branches in Oracle APEX App Builder User’s Guide.

8.3.5.2 Computing Values on Render or Submit#

Define computations to set the value of a page item or application item.

If the calculated value influences what the page initially displays, then set its Execution Point to a stage in the Pre-Rendering phase. If, instead, the computation affects the processing of submitted data, then set that property to the After Submit stage in the Processing tab.

You can assign the target item:

  • a static value
  • the value of another item

  • the result of a PL/SQL expression
  • the value of a single-column SQL query, or
  • a user preference.

If the data item you set has its Multiple Values property set, then a multi-row query result gets formatted appropriately either using a delimiter or as a JSON Array.

Computations in the Pre-Rendering section of your page set up data the user will initially see on the page. Ones in the Processing section are good for computing hidden fields, or adjusting user input, before saving their values to a system of record table.

For more information, see Understanding Page Computations in Oracle APEX App Builder User’s Guide.

8.3.5.3 Validating Submitted Data#

Define validations to enforce the correctness of submitted data before saving it. You create them in the Validating execution point of the Processing tab in your page.

Validations can check the value of a single page item or test a more complex condition involving multiple values. They can consult system of records tables as needed.

For convenience, you can also create one for a particular page item by choosing Create Validation from the item's context menu in the Rendering tab. In this case, Page Designer defaults its Associated Item property as expected.

Each validation defines a condition that determines if the value or values being checked are considered valid. You can pick from a list of declarative conditions, use a PL/SQL expression or boolean returned from a PL/SQL function body, or use a SQL statement that returns a row or not. If the condition is true, the validation succeeds. If it is false, the end user sees your configured error message. If any validations fail, the user sees all of their failure messages and the APEX engine does not execute the elements in the Processing phase of the Submit lifecycle.

For more information, see Understanding Validations in Oracle APEX App Builder User’s Guide.

Tip:

To validate columns in an Interactive Grid, set the Editable Region property of the validation to the name of the related grid and then reference the grid's columns by name when defining the validity condition.

8.3.5.4 Custom Processing on Render or Submit#

Create page processes for custom behavior in the Pre-Rendering phase of the Rendering tab, or in the After Submit or Processing phases of the Processing tab. The Execute Code process type lets you include custom code to performs any business logic required, while you can use other native process types to handle other common tasks declaratively.

Built-in page process types include starting a workflow, sending an email or push notification, printing a report, loading data, invoking a PL/SQL or REST API, storing user preferences, saving form or grid changes, and more. You can find other page process types created by the APEX developer community "app store" on https://apex.world.

For example, in the last page of the Onboard a New Employee wizard, as shown below, an Execute Code page process with When Button Pressed set to the FINISH button inserts a new EMP row using the page item values from page 43, 44, and 45 to add a new employee. Notice the Success Message is configured to display a message like "New employee MENDOZA added successfully" when the process is successful. The code looks like this:

insert into emp(empno,
                ename,
                deptno,
                hiredate,
                job,
                sal,
                comm)
        values (:P43_EMPNO,
                :P43_ENAME,
                :P44_DEPTNO,
                apex_session_state.get_timestamp('P44_HIREDATE'),
                :P45_JOB,
                :P45_SAL,
                :P45_COMM);

For more information, see Understanding Page Processes in Oracle APEX App Builder User’s Guide.

Figure 8-10 Execute Code Page Process to Insert New Employee

8.3.5.5 Best Practice Use of Invoke API#

For easier maintenance and testing of your custom code, Oracle recommends writing most business logic in functions and procedures of a PL/SQL package. You can then engage these packaged program units from one-line statements or expressions in your pages, or invoke them declaratively using an Invoke API page process.

For example, you can move the INSERT statement that adds a new employee into an ADD_NEW_EMPLOYEE procedure in an HR_APP package like the following:

create or replace package body hr_app as
    procedure add_new_employee(
        p_empno    in emp.empno%type,
        p_ename    in emp.ename%type,
        p_deptno   in emp.deptno%type,
        p_hiredate in emp.hiredate%type,
        p_job      in emp.job%type,
        p_sal      in emp.sal%type,
        p_comm     in emp.comm%type)
    is
    begin
        insert into emp(empno,
                        ename,
                        deptno,
                        hiredate,
                        job,
                        sal,
                        comm)
                values (p_empno,
                        p_ename,
                        p_deptno,
                        p_hiredate,
                        p_job,
                        p_sal,
                        p_comm);
    end add_new_employee;
end hr_app;

Then the Insert New Employee page process in the Onboard New Employee wizard can change to use the Invoke API native page process, configuring the package and procedure name as shown below. After setting the Package and Procedure name, the Processing tab updates to show the procedure's parameters under the Parameters section of the page process.

Figure 8-11 Configuring Invoke API Process to Call Package Business Logic

Where possible, Page Designer automatically maps matching page item names in the current page to provide appropriate parameter values. You can inspect and configure any of the parameters using the Property Editor as shown below.

Figure 8-12 Configuring Parameter Values for Invoke API

8.3.5.6 Making Almost Any Element Conditional#

Add a Server-side Condition to almost any element in your APEX app to control whether APEX renders or runs it.

After configuring this rule on a component or page element, the APEX engine includes or executes it only when the test evaluates to true. Choose a declarative option, a PL/SQL expression, a Boolean returned by a PL/SQL function body, or a SQL statement that returns a row. You can apply the check to many elements, including regions, page items, columns, buttons, validations, computations, processes, and branches.

The When Button Pressed property of validations, processes, and branches limits its execution to one button on the page. To run the element for multiple buttons, use the declarative condition Request is contained in value and provide a value that includes a delimited list of button names (e.g. SAVE,UPDATE,INCREMENT).

This technique works because when a button named SOME_BUTTON triggers a page submit, the APEX engine sets the value of the REQUEST substitution string to the name of the button. Since you can reference substitution strings as bind variables in SQL or PL/SQL, you can also express this multiple buttons condition using a PL/SQL expression like:

:REQUEST in ('SAVE','UPDATE','INCREMENT')

Tip:

Use the :REQUEST expression with an IN clause when a longer name of a page button (e.g. SAVE_ADDRESS) includes the shorter name of another button (e.g. SAVE). This avoids unintentionally running logic when the SAVE button is pressed, that happens to be part of the other buttons' names.

:REQUEST in ('SAVE_ADDRESS','SAVE_ASSIGNMENT') /* SAVE alone doesn't match */

For more information, see Available Conditions in Oracle APEX App Builder User’s Guide.

8.5.5.1 Clearing State with Link Target Settings#

Anywhere in APEX, when you define a target link to a page you can optionally configure a list of one or more page numbers whose session state values you want to clear.

For example, the figure below shows the Target tab of Onboard New Employee navigation menu list entry. Notice its Clear Cache setting is set to the comma-separated list of page numbers 43,44,45. In addition to page numbers, you can also list collection names to clear, as well as the special value RP to clear report pagination on the listed pages.

Figure 8-24 Clearing Session State in Link Target Settings

As shown below, the same settings are available in the Link Builder dialog when configuring the target of a button or link.

Figure 8-25 Clear Cache Setting in Button Target Dialog

8.5.5.2 Clearing State with a Page Process#

To clear specific session state at the end of your page's submit processing, use the native Clear Session State page process.

For example, the figure below shows how the last page of the Onboard New Employee wizard page flow can conditionally clear the page item session state on pages 43, 44, and 45 when the FINISH button is pressed, after it has successfully inserted the new employee. Notice the page process type is set to Clear Session State, the page numbers are specified as a comma-separated value, and the Server-side Condition's When Button Pressed property is set to FINISH.

Figure 8-26 Using Clear Session State Page Process

8.5.7.1 Key Collections Concepts#

A collection requires no formal definition. Instead, you use its predefined generic columns, such as N001, N002, C001, C002, and D001, according to the number and type of values you need to store.

Collections support a fixed number of typed columns. For a given collection, just use the columns you need in each row.

Table 8-1 Collection Columns

Scroll horizontally to view the full table
Data Type Column Name(s) Quantity
VARCHAR2 c001 to c050 50
NUMBER n001 to n005 5
DATE d001 to d005 5
CLOB clob001 1
BLOB blob001 1
XMLTYPE xmltype001 1

You add rows – also known as members – to a collection using the ADD_MEMBER procedure or function. Each member is assigned a sequence id (SEQ_ID) that is one larger than the current maximum sequence id in the collection. Using that sequence id, you can delete a member or update a member using DELETE_MEMBER or UPDATE_MEMBER procedures, respectively.

To access the data in a collection, you query the APEX_COLLECTIONS view using a WHERE clause of:
WHERE collection_name = 'COLLECTION_NAME'
For example, imagine creating a collection named CART to represent a shopping cart. You need to store:
  • Item code (VARCHAR2)
  • Quantity (NUMBER)
  • Need by date (DATE)
To create the collection, use the statement:
apex_collection.create_collection(
   p_collection_name => 'SHOPPING_CART');
You can use any column of appropriate data type, but by convention you use them in numerical order. So, you'll use column c001 to store the Item code, n001 to store the quantity, and d001 to store the date. Therefore, to add two items to the cart, use code like this:
apex_collection.add_member(
   p_c001 => 'COMP-APPL-MBP-16',
   p_n001 => 2,
   p_d001 => date'2025-05-27');
apex_collection.add_member(
   p_c001 => 'ACC-APPL-MAGICMOUSE',
   p_n001 => 1,
   p_d001 => date'2025-05-27');
To retrieve the members in the collection, write a query like the following. It is helpful to alias the columns to more meaningful names as follows:
select c001 as item_code,
       n001 as quantity,
       d001 as need_by_date
  from apex_collections
 where collection_name = 'SHOPPING_CART'

APEX automatically isolates the session state of one user session from another, so you can write the query knowing that you will only ever see the data the current user session has added to the collection named SHOPPING_CART. You can use a query like this in any APEX region to present collection data in any way you might need to.

You can also use a query over APEX_COLLECTIONS in a PL/SQL cursor for loop to iterate over the contents of the collection like this:
for i in (select c001 as item_code,
                 n001 as quantity,
                 d001 as need_by_date
            from apex_collections
           where collection_name = 'SHOPPING_CART')
loop
   -- Reference i.item_code, i.quantity,
   -- and i.need_by_date in the loop
end loop;

To proactively clear a collection before the session ends, use the TRUNCATE_COLLECTION procedure. Consult the APEX PL/SQL reference documentation for the APEX_COLLECTION package for other useful functions and procedures for working with collections.

Tip:

If you combine an editable grid with a collection, you will need to make two modifications to the typical no-code setup. First, since the grid depends on a primary key value that does not change you will need to populate one of the collection columns with a unique key. Consider generating a SYS_GUID value as the primary key for each row as the package body in the following section does.

8.5.7.2 Tips for Maintainable Collection Use#

For additional ease of maintenance, create a view for the collection query so your pages can work with the meaningful column names anywhere they need to.

For example, you can create the following view. Notice that in addition to item_code, quantity, and need_by_date, it is also storing a unique id value that collection use with an interactive grid requires:
create view shopping_cart_v as
select c001 as item_code,
       c002 as id,
       n001 as quantity,
       d001 as need_by_date
  from apex_collections
 where collection_name = 'SHOPPING_CART'
You can use this view in a PL/SQL cursor for loop to iterate over the contents of the collection like this:
for i in (select item_code,
                 id,
                 quantity,
                 need_by_date
            from shopping_cart_v)
loop
   -- Reference i.item_code, i.id,
   -- i.quantity, and i.need_by_date in the loop
end loop;
For ultimate maintainability, consider writing a PL/SQL package API that isolates the interaction with the APEX_COLLECTION package behind a set of functions and procedures that describe the business functionality it provides. For example, consider the package:
create or replace package shopping_cart_api is
    ----------------------------------------------------------
    procedure clear_cart;
    ----------------------------------------------------------
    function add_item(
        p_item_code    in varchar2,
        p_quantity     in number,
        p_need_by_date in date)
    return varchar2;
    ----------------------------------------------------------
    procedure remove_item (
        p_item_id in varchar2);
    ----------------------------------------------------------
    procedure update_item (
        p_item_id      in varchar2,
        p_item_code    in varchar2,
        p_quantity     in number,
        p_need_by_date in date);
end shopping_cart_api;

Using a package specification like this, your application code is easier to understand for yourself and any teammates who can immediately infer that it's working with a shopping cart.

The corresponding package body for the SHOPPING_CART_API package appears below. Notice how it's generating a unique SYS_GUID value to provide a primary key that does not change for each row in the collection:
create or replace package body shopping_cart_api is
    c_collection_name constant varchar2(40) := 'SHOPPING_CART';
    ----------------------------------------------------------
    procedure ensure_collection
    is
    begin
        if not apex_collection.collection_exists(c_collection_name) then
            apex_collection.create_collection(c_collection_name);
        end if;
    end ensure_collection;
    ----------------------------------------------------------
    procedure clear_cart
    is
    begin
        ensure_collection;
        apex_collection.truncate_collection(c_collection_name);
    end clear_cart;
    ----------------------------------------------------------
    function get_seq_id(
        p_item_id in varchar2)
    return number
    is
        l_ret number;
    begin
        for j in (select seq_id as seq
                    from apex_collections
                   where collection_name = c_collection_name
                     and c002 = p_item_id)
        loop
            l_ret := j.seq;
        end loop;
        return l_ret;
    end;
    ----------------------------------------------------------
    function add_item (
        p_item_code    in varchar2,
        p_quantity     in number,
        p_need_by_date in date)
    return varchar2
    is
        l_ret varchar2(255) := sys_guid();
    begin
        ensure_collection;
        apex_collection.add_member (
            p_collection_name    => c_collection_name,
            p_c001               => p_item_code,
            p_c002               => l_ret,
            p_n001               => p_quantity,
            p_d001               => p_need_by_date
        );
        return l_ret;
    end add_item;
    ----------------------------------------------------------
    procedure remove_item (
        p_item_id in varchar2)
    is
        l_seq_id number;
    begin
        ensure_collection;
        l_seq_id := get_seq_id(p_item_id);
        if l_seq_id is not null then
            apex_collection.delete_member (
                p_collection_name => c_collection_name,
                p_seq             => l_seq_id);
        end if;
    end remove_item;
    ----------------------------------------------------------
    procedure update_item (
        p_item_id      in varchar2,
        p_item_code    in varchar2,
        p_quantity     in number,
        p_need_by_date in date)
    is
        l_seq_id number;
    begin
        ensure_collection;
        l_seq_id := get_seq_id(p_item_id);
        if l_seq_id is not null then
            apex_collection.update_member(
                p_collection_name => c_collection_name,
                p_seq             => l_seq_id,
                p_c001            => p_item_code,
                p_c002            => p_item_id,
                p_n001            => p_quantity,
                p_d001            => p_need_by_date);
        end if;
    end update_item;
end shopping_cart_api;