How to Build the Navigation Menu from a Table in Oracle APEX

Create a MENU_ENTRIES table, turn it into a role-filtered dynamic list, use it as the navigation menu, and add an admin page to edit the entries.

The navigation menu of an Oracle APEX application is normally a static list. Every new page, label change or role rule means a change in App Builder and a new deployment. When administrators want to rename, reorder or hide menu entries themselves, a static list gets in the way.

A dynamic list solves this. Its entries come from a SQL query, so the menu can live in a table. This guide stores the menu in a MENU_ENTRIES table with groups, icons, target pages, required roles and an active flag. It then turns the table into the application's navigation menu and adds an admin page to edit it.

The older post Oracle Apex Dynamic Popup Navigation Menu Example builds a popup menu from a tree table without roles. The post Oracle APEX Navigation: Lists, Menus, and Breadcrumbs explains lists in general. This guide is different: the menu shows each user only the entries their Access Control roles allow.

Step 1: Create the Table

One table holds the whole menu. A row with no target page is a group heading, and PARENT_ID puts an entry under a group. REQUIRED_ROLES holds the static IDs of the roles that may see the entry, separated by colons. An empty value means every user sees it.

Create the table, the index and the sample menu:

Example:

create table menu_entries (
  entry_id        number generated by default on null as identity,
  parent_id       number,
  label           varchar2(60) not null,
  icon_css        varchar2(60),
  target_page     number(6),
  sort_seq        number(4) default on null 10 not null,
  required_roles  varchar2(200),
  is_active       varchar2(1) default on null 'Y' not null,
  constraint menu_entries_pk primary key (entry_id),
  constraint menu_entries_parent_fk foreign key (parent_id)
    references menu_entries (entry_id),
  constraint menu_entries_label_uk unique (parent_id, label),
  constraint menu_entries_active_ck check (is_active in ('Y', 'N')),
  constraint menu_entries_parent_ck check (parent_id <> entry_id)
);

create index menu_entries_parent_ix on menu_entries (parent_id);

insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (1, null, 'Home', 'fa-home', 1, 10, null);
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (2, null, 'Orders', 'fa-shopping-cart', null, 20, null);
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (3, 2, 'New Order', 'fa-plus', 30, 10, 'administrator:editor');
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (4, 2, 'Order Board', 'fa-columns', 270, 20, null);
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (5, 2, 'Approve Orders', 'fa-check-square-o', 70, 30, 'administrator');
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (6, null, 'Products', 'fa-cubes', null, 30, null);
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (7, 6, 'Product List', 'fa-table', 110, 10, null);
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (8, 6, 'Product Cards', 'fa-th-large', 320, 20, null);
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (9, null, 'Billing', 'fa-money', null, 40, null);
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (10, 9, 'Invoices', 'fa-file-text-o', 340, 10, 'administrator:editor');
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (11, 9, 'Quote Lines', 'fa-calculator', 260, 20, 'administrator:editor');
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (12, null, 'Sales Dashboard', 'fa-bar-chart', 80, 50, null);
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (13, null, 'Administration', 'fa-gear', null, 90, 'administrator');
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (14, 13, 'Order Cleanup Settings', 'fa-sliders', 170, 10, null);
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (15, 13, 'Login History', 'fa-history', 90, 20, null);
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles)
values (16, 13, 'Menu Entries', 'fa-list', 380, 30, null);
insert into menu_entries (entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles, is_active)
values (17, null, 'Schedule', 'fa-calendar', 120, 60, null, 'N');

commit;

alter table menu_entries modify entry_id
  generated by default on null as identity (start with limit value);

select lpad(' ', 2 * (level - 1)) || label as menu_tree,
       target_page, required_roles, is_active
  from menu_entries
 start with parent_id is null
connect by prior entry_id = parent_id
 order siblings by sort_seq;

Output:

Table MENU_ENTRIES created.

Index MENU_ENTRIES_PARENT_IX created.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

Commit complete.

Table MENU_ENTRIES altered.

MENU_TREE                      TARGET_PAGE REQUIRED_ROLES          IS_ACTIVE
___________________________ ______________ _______________________ ____________
Home                                     1                         Y
Orders                                                             Y
  New Order                             30 administrator:editor    Y
  Order Board                          270                         Y
  Approve Orders                        70 administrator           Y
Products                                                           Y
  Product List                         110                         Y
  Product Cards                        320                         Y
Billing                                                            Y
  Invoices                             340 administrator:editor    Y
  Quote Lines                          260 administrator:editor    Y
Sales Dashboard                         80                         Y
Schedule                               120                         N
Administration                             administrator           Y
  Order Cleanup Settings               170                         Y
  Login History                         90                         Y
  Menu Entries                         380                         Y

17 rows selected.
  • MENU_ENTRIES_PARENT_FK is a self-referencing foreign key, so an entry can only point to an existing group. MENU_ENTRIES_PARENT_CK stops an entry from being its own parent.
  • MENU_ENTRIES_LABEL_UK prevents two entries with the same label in one group.
  • SORT_SEQ and IS_ACTIVE use DEFAULT ON NULL, and MENU_ENTRIES_ACTIVE_CK allows only Y and N. No trigger is needed.
  • The sample rows use fixed IDs so the parents are easy to read. The last ALTER TABLE moves the identity past them.

The role static IDs come from Application Access Control. This application has the roles administrator, editor and reader, created as shown in the guide on Access Control roles. DEMO_DEV is an Administrator, ORDER_EDITOR an Editor and SALES_USER a Reader.

Step 2: Write the Menu Query

A dynamic list query returns one row per entry with the columns in this order: level, label, target, is current, and image (the icon). The query below builds the tree with CONNECT BY and filters the rows by role:

Example:

select level as lvl,
       label,
       case when target_page is not null
            then apex_page.get_url(p_page => target_page)
       end as target,
       case when target_page = :APP_PAGE_ID then 'YES' else 'NO' end as is_current,
       icon_css as image
  from (select *
          from menu_entries e
         where e.is_active = 'Y'
           and (e.required_roles is null
                or exists (select null
                             from apex_appl_acl_user_roles ur
                            where ur.application_id = :APP_ID
                              and ur.user_name = upper(:APP_USER)
                              and ur.role_static_id in (
                                    select column_value
                                      from apex_string.split(e.required_roles, ':')))))
 where not (target_page is null and connect_by_isleaf = 1)
 start with parent_id is null
connect by prior entry_id = parent_id
 order siblings by sort_seq
  • The inner query keeps only active entries the user may see. An entry passes when REQUIRED_ROLES is empty or when APEX_APPL_ACL_USER_ROLES has one of its roles for :APP_USER.
  • The filter runs before CONNECT BY. When a group is filtered out, its entries are never reached, so Administration hides all its pages at once.
  • The outer WHERE removes a group heading that is left with no visible entries (no target page and CONNECT_BY_ISLEAF = 1).
  • APEX_PAGE.GET_URL builds the link with the session. IS_CURRENT is YES for the entry of the page being shown.

Before using the query in APEX, test it for each user in an APEX session. This script creates a session for SALES_USER, sets the three bind variables and runs the query:

Example:

set pagesize 100
column menu_tree format a28
variable APP_ID number
variable APP_USER varchar2(100)
variable APP_PAGE_ID number
begin
  apex_session.create_session(p_app_id => 400, p_page_id => 1, p_username => 'SALES_USER');
  :APP_ID := 400;
  :APP_USER := v('APP_USER');
  :APP_PAGE_ID := 1;
end;
/
select lpad(' ', 2 * (lvl - 1)) || label as menu_tree,
       nvl2(target, 'Yes', 'No') as has_link, is_current, image
  from (
select level as lvl,
       label,
       case when target_page is not null
            then apex_page.get_url(p_page => target_page)
       end as target,
       case when target_page = :APP_PAGE_ID then 'YES' else 'NO' end as is_current,
       icon_css as image
  from (select *
          from menu_entries e
         where e.is_active = 'Y'
           and (e.required_roles is null
                or exists (select null
                             from apex_appl_acl_user_roles ur
                            where ur.application_id = :APP_ID
                              and ur.user_name = upper(:APP_USER)
                              and ur.role_static_id in (
                                    select column_value
                                      from apex_string.split(e.required_roles, ':')))))
 where not (target_page is null and connect_by_isleaf = 1)
 start with parent_id is null
connect by prior entry_id = parent_id
 order siblings by sort_seq
       );

exec apex_session.delete_session

Output:

PL/SQL procedure successfully completed.

MENU_TREE          HAS_LINK    IS_CURRENT    IMAGE
__________________ ___________ _____________ ___________________
Home               Yes         YES           fa-home
Orders             No          NO            fa-shopping-cart
  Order Board      Yes         NO            fa-columns
Products           No          NO            fa-cubes
  Product List     Yes         NO            fa-table
  Product Cards    Yes         NO            fa-th-large
Sales Dashboard    Yes         NO            fa-bar-chart

7 rows selected.

PL/SQL procedure successfully completed.

The reader sees no New Order, Approve Orders or Administration. The Billing group is gone too, because both of its entries need the Administrator or Editor role. Schedule is missing because IS_ACTIVE is N.

The same script for ORDER_EDITOR returns the Editor entries and the Billing group:

Output:

PL/SQL procedure successfully completed.

MENU_TREE          HAS_LINK    IS_CURRENT    IMAGE
__________________ ___________ _____________ ___________________
Home               Yes         YES           fa-home
Orders             No          NO            fa-shopping-cart
  New Order        Yes         NO            fa-plus
  Order Board      Yes         NO            fa-columns
Products           No          NO            fa-cubes
  Product List     Yes         NO            fa-table
  Product Cards    Yes         NO            fa-th-large
Billing            No          NO            fa-money
  Invoices         Yes         NO            fa-file-text-o
  Quote Lines      Yes         NO            fa-calculator
Sales Dashboard    Yes         NO            fa-bar-chart

11 rows selected.

PL/SQL procedure successfully completed.

And for DEMO_DEV, the administrator, it returns all 16 active entries:

Output:

PL/SQL procedure successfully completed.

MENU_TREE                   HAS_LINK    IS_CURRENT    IMAGE
___________________________ ___________ _____________ ____________________
Home                        Yes         YES           fa-home
Orders                      No          NO            fa-shopping-cart
  New Order                 Yes         NO            fa-plus
  Order Board               Yes         NO            fa-columns
  Approve Orders            Yes         NO            fa-check-square-o
Products                    No          NO            fa-cubes
  Product List              Yes         NO            fa-table
  Product Cards             Yes         NO            fa-th-large
Billing                     No          NO            fa-money
  Invoices                  Yes         NO            fa-file-text-o
  Quote Lines               Yes         NO            fa-calculator
Sales Dashboard             Yes         NO            fa-bar-chart
Administration              No          NO            fa-gear
  Order Cleanup Settings    Yes         NO            fa-sliders
  Login History             Yes         NO            fa-history
  Menu Entries              Yes         NO            fa-list

16 rows selected.

PL/SQL procedure successfully completed.

In a first test, the query used APEX_ACL.HAS_USER_ROLE for the role check. In this example it returned FALSE for every user and role, even in a PL/SQL block, while APEX_ACL.HAS_USER_ANY_ROLES returned TRUE. Reading the APEX_APPL_ACL_USER_ROLES view gave the right result, so the guide uses the view.

Step 3: Create the Dynamic List

In App Builder, open the application, click Shared Components, and under Navigation and Search click Lists. Click Create. On the first screen, enter Name Dynamic Navigation Menu, select Type Dynamic and click Next:

Create List wizard Name and Type screen with Name Dynamic Navigation Menu and Type Dynamic marked with a red box
A dynamic list gets its entries from a query.

On the Query or Static Values screen, keep Query Source Type SQL Query and paste the menu query of Step 2. Click Next:

Create List wizard Query screen with Query Source Type SQL Query and the menu query in the editor, both marked with red boxes
The menu query goes into the SQL Query editor.

The Confirm screen shows the name and the query. Click Create List:

Create List wizard Confirm screen showing List Name Dynamic Navigation Menu and the list query
The last wizard screen before the list is created.

Step 4: Use the List as the Navigation Menu

Go back to Shared Components and click User Interface Attributes. In the Navigation Menu section, change Navigation Menu List from Navigation Menu to Dynamic Navigation Menu. Keep Position Side and List Template Side Navigation Menu, and click Apply Changes:

User Interface Attributes with Navigation Menu List set to Dynamic Navigation Menu marked with a red box
One setting switches the whole application to the table-driven menu.

The Lists page now shows Yes under Navigation Menu for the new list. The old static Navigation Menu list stays in the application with 0 references, so you can switch back at any time:

Shared Components Lists page with the Dynamic Navigation Menu row marked with a red box, showing Type Dynamic and Navigation Menu Yes
The static list is kept for rollback.

Step 5: Create the Menu Admin Pages

Administrators need a page to maintain the table. Open any page in Page Designer, click the Create menu (plus icon) and click Page. Select Interactive Report:

Create a Page wizard with the Interactive Report tile marked with a red box
An interactive report with a form page lists and edits the entries.

Enter Page Number 380 and Name Menu Entries. Turn on Include Form Page, enter Form Page Number 381 and Form Page Name Menu Entry, and select Form Page Mode Modal Dialog:

Create Interactive Report wizard with Page Number 380, Name Menu Entries, Include Form Page, Form Page Number 381, Form Page Name Menu Entry and Form Page Mode marked with red boxes
The wizard creates the report page and the form page together.

Under Data Source, keep Table and enter MENU_ENTRIES as the Table / View Name:

Data Source section with Table / View Name MENU_ENTRIES marked with a red box
Both pages are based on MENU_ENTRIES.

Under Navigation, turn off Use Navigation. The wizard would add an entry to the static list, but this menu comes from the table. Click Next:

Navigation section with Use Navigation switched off and marked with a red box
The Menu Entries page gets its menu entry from the table instead.

On the Primary Key screen, keep ENTRY_ID (Number) and click Create Page:

Primary Key screen with Primary Key Column 1 ENTRY_ID marked with a red box
ENTRY_ID identifies each menu entry.

The sample data already has an entry for page 380 (Menu Entries) under Administration, so the new page appears in the menu at once.

Step 6: Allow Only Administrators

Hiding a menu entry does not protect the page, because anyone can type its URL. Open page 380 in Page Designer, click the page node, and under Security set Authorization Scheme to Is Administrator:

Page 380 properties with Security Authorization Scheme Is Administrator marked with a red box
Page 380 is for administrators only.

Set the same Authorization Scheme on page 381, the form page:

Page 381 properties with Security Authorization Scheme Is Administrator marked with a red box
The form page needs its own check.

Is Administrator is an authorization scheme of type Is In Role or Group that checks the Administrator role.

Step 7: Improve the Form Items

The wizard creates a text field for every column. Open page 381 and change the items so that administrators pick values instead of typing IDs. First set the Sequence under Layout so the items appear in this order: P381_LABEL 10, P381_PARENT_ID 20, P381_TARGET_PAGE 30, P381_ICON_CSS 40, P381_SORT_SEQ 50, P381_REQUIRED_ROLES 60, P381_IS_ACTIVE 70.

Select P381_PARENT_ID. Set Label to Parent Group. Under List of Values, set Type to SQL Query, turn on Display Null Value, set Null Display Value to "- Top level -", and enter this query:

Example:

select label as display_value,
       entry_id as return_value
  from menu_entries
 where target_page is null
 order by sort_seq
Property Editor of P381_PARENT_ID with Type Select List, Label Parent Group, List of Values Type, SQL Query, Display Null Value and Null Display Value marked with red boxes
The parent list offers only group headings.

Select P381_TARGET_PAGE. Set Type to Select List. Under List of Values, set Type to SQL Query, turn on Display Null Value, set Null Display Value to "- None (group heading) -", and enter this query:

Example:

select page_id || ' - ' || page_name as display_value,
       page_id as return_value
  from apex_application_pages
 where application_id = :APP_ID
   and page_mode = 'Normal'
   and page_id not in (0, 9999)
 order by page_id
Property Editor of P381_TARGET_PAGE with Type Select List and the List of Values settings marked with red boxes
The target page list reads the pages of the current application.

Select P381_ICON_CSS. Set Label to Icon and, under Help, Inline Help Text to "Font APEX class, for example fa-home.". Select P381_SORT_SEQ and set Label to Sequence.

Select P381_REQUIRED_ROLES. Set Type to Checkbox Group and Number of Columns to 3. Under List of Values, set Type to SQL Query and enter this query:

Example:

select role_name as display_value,
       role_static_id as return_value
  from apex_appl_acl_roles
 where application_id = :APP_ID
 order by role_name
Property Editor of P381_REQUIRED_ROLES with Type Checkbox Group, Number of Columns 3, List of Values Type SQL Query and the query marked with red boxes
One checkbox per Access Control role.

A checkbox group saves the checked values as one string separated by colons, such as administrator:editor. That is the format the menu query splits with APEX_STRING.SPLIT. Under Help, set Inline Help Text to "Leave all unchecked to show the entry to every user.".

Select P381_IS_ACTIVE. Set Type to Switch and Label to Active. The switch uses the application defaults Y and N, which match the check constraint:

Property Editor of P381_IS_ACTIVE with Type Switch and Label Active marked with red boxes
A switch is easier than typing Y or N.

Under Default, set Type to Static and Static Value to Y, so a new entry starts as active:

Default group of P381_IS_ACTIVE with Type Static and Static Value Y marked with a red box
New entries are active by default.

Click Save. On page 380, you can also change the column headings Icon Css, Is Active and Sort Seq to Icon, Active and Sequence.

Step 8: Test the Menu for Each Role

Run the application as DEMO_DEV and open the navigation menu. All groups are expanded for the screenshots; in normal use, the side menu opens one group at a time. The administrator sees every active entry:

Side navigation menu for demo_dev with Orders, Products, Billing and Administration groups and all their entries
The Administrator sees all four groups.

Sign in as ORDER_EDITOR. The Editor sees New Order and the Billing group, but not Approve Orders or Administration:

Side navigation menu for order_editor with Orders, Products and Billing groups, without Approve Orders and Administration
Entries for administrators are hidden.

Sign in as SALES_USER. The Reader sees only the entries without required roles. The empty Billing group is removed:

Side navigation menu for sales_user with Home, Orders with Order Board, Products with two entries and Sales Dashboard
The Reader gets the shortest menu.

As SALES_USER, click Order Board. The IS_CURRENT column marks the entry, and APEX opens its group and highlights it:

Order Board page with the Orders group open and Order Board highlighted in the side menu
The current page is highlighted in the menu.

Now try to open the admin page directly. Still as SALES_USER, change the page alias in the URL to menu-entries. APEX stops the request:

Error dialog Only administrators can use this page. Access denied by Page security check
The page authorization blocks users without the role.

Step 9: Add an Entry from the Admin Page

Sign in as DEMO_DEV and click Menu Entries in the Administration group. The report lists all entries:

Menu Entries interactive report with Parent, Label, Icon, Target Page, Sequence, Required Roles and Active columns
The admin page shows the menu table.

Click Create and fill in the form: Label Field Visits, Parent Group "- Top level -", Target Page 355, Icon fa-map-marker, Sequence 70. Check Administrator and Editor, keep Active on, and click Create:

Menu Entry dialog with Label Field Visits, Target Page 355, Icon fa-map-marker, Sequence 70 and the Administrator and Editor roles checked and marked with a red box
A new top-level entry for administrators and editors.

The dialog closes and the report shows the new row, but the side menu does not change yet. APEX builds the navigation menu when a page is rendered, so the entry appears on the next page load:

Menu Entries page after reload with the new Field Visits entry in the side menu marked with a red box
After a page load, Field Visits is in the menu.

Check the new row in SQL:

Example:

set pagesize 50
select entry_id, parent_id, label, icon_css, target_page, sort_seq, required_roles, is_active
  from menu_entries
 where label = 'Field Visits';

Output:

   ENTRY_ID    PARENT_ID LABEL           ICON_CSS            TARGET_PAGE    SORT_SEQ REQUIRED_ROLES          IS_ACTIVE
___________ ____________ _______________ ________________ ______________ ___________ _______________________ ____________
         19              Field Visits    fa-map-marker               355          70 administrator:editor    Y

The checkbox group saved the roles as administrator:editor. Running the test script of Step 2 again shows that ORDER_EDITOR now gets Field Visits:

Output:

PL/SQL procedure successfully completed.

MENU_TREE          HAS_LINK    IS_CURRENT    IMAGE
__________________ ___________ _____________ ___________________
Home               Yes         YES           fa-home
Orders             No          NO            fa-shopping-cart
  New Order        Yes         NO            fa-plus
  Order Board      Yes         NO            fa-columns
Products           No          NO            fa-cubes
  Product List     Yes         NO            fa-table
  Product Cards    Yes         NO            fa-th-large
Billing            No          NO            fa-money
  Invoices         Yes         NO            fa-file-text-o
  Quote Lines      Yes         NO            fa-calculator
Sales Dashboard    Yes         NO            fa-bar-chart
Field Visits       Yes         NO            fa-map-marker

12 rows selected.

PL/SQL procedure successfully completed.

SALES_USER does not get it, because the Reader role is not in the list:

Output:

PL/SQL procedure successfully completed.

MENU_TREE          HAS_LINK    IS_CURRENT    IMAGE
__________________ ___________ _____________ ___________________
Home               Yes         YES           fa-home
Orders             No          NO            fa-shopping-cart
  Order Board      Yes         NO            fa-columns
Products           No          NO            fa-cubes
  Product List     Yes         NO            fa-table
  Product Cards    Yes         NO            fa-th-large
Sales Dashboard    Yes         NO            fa-bar-chart

7 rows selected.

PL/SQL procedure successfully completed.

Step 10: Try to Delete a Group with Entries

Open the Orders group in the form and click Delete. The foreign key does not allow it, because New Order, Order Board and Approve Orders still point to the group:

Menu Entry dialog for Orders with the error ORA-02292 integrity constraint MENU_ENTRIES_PARENT_FK violated - child record found
The self-referencing foreign key protects the menu tree.

Move or delete the entries of a group first, or set the group to inactive. In a test, setting Billing to inactive hid Invoices and Quote Lines too, because the menu query filters the group before CONNECT BY.

Things to Know

  • Hiding a menu entry is not security. Protect every page with an authorization scheme, as Step 6 did for pages 380 and 381.
  • The menu query runs when each page is rendered. A change in MENU_ENTRIES shows after the next page load, not right after the dialog closes.
  • Put the role and active filters in an inline view before CONNECT BY. Then a hidden group also hides its entries, and the outer CONNECT_BY_ISLEAF check removes groups left empty.
  • In this example, APEX_ACL.HAS_USER_ROLE returned FALSE for all users when called from an APEX session in SQL, so the query reads APEX_APPL_ACL_USER_ROLES. Test the role check for each user before you switch the menu.
  • Keep the static Navigation Menu list. To roll back, select it again in User Interface Attributes.

Related Guides

Conclusion

A table-driven navigation menu in Oracle APEX needs one table, one dynamic list query, and one change in User Interface Attributes. The query builds the tree with CONNECT BY, filters entries by Access Control roles, and drops empty groups.

An interactive report with a modal form lets administrators add, reorder and hide entries without a new deployment. The tests showed a different menu for each role, the current page highlighted, a blocked direct URL, and a foreign key that keeps the tree consistent.

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