Form Regions – Superclass/Subclass
The new Form Region feature introduced in Oracle APEX 19.1 is a big improvement to the way that single-record forms are defined declaratively.
In prior versions of APEX, you were effectively limited to a single DML load process and a single DML submit process, and all the items on the page that were linked to a table column were required to be from only a single table. If you wanted to do anything more complicated than that, you had to either split your form into multiple pages or get rid of the DML processes and hand-code the PL/SQL yourself.
The new components/attributes for Form Regions are:
- Region type “Form” which defines the Source Table (or view, or SQL Query, or PL/SQL returning a SQL query, or Web Source) and associated semantics.
- Item Source attribute that associates each item with a particular Form Region. The item can optionally be marked as “Query Only” and/or as a “Primary Key”.
- Pre-render process type “Form – Initialization” that is associated with a Form Region.
- Page process type “Form – Automatic Row Processing (DML)” that is associated with a Form Region. This can be used to change the Target for the form region (default is the Source Table) to a different table or view, or to replace the DML processing with your custom PL/SQL. You can also modify other semantics such as Prevent Lost Updates and row locking.
If you generate a single-record form page APEX will generate all the above for you and you can then customise them as you need.
Now, I want to create a slightly more complex form – one based on two tables that are related – “Transaction Lines” and “Reserve Lines” have the same Primary Key, and a referential integrity constraint linking them. They act like a “super class / subclass”, where one table (“Reserve Lines”) is optional. This data model eliminates the need to have lots of nullable columns in a base table.
Here’s the form for editing a single “Transaction Line”:
The form shows the associated “Reserve Line” if it exists:
If the “Reserve” checkbox is ticked, the Reserve details region is shown and the user can enter the Reserve Line attributes.
I need the page to perform the following actions:
- For a new record, if the user leaves “Reserve” unticked, only a Transaction Line should be inserted.
- For a new record, if the user ticks “Reserve”, both a Transaction Line and a Reserve Line should be inserted.
- For an existing record, if the user ticks or unticks “Reserve”, the Reserve Line should be inserted or deleted accordingly.
If you want to see this example in action, feel free to try it out here:
https://apex.oracle.com/pls/apex/f?p=JK201904&c=JK64 (login using your email address if you want). The starting page is a blank transaction; click “Create”, then click “Add Line” to open the transaction line page.
To do this, I built the form in the following manner:
- Created a Form Region for the Transaction Line with standard Initialize form and Automatic Row Processing (DML) processes.
- Created a second Form Region for the Reserve Line with the standard processes (but customised later).
- Added the “P2_RESERVE_Y” checkbox item, not linked to any source column. Added a dynamic action to show/hide the Reserve details region if it is ticked/unticked.
- Added a “set up reserve line” PL/SQL Code process immediately after the Initialize form Transaction Line and just before the Initialize form Reserve Line process:
- Server-side Condition Type = Rows returned
- SQL Query =
select null from reserve_lines where line_id=:P2_LINE_ID
- PL/SQL Code:
:P2_RESERVE_LINE_ID := :P2_LINE_ID; :P2_RESERVE_Y := 'Y';
5. Change the Automatic Row Processing (DML) for the Reserve Line region:
- Target Type = PL/SQL Code
- PL/SQL Code to Insert/Update/Delete:
case when :P2_RESERVE_Y is null then delete reserve_lines where line_id=:P2_RESERVE_LINE_ID; when :P2_RESERVE_Y is not null and :P2_RESERVE_LINE_ID is null then insert into reserve_lines (line_id,asset,target_amt,target_year) values (:P2_LINE_ID,:P2_ASSET,:P2_TARGET_AMT,:P2_TARGET_YEAR ) returning line_id into :P2_RESERVE_LINE_ID; when :P2_RESERVE_Y is not null and :P2_RESERVE_LINE_ID is not null then update reserve_lines set asset=:P2_ASSET ,target_amt=:P2_TARGET_AMT ,target_year=:P2_TARGET_YEAR where line_id=:P2_RESERVE_LINE_ID; else null; end case;
6. Add a special handler to delete the reserve line if the user clicks the Delete button (this needs to be executed prior to the Process form Transaction Line to avoid a FK violation).
This solution is not quite as “low-code” as I’d like; it would be simpler to call a TAPI here instead of hardcoding the DML statements. The reason we need custom PL/SQL here is that when the user clicks the “Create” or “Save” buttons (which are associated with the SQL Insert and Update actions, respectively), we often need to translate this into a different DML action (insert, update, or delete) for the Reserve Lines table.