---
title: SAIF.Presentation.Web.Controls.V2.Menu business rules
summary: Enumerated business rules with per-rule trigger, enforcement, and outcomes.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-08-17
library: SAIF.Presentation.Web.Controls.V2.Menu
reference_type: package
---

# SAIF.Presentation.Web.Controls.V2.Menu business rules

This page captures business rules enforced by this library.

## BR-MENU-001: No navigation data is loaded at design time

| Field | Value |
| --- | --- |
| **Statement** | No navigation data is loaded when the control is running in the Visual Studio designer. |
| **Trigger** | The control's `DesignMode` property is true when `GetNavigationData` is invoked. |
| **Enforcement** | `GetNavigationData` (`SAIFMenu.vb`, `SAIFMenuTop.vb`) returns `Nothing` immediately without touching session or `NavigationContext`. |
| **Outcome** | No session or database access occurs at design time; the designer surface renders using the design-mode placeholder paths instead. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-002: Navigation data is cached once per session

| Field | Value |
| --- | --- |
| **Statement** | Navigation data is loaded at most once per ASP.NET session and reused for the remainder of the session. |
| **Trigger** | `GetNavigationData` finds a non-null value under `Session("navdata")`. |
| **Enforcement** | The cached `NavigationContext` is returned directly, bypassing construction. |
| **Outcome** | Menu/authorization data does not reflect changes made to the user's authorised items until the session ends and a new one begins.* |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-003: The menu user ID is the normalized Windows logon identity

| Field | Value |
| --- | --- |
| **Statement** | The menu user is the Windows logon identity with the domain prefix removed, trimmed and upper-cased. |
| **Trigger** | `Session("navdata")` is empty (first access in the session). |
| **Enforcement** | `HttpContext.Current.Request.LogonUserIdentity.Name` is read, the substring after the last `\` is taken, trimmed, and upper-cased. |
| **Outcome** | The resulting normalized user ID becomes the sole input used to resolve authorised menu items. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-004: Menu content is scoped to the signed-in user's authorised items

| Field | Value |
| --- | --- |
| **Statement** | Menu content is scoped to the signed-in user's authorised items — the user ID is the sole `NavigationContext` constructor input. |
| **Trigger** | First access to navigation data in the session. |
| **Enforcement** | `New NavigationContext(userid)` is constructed and stored in `Session("navdata")`. |
| **Outcome** | Subsequent renders read the user's authorised items from the session-cached collection instead of querying Oracle again. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-005: The hosting page must be castable to ViewPageBase

| Field | Value |
| --- | --- |
| **Statement** | The hosting page must be (or be castable to) `ViewPageBase`; otherwise page-level integration is skipped. |
| **Trigger** | Control `OnInit` / `Page_PreLoad`. |
| **Enforcement** | The control casts `Page` to `ViewPageBase`, storing it in `m_Page`; the cast fails silently (`Nothing`) when the page isn't a `ViewPageBase`. |
| **Outcome** | `ViewID`-based session/view-state keys become empty strings and page-lifecycle integration (`PreLoad` handling) is skipped when the host page is not a `ViewPageBase`.* |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-006: A zero-sized default-submit button renders first

| Field | Value |
| --- | --- |
| **Statement** | A zero-sized default-submit image button is rendered first so pressing Enter does not trigger favourites maintenance. |
| **Trigger** | Control tree construction (`BuildMenuIcons`). |
| **Enforcement** | `imgDefaultOnSubmit` / `imgTopDefaultOnSubmit`, a zero-sized `SAIFImageButton`, is added to the control tree ahead of other icons. |
| **Outcome** | Pressing Enter on the page submits through this hidden button rather than triggering the favourites-maintenance or other icon actions. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-007: The left menu control tree is rebuilt on every page load

| Field | Value |
| --- | --- |
| **Statement** | The left menu control tree is rebuilt on every page load. |
| **Trigger** | `SAIFMenu.OnLoad`. |
| **Enforcement** | `CreateControls` runs unconditionally on every page load, rebuilding the context box, favourites, and applications sections. |
| **Outcome** | No caching of the rendered control tree across postbacks; the menu is always rebuilt from current authorisation/preference state. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-008: F12 without Ctrl selects and focuses the context box

| Field | Value |
| --- | --- |
| **Statement** | Pressing F12 without Ctrl selects and focuses the context search box. |
| **Trigger** | `SAIFMenu.OnLoad` registers a page startup script. |
| **Enforcement** | A client script binds key code 123 (F12) without Ctrl to select and focus the context textbox. |
| **Outcome** | Users can jump to the context search box via a single keystroke. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-009: Enter in the context box posts back through the hidden EC link button

| Field | Value |
| --- | --- |
| **Statement** | Pressing Enter in the context box posts back through the hidden `EC` link button rather than submitting the form normally. |
| **Trigger** | Same `OnLoad` script registration. |
| **Enforcement** | Enter-key handling is wired to post back through the hidden `EC` `LinkButton` instead of the browser's default form submission. |
| **Outcome** | Enter-key submissions always route through `m_EnterControl_Click`, ensuring `MenuNavigate("ContextValue", "ENTER")` fires consistently. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-010: Watermark scripts are registered once per page

| Field | Value |
| --- | --- |
| **Statement** | Watermark scripts are registered once per page. |
| **Trigger** | `SAIFMenu.OnLoad`. |
| **Enforcement** | `RegisterClientScriptBlock` guards against duplicate registration using a per-page key. |
| **Outcome** | The `js_waterMark_Focus` / `js_waterMark_Blur` functions are emitted only once even if multiple `SAIFMenu` instances exist. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-011: The context box supports three display modes

| Field | Value |
| --- | --- |
| **Statement** | The context box supports three display modes: normal, read-only and hidden. |
| **Trigger** | `SAIFMenu.InitializeControls`. |
| **Enforcement** | The `ContextViewStyle` switch renders an editable, read-only, or hidden textbox depending on `ContextViewStyleEnum`. |
| **Outcome** | Hosting pages control the interactivity of the context search box without needing to conditionally omit the control. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-012: An empty context box displays its label as watermark text

| Field | Value |
| --- | --- |
| **Statement** | An empty context box displays its label as watermark text styled `watermark`. |
| **Trigger** | `InitializeControls`, Normal mode with an empty `ContextValue` and a non-empty `ContextLabel`. |
| **Enforcement** | The label text is written into the textbox and styled with the `watermark` CSS class. |
| **Outcome** | Users see a placeholder prompt instead of an empty box. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-013: The watermark clears on focus and is restored on blur

| Field | Value |
| --- | --- |
| **Statement** | The watermark clears on focus and is restored on blur when the box is left empty. |
| **Trigger** | Client-side focus/blur events on the context textbox. |
| **Enforcement** | `js_waterMark_Focus` / `js_waterMark_Blur` scripts toggle the watermark text and CSS class. |
| **Outcome** | The watermark behaves like placeholder text without using the HTML5 `placeholder` attribute. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-014: Single quotes in the context label are escaped before script emission

| Field | Value |
| --- | --- |
| **Statement** | Single quotes in the context label are escaped before being emitted into JavaScript. |
| **Trigger** | `InitializeControls` attaches `OnFocus` / `OnBlur` handlers. |
| **Enforcement** | The `ContextLabel` value has single quotes escaped before being concatenated into the inline JavaScript call. |
| **Outcome** | A label containing a single quote does not break the generated script, though other characters remain unescaped.* |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-015: A context box showing its watermark label is treated as empty

| Field | Value |
| --- | --- |
| **Statement** | A context box still showing its watermark label is treated as empty. |
| **Trigger** | Reading the `ContextValue` property. |
| **Enforcement** | If the textbox text equals the watermark label, `ContextValue` returns `String.Empty` rather than the literal label text. |
| **Outcome** | Callers never see the watermark prompt text as a real user-entered value. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-016: The context value is upper-cased when configured

| Field | Value |
| --- | --- |
| **Statement** | The context value is upper-cased when `UpperCaseContextValue` is set. |
| **Trigger** | Reading `ContextValue` with `UpperCaseContextValue` = True. |
| **Enforcement** | The returned string is upper-cased before being handed back to the caller. |
| **Outcome** | Callers relying on case-insensitive matching receive normalized input. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-017: Setting the context value raises OnContextChanged

| Field | Value |
| --- | --- |
| **Statement** | Setting the context value raises `OnContextChanged` with the previous and new values. |
| **Trigger** | Assignment to the `ContextValue` property. |
| **Enforcement** | The setter trims (and optionally upper-cases) the incoming value, then raises `OnContextChanged(oldValue, newValue)`. |
| **Outcome** | Hosting pages can react to context changes (e.g., re-querying data) without polling the textbox themselves. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-018: Design mode shows placeholder favourites

| Field | Value |
| --- | --- |
| **Statement** | Design mode shows three placeholder favourites rather than live data. |
| **Trigger** | `AddFavorites` when `DesignMode` is true. |
| **Enforcement** | Three static `Favorite 1`–`Favorite 3` links are emitted instead of querying `NavigationData`. |
| **Outcome** | The control has a representative appearance in the Visual Studio designer without requiring a live session. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-019: Favourites are displayed in MenuOrder sequence

| Field | Value |
| --- | --- |
| **Statement** | Favourites are displayed in `MenuOrder` sequence. |
| **Trigger** | `AddFavorites` at run time with a non-empty `Favorites` collection. |
| **Enforcement** | The collection is sorted by `MenuOrder` before link generation (delegated to `clsMenuAuthorizedItemCollection`). |
| **Outcome** | Favourites appear in the sequence configured by menu administration rather than an arbitrary order. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-020: Favourite link IDs embed the menu item sequence number

| Field | Value |
| --- | --- |
| **Statement** | Favourite link IDs embed the menu item sequence number so clicks can be resolved back to the item. |
| **Trigger** | `AddFavorites` link construction. |
| **Enforcement** | Each `LinkButton` ID is built as `<controlId>fav_LB<MenuItemSeqNum>`. |
| **Outcome** | `LinkButtonClick` can parse the sequence number back out of the ID to resolve which item was clicked. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-021: A favourite's tooltip combines its menu category and item tooltip

| Field | Value |
| --- | --- |
| **Statement** | A favourite's tooltip combines its menu category and the item tooltip. |
| **Trigger** | `AddFavorites` link construction. |
| **Enforcement** | The `ToolTip` is set to `<MenuCategory> - <ToolTip>`. |
| **Outcome** | Hovering a favourite shows both its category and page-specific tooltip text. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-022: The favourite matching the current page is highlighted

| Field | Value |
| --- | --- |
| **Statement** | The favourite matching the current page is highlighted with `favoriteitem-selected`. |
| **Trigger** | `AddFavorites` link construction, comparing `MenuPageSeqNum` to the current item. |
| **Enforcement** | CSS class is set to `favoriteitem-selected` for a match, otherwise `favoriteitem`. |
| **Outcome** | The user can visually identify which favourite corresponds to the page they are viewing. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-023: The Change Password link is offered only to external clients

| Field | Value |
| --- | --- |
| **Statement** | The "Change Password" link is offered only to external clients. |
| **Trigger** | `AddFavorites`, after rendering favourite links. |
| **Enforcement** | `If AppGlobal.EnvironmentService.IsClientExternal` appends a "Change Password" link (`ChangePWLink`) to the favourites list. |
| **Outcome** | Internal users, whose passwords are managed by domain policy, do not see a self-service password link. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-024: The applications list is a fixed-size, timed accordion

| Field | Value |
| --- | --- |
| **Statement** | The applications list is an accordion 120 pixels wide with a 250 ms transition. |
| **Trigger** | `AddLinks` when `ShowApps` is true. |
| **Enforcement** | An `AjaxControlToolkit.Accordion` (`UMenu`) is constructed with a fixed 120-pixel width, header CSS classes `applicationitem` / `application-current`, and a 250 ms transition at 50 frames per second. |
| **Outcome** | The applications accordion has a consistent size and animation across all hosting pages. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-025: Application items are ordered by category then order

| Field | Value |
| --- | --- |
| **Statement** | Application items are ordered by menu category then menu order. |
| **Trigger** | `AddLinks`, before pane construction. |
| **Enforcement** | Items are sorted using two `SortParm` entries (`MenuCategory`, `MenuOrder`) via `clsMenuAuthorizedItemCollection.SortMulti`. |
| **Outcome** | Categories and their items render in a predictable, administrator-configured sequence. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-026: One accordion pane is created per menu category

| Field | Value |
| --- | --- |
| **Statement** | One accordion pane is created per menu category. |
| **Trigger** | `AddLinks` iteration over sorted items. |
| **Enforcement** | A new `AccordionPane` is opened whenever the category code changes from the previous item. |
| **Outcome** | Items sharing a category are grouped under a single collapsible pane. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-027: A category pane is created only when it has a visible item

| Field | Value |
| --- | --- |
| **Statement** | A category pane is created only when it contains at least one visible item. |
| **Trigger** | `AddLinks` category-change detection. |
| **Enforcement** | A pane is opened only when the category contains at least one item with `IsVisible = True`. |
| **Outcome** | Categories whose items are all hidden do not produce an empty, unusable pane. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-028: Pane IDs must be non-empty, non-reserved, and unique

| Field | Value |
| --- | --- |
| **Statement** | Pane IDs must be non-empty, must not equal `_Header` and must be unique; violations throw `ApplicationException`. |
| **Trigger** | `AddLinks` pane creation. |
| **Enforcement** | A pane ID that is empty, equals `_Header`, or duplicates an existing pane throws `ApplicationException("Bad Menu Pane ID: …")`. |
| **Outcome** | Malformed or colliding menu category configuration surfaces as a hard failure rather than a silently broken accordion. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-029: Only IsVisible items are rendered as menu links

| Field | Value |
| --- | --- |
| **Statement** | Only items flagged `IsVisible` are rendered as menu links. |
| **Trigger** | `AddLinks` and `BuildLinkCollection` item iteration. |
| **Enforcement** | A `LinkButton` is added only `If Item.IsVisible`. |
| **Outcome** | Items flagged not visible are excluded from both the left accordion and the top link row even if they are present in the authorised collection. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-030: The current page's application link is highlighted

| Field | Value |
| --- | --- |
| **Statement** | The application link for the current page is highlighted with `applicationitemselected`. |
| **Trigger** | `AddLinks` link construction. |
| **Enforcement** | CSS class `applicationitemselected` is applied when the item matches the current page; otherwise `application-submenu`. |
| **Outcome** | The active application link is visually distinguished in the accordion. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-031: The accordion opens on the selected category

| Field | Value |
| --- | --- |
| **Statement** | The accordion opens on the pane matching `SelectedCategory`. |
| **Trigger** | `AddLinks` / `SelectCategoryInMenu`, when `SelectedCategory` is set. |
| **Enforcement** | The accordion's selected pane index is set to match the pane whose ID corresponds to `SelectedCategory`. |
| **Outcome** | The applications section opens directly to the category relevant to the current page rather than always defaulting to the first pane. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-032: The context value is preserved before menu-driven postback navigation

| Field | Value |
| --- | --- |
| **Statement** | The context value is preserved in session before any menu-driven postback navigation. |
| **Trigger** | `ExpandClick`, `NavigateToItem`, `m_EnterControl_Click`. |
| **Enforcement** | The current `ContextValue` is written to `Session("CVTextBox" & ViewID)` before the accordion is rebuilt or navigation occurs. |
| **Outcome** | The user's in-progress search text survives the postback and is restored by `Page_PreLoad`. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-033: The applications section toggles between expanded and collapsed

| Field | Value |
| --- | --- |
| **Statement** | The applications section toggles between expanded (`apps-minus.gif`) and collapsed (`apps-plus.gif`). |
| **Trigger** | `ExpandClick` (clicking the expand/collapse icon). |
| **Enforcement** | The toggle image source alternates between `apps-minus.gif` (Collapse) and `apps-plus.gif` (Expand), and `ShowApps` is set accordingly. |
| **Outcome** | The user gets consistent visual feedback for the applications pane's expanded/collapsed state. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-034: Expanding the applications section rebuilds and re-selects the category

| Field | Value |
| --- | --- |
| **Statement** | Expanding the applications section rebuilds the accordion and re-selects the current category. |
| **Trigger** | `ExpandClick`, expanding branch. |
| **Enforcement** | `AddLinks` (rendering logic) is re-invoked and the previously selected category is re-applied. |
| **Outcome** | Re-expanding the applications section restores the same category context the user had before collapsing it. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-035: The expand/collapse choice is saved as a user preference

| Field | Value |
| --- | --- |
| **Statement** | The expanded/collapsed choice is saved as user preference `SHOWAPPS` (`Y`/`N`) at application scope. |
| **Trigger** | `ExpandClick`, when a `clsPreferences` instance is available. |
| **Enforcement** | `Preferences.Add("SHOWAPPS", "Y"/"N", PrefScope.AppLevel)` followed by `Preferences.Save()`. |
| **Outcome** | The expand/collapse choice persists across sessions for the user, via `SAIF.Framework.Preferences` in Oracle (DB user `CLM`). |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-036: Session keys are namespaced by the page's ViewID

| Field | Value |
| --- | --- |
| **Statement** | Session keys are namespaced by the page's `ViewID`, falling back to an empty string. |
| **Trigger** | `Page_PreLoad` in both controls. |
| **Enforcement** | `m_Page.ViewID` (or empty string when not a `ViewPageBase`) is captured and used as a suffix on session/view-state keys. |
| **Outcome** | Multiple concurrent views in the same session do not collide on shared session state. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-037: The SHOWAPPS preference is applied only on the initial authenticated request

| Field | Value |
| --- | --- |
| **Statement** | The saved `SHOWAPPS` preference is applied only on the initial (non-postback) request for an authenticated user. |
| **Trigger** | `SAIFMenu.Page_PreLoad`, `Not IsPostBack` and an authenticated identity. |
| **Enforcement** | `ShowApps` is set from the stored `SHOWAPPS` preference only under this condition. |
| **Outcome** | The saved preference is not reapplied (and cannot override in-session toggling) on every postback. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-038: The applications list defaults to expanded when no preference is stored

| Field | Value |
| --- | --- |
| **Statement** | With no stored `SHOWAPPS` preference the applications list defaults to expanded. |
| **Trigger** | Same `Page_PreLoad` branch, no stored preference value. |
| **Enforcement** | `ShowApps` defaults to `True` (expanded) when no `SHOWAPPS` preference exists. |
| **Outcome** | First-time users see the applications pane expanded rather than collapsed. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-039: The current page is resolved once and cached in view state

| Field | Value |
| --- | --- |
| **Statement** | The current page is resolved once and then cached in view state. |
| **Trigger** | `Page_PreLoad` in both controls, when the current page is not yet cached. |
| **Enforcement** | `NavigationData.GetCurrentPage()` is called and the result stored in view state (`CurrentPage`). |
| **Outcome** | The comparatively expensive current-page lookup happens at most once per postback cycle. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-040: The context value is restored from session on the initial request only

| Field | Value |
| --- | --- |
| **Statement** | The context value is restored from session on the initial request only. |
| **Trigger** | `SAIFMenu.Page_PreLoad`, `Not IsPostBack`. |
| **Enforcement** | `ContextValue` is set from `Session("CVTextBox" & ViewID)` when present. |
| **Outcome** | A search value entered before a menu-driven navigation reappears in the context box on the destination page's first render. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-041: Preferences are keyed by the user-name portion of DOMAIN\user

| Field | Value |
| --- | --- |
| **Statement** | Preferences are keyed by the user name portion of `DOMAIN\user`. |
| **Trigger** | `SAIFMenu.Preferences` property access. |
| **Enforcement** | `HttpContext.Current.User.Identity.Name.Split("\"c)` yields the account name used as the `clsPreferences` user key. |
| **Outcome** | Preferences are scoped to the individual signed-in account, not the domain-qualified name. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-042: Preferences are stored under the fixed application code BOMENU

| Field | Value |
| --- | --- |
| **Statement** | Preferences are stored under the fixed application code `BOMENU`. |
| **Trigger** | Same `Preferences` property access. |
| **Enforcement** | `New clsPreferences(username, "BOMENU")` hard-codes the application scope. |
| **Outcome** | All menu layout preferences for this control are grouped under a single, well-known application code regardless of hosting page. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-043: Preference handling is skipped for non DOMAIN\user identities

| Field | Value |
| --- | --- |
| **Statement** | If the identity is not in `DOMAIN\user` form, preference handling is skipped entirely. |
| **Trigger** | `Preferences` property access, `names.Count <> 2`. |
| **Enforcement** | The property returns `Nothing`, and all callers (`ExpandClick`, `Page_PreLoad`) skip preference read/write when it is `Nothing`. |
| **Outcome** | Non-domain-qualified identities (or unauthenticated requests) never attempt a preference database call. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-044: The change-password link redirects with the view ID

| Field | Value |
| --- | --- |
| **Statement** | The change-password link redirects to `/BusinessOnline/Changepassword.aspx` carrying the view ID as `__SAIFPID`. |
| **Trigger** | `LinkButtonClick`, link ID contains `ChangePWLink`. |
| **Enforcement** | `Response.Redirect("/BusinessOnline/Changepassword.aspx?__SAIFPID=" & ViewID)`. |
| **Outcome** | External users are taken directly to the password-change page, bypassing the normal `MenuNavigate` event pattern, carrying the view ID for correlation. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-045: The clicked menu item is identified by the numeric suffix after _LB

| Field | Value |
| --- | --- |
| **Statement** | The clicked menu item is identified by the numeric suffix after `_LB` in the link ID. |
| **Trigger** | `LinkButtonClick` in both controls. |
| **Enforcement** | The substring following `_LB` in the link's ID is parsed as the menu item sequence number. |
| **Outcome** | A single generic click handler can resolve any rendered link back to its authorised menu item. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-046: Navigation targets come only from the authorised item

| Field | Value |
| --- | --- |
| **Statement** | Navigation targets are taken from the authorised item's `ApplicationPath` and `PageLink` — a user can only navigate to items present in their authorised collection. |
| **Trigger** | `NavigateToItem`, after resolving the sequence number. |
| **Enforcement** | `NavigationData.MenuAuthorizedItems.Item(ItemSeq)` supplies `ApplicationPath` and `PageLink`; no other source of navigation targets exists. |
| **Outcome** | A user cannot navigate through the menu to a page outside their authorised item collection. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-047: The control never navigates itself; it raises MenuNavigate

| Field | Value |
| --- | --- |
| **Statement** | The control never navigates itself; it raises `MenuNavigate` for the host page to act on. |
| **Trigger** | `NavigateToItem` completion. |
| **Enforcement** | The resolved target is passed to `MenuNavigate(ApplicationPath, PageLink)`, an event, rather than a direct `Response.Redirect`. |
| **Outcome** | The hosting page retains full control over how (or whether) navigation is actually performed. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-048: A malformed link ID or failed navigation is silently ignored

| Field | Value |
| --- | --- |
| **Statement** | A malformed link ID or failed navigation is silently ignored. |
| **Trigger** | `LinkButtonClick`, any exception during sequence parsing or navigation. |
| **Enforcement** | An empty `Catch` block swallows the exception. |
| **Outcome** | A malformed ID or failed lookup produces no visible error and no navigation, rather than a crash or user-facing message. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-049: The favourites configuration icon navigates to the favourite maintenance page

| Field | Value |
| --- | --- |
| **Statement** | The favourites configuration icon navigates to `MenuCache` / `frmFavoriteMaint.aspx`. |
| **Trigger** | `MenuImage_Click`, `imgFavoriteConfig` clicked. |
| **Enforcement** | `MenuNavigate("MenuCache", "frmFavoriteMaint.aspx")` is raised. |
| **Outcome** | Favourite list maintenance is delegated entirely to a separate page outside this library. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-050: The hidden default-submit button raises DefaultOnSubmit

| Field | Value |
| --- | --- |
| **Statement** | The hidden default-submit button raises `DefaultOnSubmit` so the page controls Enter-key behaviour. |
| **Trigger** | `MenuImage_Click`, `imgDefaultOnSubmit` / `imgTopDefaultOnSubmit` clicked. |
| **Enforcement** | `DefaultOnSubmit()` event is raised instead of any navigation or toolbar event. |
| **Outcome** | The hosting page defines its own Enter-key behaviour without the menu forcing a specific action. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-051: Enter in the context box raises MenuNavigate(ContextValue, ENTER)

| Field | Value |
| --- | --- |
| **Statement** | Pressing Enter in the context box raises `MenuNavigate("ContextValue", "ENTER")`. |
| **Trigger** | `m_EnterControl_Click` (hidden `EC` `LinkButton` postback). |
| **Enforcement** | The context value is stored in session, then `MenuNavigate("ContextValue", "ENTER")` is raised. |
| **Outcome** | Hosting pages can treat Enter-in-context-box as a distinct navigation intent from a menu-link click. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-052: Top page links render inside the TopMenuApp-row panel

| Field | Value |
| --- | --- |
| **Statement** | Top page links are rendered inside a `TopMenuApp-row` panel within the `topmenubox` container. |
| **Trigger** | `SAIFMenuTop.CreateChildControls`. |
| **Enforcement** | The generated `LinkButton`s are added to a `TopMenuApp-row` panel nested in the `topmenubox` navigation panel. |
| **Outcome** | Consistent DOM/CSS structure for the top link row across all hosting pages. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-053: Every toolbar icon is a registered SAIFImageButton

| Field | Value |
| --- | --- |
| **Statement** | Every toolbar icon is a `SAIFImageButton` registered in the menu-button dictionary under its control ID. |
| **Trigger** | `SAIFMenuTop.BuildMenuIcons` (`CreateChildControls`). |
| **Enforcement** | Each icon is instantiated as `SAIFImageButton`, wired to `MenuImage_Click`, and registered in the `clsMenuButton` dictionary under its control ID. |
| **Outcome** | Every toolbar action shares the same rendering/behaviour contract and can be looked up generically by ID. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-054: Toolbar button state is serialized into view state

| Field | Value |
| --- | --- |
| **Statement** | Toolbar button state is serialized into view state keyed by `ClientID` plus the logical button name. |
| **Trigger** | `MenuButtonSet` / `MenuButtonGet`. |
| **Enforcement** | `clsMenuButton` instances are persisted into `ViewState` keyed by `ClientID` plus the logical button name. |
| **Outcome** | Toolbar button visibility/enabled/tooltip state survives postbacks without re-deriving it from the authorised page every time. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-055: Design mode shows placeholder page links

| Field | Value |
| --- | --- |
| **Statement** | Design mode shows three placeholder page links. |
| **Trigger** | `BuildLinkCollection`, `DesignMode` true. |
| **Enforcement** | Three static `Page 1`–`Page 3` links are emitted instead of querying navigation data. |
| **Outcome** | The control has a representative designer appearance without a live session. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-056: Top links show only the currently selected category

| Field | Value |
| --- | --- |
| **Statement** | Top links show only the authorised items of the currently selected category, in `MenuOrder` sequence. |
| **Trigger** | `BuildLinkCollection` at run time. |
| **Enforcement** | Items are sorted by `MenuOrder`, then only `MenuAuthorizedItems.AppItemsByCategory(SelectedCategory)` is iterated. |
| **Outcome** | The top row shows only the pages belonging to the category currently selected in the left menu, in administrator-configured order. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-057: Top link IDs are prefixed top_LB plus the sequence number

| Field | Value |
| --- | --- |
| **Statement** | Top link IDs are prefixed `top_LB` plus the menu item sequence number. |
| **Trigger** | `BuildLinkCollection` link construction. |
| **Enforcement** | Each `LinkButton` ID is `top_LB<MenuItemSeqNum>`. |
| **Outcome** | `LinkButtonClick` can parse the sequence number back out to resolve the clicked item, matching the pattern used for favourites. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-058: The current page's top link is highlighted

| Field | Value |
| --- | --- |
| **Statement** | The top link for the current page is highlighted with `TopMenuApp-rowSelected`. |
| **Trigger** | `BuildLinkCollection` link construction, matching current item. |
| **Enforcement** | CSS class `TopMenuApp-rowSelected` is applied to the matching link. |
| **Outcome** | The user can see which top-row page they are currently viewing. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-059: Toolbar buttons appear only when the current page enables them

| Field | Value |
| --- | --- |
| **Statement** | Toolbar buttons appear only when the current authorised page enables them (`IsDisplayInsert`, `IsDisplaySave`, `IsDisplayDelete`, `IsDisplayPrint`, `IsDisplayDownload`, `IsDisplaySavePreferences`, `IsDisplayHelp`). |
| **Trigger** | `SAIFMenuTop.Page_PreLoad`, non-postback. |
| **Enforcement** | Each button's `Visible` / `Enabled` is set from the matching `clsMenuAuthorizedItem` flag. |
| **Outcome** | Toolbar actions not permitted for the current page are hidden and disabled, independent of the user's overall menu authorisation. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-060: Toolbar tooltips are taken from the current page's configuration

| Field | Value |
| --- | --- |
| **Statement** | Toolbar tooltips are taken from the current page's configured tooltips. |
| **Trigger** | Same `Page_PreLoad` pass. |
| **Enforcement** | Page-defined tooltip text is copied onto the print, insert, save, delete, and download buttons. |
| **Outcome** | Toolbar icons show page-specific guidance rather than a single generic tooltip. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-061: The one-click guard script is registered once per page

| Field | Value |
| --- | --- |
| **Statement** | The one-click guard script and flag variable are registered once per page. |
| **Trigger** | `BuildClientOneClickJavascript`. |
| **Enforcement** | `RegisterClientScriptBlock` guards the `mnuWasClicked` flag and `mnuOneClickOnly` function against duplicate emission. |
| **Outcome** | Multiple toolbar buttons share one client-side one-click guard without duplicating script blocks. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-062: Rendered icon image, access key and tooltip are synchronised from the model

| Field | Value |
| --- | --- |
| **Statement** | Rendered icon image, access key and tooltip are synchronised from the logical button model before rendering. |
| **Trigger** | `SAIFMenuTop.OnPreRender`. |
| **Enforcement** | For each icon with a matching `clsMenuButton`, the image URL, access key, and tooltip are updated only when they differ from the currently rendered values. |
| **Outcome** | Icon appearance stays consistent with the toolbar model even if a button's image/tooltip changed since the icon was created. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-063: Access-key tooltips are prefixed for Firefox

| Field | Value |
| --- | --- |
| **Statement** | Access-key tooltips are prefixed `Alt+`, or `Shift+Alt+` on Firefox 2 and later. |
| **Trigger** | `OnPreRender`, per toolbar icon with an access key. |
| **Enforcement** | `AccessKeyPrefix` inspects `IsFirefox` (Firefox 2+) to choose between `Alt+` and `Shift+Alt+` prefixes for the tooltip text. |
| **Outcome** | The displayed keyboard-shortcut hint matches the actual browser-specific access-key combination. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-064: Activity indicators and form-disabling apply to all toolbar icons together

| Field | Value |
| --- | --- |
| **Statement** | Activity indicators and form-disabling are applied to all toolbar icons together via `EnableButtonIndicators`. |
| **Trigger** | `OnPreRender`, per toolbar icon. |
| **Enforcement** | When `EnableButtonIndicators` is true, each icon's `EnableIndicator` / `DisableFormControlsOnClick` are turned on together. |
| **Outcome** | All toolbar icons share the same activity-indicator behaviour as a single library-wide setting. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-065: One-click buttons are guarded client-side when indicators are off

| Field | Value |
| --- | --- |
| **Statement** | When indicators are off, one-click buttons are guarded client-side by `mnuOneClickOnly`. |
| **Trigger** | `OnPreRender`, `EnableButtonIndicators` false. |
| **Enforcement** | One-click buttons receive `onclick="javascript:mnuOneClickOnly(this);"`. |
| **Outcome** | Double-submission is still guarded client-side even without the visual activity indicator. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-066: The toolbar container renders only when a button is visible

| Field | Value |
| --- | --- |
| **Statement** | The toolbar container is rendered only when at least one toolbar button is visible. |
| **Trigger** | `RenderControl`. |
| **Enforcement** | `ShowToolBar` is evaluated before opening the toolbar `div`; the container is omitted entirely when no button is visible. |
| **Outcome** | Pages with no permitted toolbar actions do not render an empty toolbar strip. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-067: Each toolbar icon renders only when its button is visible

| Field | Value |
| --- | --- |
| **Statement** | Each toolbar icon renders only when its logical button is visible. |
| **Trigger** | `RenderControl` icon emission. |
| **Enforcement** | Each icon's markup is conditioned on the logical button's `Visible` flag. |
| **Outcome** | Hidden actions produce no DOM element at all, not just a CSS-hidden one. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-068: A disabled button renders a _d image variant and a disabled control

| Field | Value |
| --- | --- |
| **Statement** | A disabled button renders a `_d`-suffixed image variant and an ASP.NET-disabled control. |
| **Trigger** | `RenderControl` / `DisableIcon`, when a button is disabled. |
| **Enforcement** | `DisableIcon` swaps in a `_d`-suffixed image variant and marks the ASP.NET control disabled. |
| **Outcome** | A visible-but-disabled action is visually distinguished (greyed icon) and cannot be clicked. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-069: Toolbar icons render in fixed groups

| Field | Value |
| --- | --- |
| **Statement** | Toolbar icons render in fixed groups: right icons, then insert/save/delete/edit/cancel/submit, then print/preview/email/download/calculate. |
| **Trigger** | `RenderControl`. |
| **Enforcement** | Icons render in the fixed order: right-hand icons, then insert/save/delete/edit/cancel/submit, then print/preview/email/download/calculate. |
| **Outcome** | Toolbar layout is identical across every hosting page regardless of which buttons are visible. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-070: A pipe separator image renders after the insert/save/delete group

| Field | Value |
| --- | --- |
| **Statement** | A pipe separator image is rendered after the insert/save/delete group. |
| **Trigger** | `RenderControl`, after the insert/save/delete/edit/cancel/submit group. |
| **Enforcement** | A pipe-separator image is rendered before the print/preview/email/download/calculate group. |
| **Outcome** | The two logical toolbar groups are visually separated. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-071: Rendering failures are reported in-page rather than raised

| Field | Value |
| --- | --- |
| **Statement** | Rendering failures are reported in-page as message, source and stack trace rather than raised. |
| **Trigger** | `RenderControl` (both controls), any unhandled exception during rendering. |
| **Enforcement** | The exception's message, source, and stack trace are caught and written directly into the rendered page rather than re-thrown. |
| **Outcome** | A rendering failure is visible to any viewer of the page, including full stack-trace detail — an information-disclosure risk in production. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb` |

---

## BR-MENU-072: Every image button carries CSS class SAIFImageButton

| Field | Value |
| --- | --- |
| **Statement** | Every image button carries CSS class `SAIFImageButton`. |
| **Trigger** | `SAIFImageButton.PreRender`. |
| **Enforcement** | The control's CSS class is set to `SAIFImageButton`. |
| **Outcome** | Consistent base styling is guaranteed for every image button instance regardless of caller configuration. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFImageButton.vb` |

---

## BR-MENU-073: Enabling the indicator switches the CSS class and publishes DisableFormControls

| Field | Value |
| --- | --- |
| **Statement** | Enabling the indicator switches the class to `SAIFImageButtonIndicator` and publishes the `DisableFormControls` attribute. |
| **Trigger** | `PreRender`, `EnableIndicator` true. |
| **Enforcement** | CSS class switches to `SAIFImageButtonIndicator` and a `DisableFormControls` attribute is published on the rendered element. |
| **Outcome** | Client-side script can identify indicator-enabled buttons and disable the form while the AJAX action is in flight. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFImageButton.vb` |

---

## BR-MENU-074: ImageButton.js is registered once per page, only when the indicator is enabled

| Field | Value |
| --- | --- |
| **Statement** | `ImageButton.js` is registered once per page and only when the indicator is enabled. |
| **Trigger** | `RegisterClientScripts`, `EnableIndicator` true. |
| **Enforcement** | `/Resources/Scripts/Controls/ImageButton.js` is registered via a per-page duplicate-registration guard. |
| **Outcome** | The indicator's client script is loaded only when needed and never duplicated on a page with multiple indicator buttons. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFImageButton.vb` |

---

## BR-MENU-075: Each toolbar icon maps to exactly one ToolBarButton value

| Field | Value |
| --- | --- |
| **Statement** | Each toolbar icon maps to exactly one `ToolBarButton` enum value raised to the host page. |
| **Trigger** | `SAIFMenuTop.MenuImage_Click`. |
| **Enforcement** | The clicked icon's ID is mapped to a single `ToolBarButton` enum value, which is passed to the raised `ToolbarClick` event. |
| **Outcome** | Host pages receive a strongly-typed, unambiguous indication of which toolbar action was invoked. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-076: The help icon raises both ToolbarClick(Help) and MenuNavigate

| Field | Value |
| --- | --- |
| **Statement** | The help icon raises both `ToolbarClick(Help)` and `MenuNavigate("", "Help")`. |
| **Trigger** | `MenuImage_Click`, `mnuHelp` clicked. |
| **Enforcement** | Both `ToolbarClick(ToolBarButton.Help)` and `MenuNavigate("", "Help")` are raised for the same click. |
| **Outcome** | Host pages can handle Help either as a toolbar action or as a navigation event, or both. |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## BR-MENU-077: Toolbar click handling does not re-check visibility or enabled state

| Field | Value |
| --- | --- |
| **Statement** | Toolbar click handling does not re-check button visibility or enabled state on the server. |
| **Trigger** | `MenuImage_Click`, any toolbar icon click. |
| **Enforcement** | Visibility/enabled guard checks exist only as commented-out code; the click handler raises its event unconditionally. |
| **Outcome** | A postback forged to target a hidden or disabled toolbar action still raises the corresponding event; the hosting page must re-validate the action server-side.* |
| **Source** | `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb` |

---

## Notes
- Mark inferred statements with `*` and unknown statements with `**`. Do not use bold inline confidence labels.
- No business rule in this library involves direct database access; the two delegated persistence paths (menu authorization data and the `SHOWAPPS` preference) are documented in [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. Statements marked with ** could not be confirmed; treat as unknown until verified.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Operations and processes](operations-processes.md)
- [Database tables](database-tables.md)
- [Security and authorization](security-authorization.md)
- [Control catalog](control-catalog.md)
- [API reference](api/index.md)

## Sources
- `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenu.vb`
- `SAIF.Presentation.Web.Controls.V2.Menu/SAIFMenuTop.vb`
- `SAIF.Presentation.Web.Controls.V2.Menu/SAIFImageButton.vb`
