Skip to content

Navigation

Genie renders the application’s left navigation from a single *.navbar.xml model. You describe the tree once; the engine stores it, filters it per user, computes its badges, and ships the React UI a ready-to-render JSON tree. The server never trusts the client to decide what a user may see.

A *.navbar.xml file is a tree of four node kinds:

<Navbar>
<Item Name="Home" Label="Home" Icon="fa fa-house" Route="/" />
<Module Name="catalog" Label="Catalog" Icon="fa fa-box-open" Route="/table/products">
<Section Label="Master Data">
<Item Name="products" Label="Products" Icon="fa fa-box" Route="/table/products" />
<Item Name="categories" Label="Categories" Route="/table/categories" />
</Section>
<Divider />
<Section Label="Reports">
<Item Name="low-stock-report" Label="Low Stock" Route="/table/low-stock-report">
<CountsBadge Color="danger">SELECT COUNT(*) FROM [Inventory].[vw_LowStock]</CountsBadge>
</Item>
</Section>
</Module>
<Module Name="admin" Label="Admin" Icon="fa fa-gear" VisibleIf="User.IsInRole('Admin')"></Module>
<Module Name="docs" Label="Docs" Icon="fa fa-book" Route="https://github.com" Target="_blank" />
</Navbar>
Kind Role
<Module> a top-level rail entry; may itself be a link and hold children
<Section> a non-clickable grouping heading; hidden when it has no visible children
<Item> a navigable link (Route); may nest child <Item>s
<Divider> a decorative separator

Routes are client-side hashes/paths that map to the UI’s GenieRouter/table/{slug}, /view/{slug}, /form/{slug}, /wizard/{slug}, or /. An external link uses an absolute URL plus Target="_blank".

At model migration the whole tree is synced into a single NavbarDefinition row (DefinitionJson) named default. The sync is a full replace — the XML is the source of truth, so removing a node from the file removes it from the navbar on the next migration. Keep one *.navbar.xml per app.

GET /api/v1/genie/navbar returns only what the authenticated user is allowed to see. NavbarService loads the stored tree, then NavbarFilter prunes it against the user’s permissions:

  • A leaf link is visible when the user holds List or Execute on the resource its route targets (the resource name is extracted from the /table|form|view|wizard/{name} route). Closed by default — an unknown resource is hidden.
  • A container (Module, or an Item with children) is visible only if it has at least one visible descendant.
  • A Section is visible only with at least one visible child.
  • Dividers are kept, then leading, trailing and consecutive dividers are pruned so no dangling separators remain.
  • A super-admin sees everything.

VisibleIf on a node is a server-evaluated role/expression guard (e.g. User.IsInRole('Admin')) applied on top of the permission rules.

An <Item> may carry one badge whose value is computed server-side from a scalar SELECT:

  • <CountsBadge> — a numeric count (e.g. pending records).
  • <BulletsBadge> — an indicator dot driven by a count.
<Item Name="purchase-orders" Label="Purchase Orders" Route="/table/purchase-orders">
<BulletsBadge Color="info">
SELECT COUNT(*) FROM [Inventory].[PurchaseOrders] WHERE Status = 'Submitted' AND IsDeleted = 0
</BulletsBadge>
</Item>

NavbarService runs each visible link’s badge query with the current session parameters (@SessionUserId, @SessionCompanyId, …) and ships only the computed number and colour — the SQL never leaves the server. A badge query that fails is logged and dropped; it never breaks the navbar.

Method Route Returns
GET /api/v1/genie/navbar SuccessDataResult<List<NavbarItem>> — the filtered, badged tree

Each NavbarItem carries Name, Kind, Label, Icon, Route, Target, Order, Children, and an optional Badge ({ Kind, Value, Color }). The UI’s GenieNavbar renders it directly.

An item’s Name is also the key a view’s <ParentView> maps to for its breadcrumb root. Worked example: sample/Inventory/models/navbar/inventory.navbar.xml.