---
title: Menu tree construction
summary: Retrieves and persists the hierarchical menu structure — pages grouped under an application, and items grouped under a page.
status: draft
owner: SAIF
last_reviewed: 2026-07-30
library: SAIF.Business.Menu
reference_type: package
operation_id: OP-MNU-01
trigger: Presentation-layer consumer requests a menu item/page by key, all items/pages, or saves a new/changed item or page
---

# Menu tree construction

Menu tree construction builds and maintains the two-level hierarchy that the presentation layer renders as the application menu: `clsMenuPage` records group toolbar buttons and links under an application, and `clsMenuItem` records are the individual tree nodes assigned to a page and a category. Both objects follow the standard BOF CRUD lifecycle — retrieve by key or in full, insert with a sequence-generated key, update, and delete — each governed by the row-count contention check.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-MNU-01 |
| **Trigger** | Caller requests `clsMenuItem`/`clsMenuItemCollection` or `clsMenuPage`/`clsMenuPageCollection` by key or in full, or calls `Save()` after `Create()`/retrieve |
| **Inputs** | `MenuItemSeqNum` / `MenuPageSeqNum` (key lookups); for insert/update — page/category association, tooltip text, visibility flag, page order, application code, page link, and toolbar-button flags/tooltips |
| **Outputs** | Populated `clsMenuItem`/`clsMenuItemCollection` or `clsMenuPage`/`clsMenuPageCollection`; on save, a new sequence-generated key or updated row; error messages on DML contention |
| **Primary components** | `clsMenuItem`, `clsMenuItemCollection`, `clsMenuPage`, `clsMenuPageCollection` |
| **Entry point** | `Business\SAIF.Business.Menu\clsMenuItem.vb`, `Business\SAIF.Business.Menu\clsMenuPage.vb` |

## Steps

1. **Retrieve a single menu item.** `clsMenuItem.GetMenuItem` creates a `Criteria` with the requested `MenuItemSeqNum` and invokes the data portal, which runs `BeforeRetrieve` to bind `:MenuItemSeqNum` and execute `RetrieveSQL` ([SQL-022](../sql/SQL-022-saif-business-menu.md), [MENU_ITEM](../database-tables.md#menu-item)). `AfterRetrieve` fills the object from the reader and marks it old.
   - `MenuPage` and `MenuCategory` are lazy-loaded read-only properties: the first access triggers `clsMenuPage.GetMenuPage` ([SQL-024](../sql/SQL-024-saif-business-menu.md)) or `clsMenuCategoryCode.GetMenuCategoryCode` respectively.
   - `SecurityGroups` is likewise lazy-loaded via `clsMenuSecurityCollection.GetMenuSecuritiesForItem` ([SQL-027](../sql/SQL-027-saif-business-menu.md)).
2. **Retrieve the full item collection.** `clsMenuItemCollection.GetAllMenuItems` executes the unfiltered `RetrieveSQL` ([SQL-023](../sql/SQL-023-saif-business-menu.md), [MENU_ITEM](../database-tables.md#menu-item)) and loads one `clsMenuItem` per row via `AfterRetrieve`.
3. **Retrieve a single page or the full page collection.** `clsMenuPage.GetMenuPage` and `clsMenuPageCollection.GetAllMenuPages` follow the identical by-key ([SQL-024](../sql/SQL-024-saif-business-menu.md)) and all-rows ([SQL-025](../sql/SQL-025-saif-business-menu.md)) pattern against [MENU_PAGE](../database-tables.md#menu-page).
4. **Create a new menu item.** `clsMenuItem.NewMenuItem` calls `Create()`, which invokes `BeforeCreate`/`AfterCreate`: a scalar request executes `SELECT MENU_ITEM_SEQ.NEXTVAL FROM DUAL` ([SQL-002](../sql/SQL-002-saif-business-menu.md), [DUAL](../database-tables.md#dual)), and the returned value seeds `MenuItemSeqNum` on both the object and its criteria before the object is marked new and clean.
5. **Save (insert) the new item.** `IsValid` checks `MyBase.IsValid` and the validity of the lazily-loaded `SecurityGroups` child collection before `Save()`/`SaveChild()` proceeds. `BeforeInsert` binds the page, category, visible flag, page order, tooltip, and the acting user (`DataPortalInfo.UserID`, upper-cased), then executes `InsertSQL` ([SQL-008](../sql/SQL-008-saif-business-menu.md), [MENU_ITEM](../database-tables.md#menu-item)).
   - On success (`RowsAffected = 1`): `AfterInsert` calls `MarkOld()` and, if `SecurityGroups.IsDirty`, `SaveChildObject(SecurityGroups)` cascades the save to the child security collection ([BR-MNU-02](../business-rules.md#br-mnu-02-dml-row-count-enforcement)).
   - On failure (`RowsAffected <> 1`): `Me.AddErrorMessage("InsertError", "Insert Failed")` is added; the save is rejected.
6. **Save (insert) a new menu page.** `clsMenuPage.NewMenuPage` / `AfterCreate` obtains `MENU_PAGE_SEQ.NEXTVAL` ([SQL-003](../sql/SQL-003-saif-business-menu.md), [DUAL](../database-tables.md#dual)); `BeforeInsert` assembles the insert with `StringBuilder` across all page fields (page name, link, application code, favorite name, and every toolbar flag/tooltip) and executes `InsertSQL` ([SQL-009](../sql/SQL-009-saif-business-menu.md), [MENU_PAGE](../database-tables.md#menu-page)).
   - On a rows-affected value other than `1`: `AddErrorMessage("InsertError", ...)` ([BR-MNU-02](../business-rules.md#br-mnu-02-dml-row-count-enforcement)).
7. **Update an existing item or page.** `BeforeUpdate` rebinds all mutable fields plus the primary key and executes `UpdateSQL` ([SQL-034](../sql/SQL-034-saif-business-menu.md) for items, [SQL-035](../sql/SQL-035-saif-business-menu.md) for pages). `AfterUpdate` applies the same row-count contention check, calling `MarkOld()` on success or `AddErrorMessage("UpdateError", BOC.Messages.DBContentionError)` on failure ([BR-MNU-02](../business-rules.md#br-mnu-02-dml-row-count-enforcement)).
8. **Delete an item or page.** `BeforeDelete` binds the key and executes `DeleteSQL` ([SQL-041](../sql/SQL-041-saif-business-menu.md) for items, [SQL-042](../sql/SQL-042-saif-business-menu.md) for pages). `AfterDelete` calls `MarkNew()` on a rows-affected value of `1`, or adds a `DeleteError` otherwise ([BR-MNU-02](../business-rules.md#br-mnu-02-dml-row-count-enforcement)).
9. **Assemble the tree.** The presentation layer combines the retrieved item and page collections — joining items to their parent page via `MenuPageSeqNum` and to their category via `MenuCategoryCode` — to render the hierarchical menu. This assembly step is performed by the consuming presentation layer, not within this library.*

## Process flow

The following diagram visualizes the retrieve/save control flow shared by `clsMenuItem` and `clsMenuPage`.

```mermaid
flowchart TD
	Start([Caller requests item/page<br>by key, all, or new]) --> Which{Operation?}
	Which -->|Retrieve by key| RetKey[Execute RetrieveSQL<br>with key parameter]
	RetKey --> Fill[Fill object from reader;<br>MarkOld]
	Which -->|Retrieve all| RetAll[Execute unfiltered RetrieveSQL]
	RetAll --> FillEach[Load one object per row]
	Which -->|Create new| Seq[Obtain NEXTVAL from<br>MENU_ITEM_SEQ / MENU_PAGE_SEQ]
	Seq --> MarkNewClean[Seed key; MarkNew; MarkClean]
	MarkNewClean --> SaveCall{Save called?}
	SaveCall -->|Yes| Insert[Execute InsertSQL]
	Insert --> Rows{RowsAffected = 1?}
	Rows -->|Yes| Ok([MarkOld; cascade child save])
	Rows -->|No| Err([AddErrorMessage: InsertError])
```

## Failure and edge-case behavior

- Every insert, update, and delete on `clsMenuItem` and `clsMenuPage` enforces a strict rows-affected-equals-1 check; any other value raises `InsertError`, `UpdateError`, or `DeleteError` and the object is not marked clean ([BR-MNU-02](../business-rules.md#br-mnu-02-dml-row-count-enforcement)).
- `clsMenuItem.IsValid` and `IsDirty` both cascade into the lazily-loaded `SecurityGroups` child collection, so a dirty or invalid security-group child blocks or flags the parent item even if the item's own fields are unchanged.
- The child `SecurityGroups` collection is only saved when `SecurityGroups.IsDirty` is true and the parent insert/update succeeded; if the parent DML fails, the child save is skipped entirely.
- `clsMenuPage`'s insert/update SQL is assembled with `StringBuilder` across roughly twenty columns; a mismatch between the assembled parameter list and the bound parameters would surface only at execution time, not through compile-time checking.*
- Presentation-layer tree assembly (joining items to pages and categories) is not implemented in this library; if the page or category referenced by an item's foreign key is deleted independently, the tree assembly step must handle the resulting orphaned reference.*

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [SQL catalog](../sql/index.md)
- [Database tables](../database-tables.md)

> **Note:** Statements marked with `*` were inferred from code structure, naming
> conventions, or contextual evidence where direct confirmation was not available
> in source artifacts.

## Sources
- `Business\SAIF.Business.Menu\clsMenuItem.vb`
- `Business\SAIF.Business.Menu\clsMenuItemCollection.vb`
- `Business\SAIF.Business.Menu\clsMenuPage.vb`
- `Business\SAIF.Business.Menu\clsMenuPageCollection.vb`
