Automatic Menus & Navigation
Learn how to declaratively register pages in menus and automatically hide them based on user roles.
Overview
In JopiJS, you don't manually build large sidebar menus. Instead, each page declares where it belongs in the navigation. JopiJS then aggregates this data and, most importantly, filters it automatically based on the user's permissions.
This system provides:
- Declarative Registration: Link a page to a menu directly from its route folder.
- Automatic RBAC: If a user is not authorized to see a page (via Marker Files), the menu entry won't even exist for them.
- Modular Menus: Modules can add items to existing menus without touching the core layout.
Installation
The automatic menu system requires the installation of the @jopijs/jopimod_menu module.
npm install @jopijs/jopimod_menu --save
# After install, remember to sync virtual paths
npm run postinstall1. Registering a Page in a Menu
To add a page to a menu, you use a config.ts file inside the route folder. This file allows you to specify the navigation path (including submenus).
Project Structure:
src/ mod_catalog/
└── @routes/
└── admin/
└── add-product/
├── page.tsx
├── needRole_admin.cond # <--- Visibility is tied to this!
└── config.ts # <--- Registration logicThe Configuration Logic (config.ts)
You use the config object to "push" your page into one or more menus.
import { JopiRouteConfig } from "jopijs";
export default function (config: JopiRouteConfig) {
// Add to the sidebar under "Inventory" > "New Product"
config.menu_addToLeftMenu(["Inventory", "New Product"]);
// You can also add it to other menus simultaneously
config.menu_addToTopMenu(["Direct Actions", "Add Product"]);
}2. Automatic Role Filtering
The magic of JopiJS navigation is that it is secure by default.
If your route folder contains a marker file like needRole_admin.cond:
- Server-side: The route is protected at the API/Page level.
- Client-side: The menu system automatically checks if the current user has the
adminrole. - Result: If the user lacks the role, the menu item is purged from the data returned to the UI.
Zero Boilerplate: You never have to write if (user.isAdmin) { ... } in your navigation code. JopiJS handles the lookup and filtering before your component even renders.
3. Consuming the Menu Data
To display the menu in your layout, use the useMenu hook provided by the @jopijs/jopimod_menu module.
The Menu Data Structure
The hook returns an array of objects implementing the MenuItem interface:
export interface MenuItem {
key: string; // The unique key for the menu entry
items?: MenuItem[]; // Submenu items (recursive)
isActive?: boolean; // True if the current URL matches this item
title?: string; // The display name
url?: string; // The link destination
icon?: React.FC<any>; // An optional React component for the icon
// If you want to customize the breadcrumb informations.
breadcrumb?: string[] | React.FC<any>;
[key: string]: any; // Catch-all for extra metadata
}React Implementation Example
Here is how you can render a simple sidebar. Note that menus can be recursive (using the items property).
import useMenu from "@hooks/jopijs.menu.useMenu";
import MenuNames from "@/lib/jopijs.menu.MenuNames";
export default function Sidebar() {
const menu = useMenu(MenuNames.LEFT_MENU);
return (
<aside>
{menu.map(item => (
<div key={item.key} className={item.isActive ? "active" : ""}>
{item.url ? (
<a href={item.url}>{item.title}</a>
) : (
<span>{item.title}</span>
)}
{/* Rendering submenus if they exist */}
{item.items && (
<ul className="submenu">
{item.items.map(sub => (
<li key={sub.key}>
<a href={sub.url}>{sub.title}</a>
</li>
))}
</ul>
)}
</div>
))}
</aside>
);
}4. Configuration Overrides
When a module overrides a route folder, it can also override the configuration.
- Total Replace: If your overriding folder contains a
config.tsand has ahigh.prioritymarker, it completely replaces the original configuration. - Inheritance: If you override a page but don't provide a
config.ts, the original module's configuration is preserved.
Example: Moving a core page to a different menu
src/ mod_rebranding/
└── @routes/
└── admin/ add-product/
├── config.ts # New menu paths
└── high.priority # Ensures this config winsSummary
- Use
config.tsto register routes in a menu hierarchy. - Navigation visibility is automatically synchronized with your Role-Based Access Control.
- Use
useMenuto retrieve data that is already filtered for the current user's roles. - This creates a truly modular system where menus grow and shrink automatically as modules are added or removed.