Stop Duplicate Records from Double-Clicking Save in Oracle APEX

One Before Page Submit dynamic action disables the buttons on every page while it submits, plus an optional database safety net for critical tables.

Some users double-click every button. In an Oracle APEX form, a double-click on Create can send the page to the server twice, and both requests insert the record. The result is two identical rows, and nobody knows which one is the right one.

This happens in a form created by the Create App wizard in Oracle APEX 26.1. In the demo Customers application, a user entered Northwind Traders and double-clicked Create:

Customers report with Northwind Traders listed twice with the same phone, credit limit, and status
One double-click, two customers

The fix is one dynamic action on the Global Page: it disables the buttons as soon as the page is submitted, on every page of the application at once. At the end, an optional database safety net is shown for the few tables where a duplicate would be really costly.

The Fix: Disable the Buttons While the Page Submits

The Global Page (page 0) shows its components on every page of the application, and that includes dynamic actions. If your application has no Global Page, create one with Create Page, Global Page.

Open page 0 in Page Designer and click the Dynamic Actions tab. Right-click Events, choose Create Dynamic Action, and set:

  • Identification, Name: Disable buttons on submit
  • When, Event: Before Page Submit
Global Page dynamic action Disable buttons on submit with the event Before Page Submit
A dynamic action on the Global Page runs on every page

Select the True action, set Action to Execute JavaScript Code, and enter this code:

// The page is being submitted: disable all buttons, so a second click does nothing.
// Only when the form passes its checks, otherwise the user must still be able to fix it.
if (apex.page.validate()) {
  $('button').prop('disabled', true);
}
True action Execute JavaScript Code with the code that disables all buttons when apex.page.validate() is true
The action disables all buttons of the page

Click Save. Before Page Submit runs at the moment the user clicks a button that submits the page, before APEX checks the form in the browser, for example for empty required fields. If that check fails, the page is not submitted, and the user must still be able to correct the form and click again. That is why the code first calls apex.page.validate(), which runs the same check, and disables the buttons only when the form is valid.

Now, when the user clicks Create, the buttons are disabled right away until the server answers, so a second click does nothing:

Customer dialog for Contoso Ltd while it is being saved, with the Cancel and Create buttons disabled
While the page is submitting, Create and Cancel are disabled

In the lab, a double-click on Create now saves exactly one customer, every time. A form with an empty required field still shows its error, and the buttons stay usable.

Optional: An Extra Safety Net for Critical Tables

The dynamic action above is enough for most applications, and you do not need anything else for your normal tables. It works in the browser, though, so it depends on JavaScript running on the page. For a few tables where a duplicate would be really costly, such as orders or payments, you can add an extra level of safety in the database: give each new form a one-time token, and let the database accept every token only once. Skip this section if you do not have such a table.

Add a column for the token with a unique constraint:

alter table customers add create_token varchar2(32);
alter table customers add constraint customers_create_token_uk unique (create_token);

On the form page (page 3 in the demo), create a page item P3_CREATE_TOKEN in the Customer form region and set its Type to Hidden. Keep Value Protected on, so the token cannot be changed in the browser:

Page item P3_CREATE_TOKEN with Type Hidden and Value Protected on
A hidden item for the token

Then set:

  • Source, Form Region: Customer, and Column: CREATE_TOKEN, so the form saves the token with the record
  • Default, Type: Expression, Language: PL/SQL, and PL/SQL Expression: sys_guid()
P3_CREATE_TOKEN with Source Column CREATE_TOKEN and Default PL/SQL Expression sys_guid()
The token is saved in CREATE_TOKEN and gets a new value each time the form opens

Click Save. Every time the form opens for a new customer, sys_guid() gives the token a new unique value. If the same form is submitted twice, both requests carry the same token. The first insert saves it, and the second insert breaks the unique constraint CUSTOMERS_CREATE_TOKEN_UK, so the database rejects it and APEX rolls it back.

To test the safety net alone, I switched off the dynamic action and double-clicked Create for Fabrikam Inc. The table has one Fabrikam Inc, and the Error Log page from the article on handling all errors in one place shows the second insert that the database stopped:

Error Log row with ORA-00001 unique constraint CUSTOMERS_CREATE_TOKEN_UK violated for the Process form Customer
The second request of the double-click was stopped by the unique constraint

Good to Know

  • A double-click on Apply Changes for an existing record only saves the same values twice, and with the dynamic action in place, it is sent only once anyway.
  • If a table already has a real unique key, such as a unique email, that key also stops duplicates in the database. The optional token is only for critical tables where two records may legally look the same, like two payments of the same amount.
  • If you use the token, existing records keep an empty token, which a unique constraint allows. If a user ever sees the constraint error, give it a clear text in your error handling function, for example a WHEN line for CUSTOMERS_CREATE_TOKEN_UK with the message "This customer has already been saved."
  • Before Page Submit runs only for buttons that submit the page. A button that runs server-side code through a dynamic action does not submit the page. For such a button, add a Disable action for the button as the first action of its dynamic action, and an Enable action as the last one.

Summary

A double-click on Create can insert the same record twice in Oracle APEX. One dynamic action on the Global Page, with the event Before Page Submit and a few lines of JavaScript, disables the buttons on every page while the page is submitting, but only after the form passes its checks. That is all most applications need. For a few critical tables, a hidden token item with sys_guid() and a unique constraint on its column add an optional second level of safety in the database.

Vinish Kapoor
Vinish Kapoor

An Oracle ACE, author of four books on Oracle APEX, SQL and PL/SQL, and Oracle Forms, and a software developer building Oracle database applications since 2001.

guest

0 Comments
Oldest
Newest Most Voted
00