Skip to main content

Goodbye Hardcoded Buttons: Storing UI Actions in Database Rows ๐ŸŽ›๏ธ๐Ÿ“‹

Updated 6 min readMarkdown
Part of seriesEnterprise APEX Workflows at Scale
Goodbye Hardcoded Buttons: Storing UI Actions in Database Rows ๐ŸŽ›๏ธ๐Ÿ“‹

๐Ÿ‡ช๐Ÿ‡ฌ ุงู„ู†ุณุฎุฉ ุงู„ุนุฑุจูŠุฉ: ุงู‚ุฑุฃ ู‡ุฐุง ุงู„ู…ู‚ุงู„ ุจุงู„ุนุงู…ูŠุฉ ุงู„ู…ุตุฑูŠุฉ ู…ู† ู‡ู†ุง.

Part 4 of the "Enterprise APEX Workflows at Scale" Series.


โšก The Quick TL;DR

If you have ever opened Oracle APEX Page Designer and encountered 40 separate buttons crammed into a single header regionโ€”each with a hairball of Server-Side Conditions like "show only if step is FINANCE_AUDIT, priority is HIGH, and user is in ROLE_CONTROLLER"โ€”you have witnessed peak technical debt.

By externalizing button definitions into a relational configuration table (wf_action_config), you turn your UI into a programmable controller that dynamically renders the exact right buttons, icons, colors, and confirmation prompts for hundreds of workflow steps without touching Page Designer ever again.

Here is how to replace button clutter with declarative metadata in Oracle APEX.


๐ŸŽฎ The Hardwired Arcade Console vs. The Programmable Controller

Imagine the world of video game controllers:

  • The Hardwired Console Nightmare: Every time a new game comes out, you take out a soldering iron, melt a new hole in the plastic casing of your controller, solder a physical red button, wire it to an internal microprocessor pin, and write "MAGIC SPELL" on it with a Sharpie. After six months, your controller looks like a deformed hedgehog with 50 plastic buttons protruding at odd angles.
  • The Modern Programmable Controller: The controller has a sleek, standard row of dynamic buttons equipped with micro-OLED screens. When you load a racing game, Button 1 lights up as "Nitro (Green)". When you load a puzzle game, the exact same button transforms into "Rotate (Blue)". The hardware never changes; the game simply feeds it a configuration packet.

In enterprise Oracle APEX applications, hardcoding dozens of buttons inside Page Designer is the equivalent of soldering buttons onto plastic. It produces brittle, bloated, and unmaintainable applications.


๐Ÿ’ฅ The Pain of the 40-Button Page

When you build complex multi-step enterprise workflows in Oracle APEX, every stage needs distinct user actions:

  • A Manager Review step needs: Approve, Reject, Request Info.
  • A Finance Audit step needs: Verify Budget, Authorize Payment, Return to Requester.
  • A Legal Review step needs: Upload Addendum, Flag Clause, Approve Terms.

If you build this conventionally inside APEX:

  1. Page Designer Becomes Unusable: The button panel in the Component Tree is an unreadable wall of 45 buttons.
  2. Server-Side Conditions Become Spaghetti: You end up with 45 custom PL/SQL expressions checking session state, task types, user roles, and amounts.
  3. Merge Conflict Disasters in Git: If two developers adjust different buttons on the same page, the exported SQL file has hundreds of lines of conflicting component ID diffs.
  4. Zero Agility: Changing a button label from "Reject" to "Return for Revisions" requires an entire code lifecycle: developer sprint, git branch, export, test, and production deployment.

๐Ÿ“‹ The Metadata Solution: wf_action_config

In a scalable enterprise architecture, UI actions are treated as relational data rows. We define an action configuration table in our data schema (app_data):

-- ====================================================================
-- TABLE: app_data.wf_action_config
-- PURPOSE: Declarative metadata defining action buttons for workflow steps
-- ====================================================================

create table app_data.wf_action_config (
    step_id          varchar2(50) primary key, -- e.g. 'PO_MANAGER_REVIEW'
    step_name        varchar2(150) not null,
    module_code      varchar2(50)  not null,   -- e.g. 'PROCUREMENT', 'CAPEX'
    
    -- Action Slot 1
    has_btn1         varchar2(1)   default 'N' check (has_btn1 in ('Y', 'N')),
    btn1_config      varchar2(200), -- Format: Label,CSS_Class,Icon_Class,Requires_Confirm
    btn1_action_code varchar2(50),  -- e.g. 'APPROVE'
    
    -- Action Slot 2
    has_btn2         varchar2(1)   default 'N' check (has_btn2 in ('Y', 'N')),
    btn2_config      varchar2(200),
    btn2_action_code varchar2(50),  -- e.g. 'REJECT'
    
    -- Action Slot 3
    has_btn3         varchar2(1)   default 'N' check (has_btn3 in ('Y', 'N')),
    btn3_config      varchar2(200),
    btn3_action_code varchar2(50),  -- e.g. 'REQUEST_INFO'
    
    -- Action Slot 4
    has_btn4         varchar2(1)   default 'N' check (has_btn4 in ('Y', 'N')),
    btn4_config      varchar2(200),
    btn4_action_code varchar2(50),
    
    created_at       timestamp with local time zone default current_timestamp,
    updated_at       timestamp with local time zone default current_timestamp
);

๐ŸŽจ Expressive Configuration in Simple Data Rows

Look at how clear, readable, and expressive our button configurations become when stored as data:

-- 1. Purchase Order - Department Manager Review
insert into app_data.wf_action_config (
    step_id, step_name, module_code,
    has_btn1, btn1_config, btn1_action_code,
    has_btn2, btn2_config, btn2_action_code,
    has_btn3, btn3_config, btn3_action_code
) values (
    'PO_MANAGER_REVIEW', 
    'Department Manager PO Review', 
    'PROCUREMENT',
    'Y', 'Approve Order,u-success,fa-check,1',            'APPROVE',
    'Y', 'Reject Order,u-danger,fa-times,1',             'REJECT',
    'Y', 'Request Clarification,u-warning,fa-comment,0', 'REQUEST_INFO'
);

-- 2. Capital Expenditure - Senior Financial Audit
insert into app_data.wf_action_config (
    step_id, step_name, module_code,
    has_btn1, btn1_config, btn1_action_code,
    has_btn2, btn2_config, btn2_action_code
) values (
    'CAPEX_FINANCE_AUDIT', 
    'Capital Expenditure Executive Audit', 
    'CAPEX',
    'Y', 'Authorize Expenditure,u-success,fa-check-circle,1', 'APPROVE',
    'Y', 'Return for Budget Revision,u-danger,fa-undo,1',     'REJECT'
);

-- 3. Accounts Payable - Vendor Invoice Matching
insert into app_data.wf_action_config (
    step_id, step_name, module_code,
    has_btn1, btn1_config, btn1_action_code,
    has_btn2, btn2_config, btn2_action_code,
    has_btn3, btn3_config, btn3_action_code
) values (
    'AP_INVOICE_MATCH', 
    'Accounts Payable 3-Way Match', 
    'INVOICING',
    'Y', 'Confirm Match & Release,u-primary,fa-link,1',   'MATCH_RELEASE',
    'Y', 'Flag Discrepancy,u-danger,fa-flag,1',           'FLAG_ERROR',
    'Y', 'Upload Supporting Dockets,u-info,fa-upload,0',  'UPLOAD_ATTACHMENT'
);

Breaking Down the Configuration String:

Notice the 4-part comma-delimited string in btn_config:

'Approve Order,u-success,fa-check,1'
       โ”‚          โ”‚          โ”‚   โ””โ”€โ”€ Confirmation Dialog: 1=Prompt user, 0=Instant click
       โ”‚          โ”‚          โ””โ”€โ”€โ”€โ”€โ”€โ”€ Font Awesome Icon: fa-check
       โ”‚          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Universal Theme CSS Class: u-success (Green)
       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Display Label: "Approve Order"

๐ŸŒŸ Why This Beats Page Designer Buttons Every Single Time

Feature Hardcoded Page Designer Buttons Database-Driven wf_action_config
Buttons on Page 30 โ€“ 50 distinct components Zero static buttons (1 dynamic container)
New Workflow Step Add buttons, write conditions, export, deploy Insert 1 row into database table
Change Button Label Code edit + Git PR + Deployment Simple UPDATE statement in 2 seconds
Confirmation Popups Custom JavaScript dynamic actions per button Built-in flag (1 or 0) handled automatically
Visual Consistency Easy for developers to pick inconsistent colors Enforced theme standards (u-success, u-danger)
Git Conflicts Constant collisions on page exports Zero conflicts on the UI page

๐ŸฆŽ Coming Up Next: Part 5

We have our action metadata safely stored in relational tables. But how does Oracle APEX turn that metadata into interactive, beautifully styled HTML buttons on the user's screen at runtime?

And more importantly: how do we ensure that only users with the proper authorization can actually see and click those buttons?

In Part 5 ("The Chameleon Cockpit: Dynamically Rendering Action Bars"), we will build a dynamic PL/SQL rendering region on our Task Detail Page that morphs the toolbar on the fly and enforces ironclad server-side authorization!

See you in Part 5! ๐ŸฆŽ๐ŸŽš๏ธ

64 views

No comments yet. Be the first to comment.

More from this blog