Hi,
I need to add ItemMenu and SubItemMenu dynamically through AppService.
The main menu documentsMenu appears only collapsed and all the main menus (e.g., SaaS, Administration, etc.) are only collapsed and cannot be expanded.
The generated HTML seems correct, however, the SubItemMenus exist.
How can I resolve this?
Thanks
This is code:
// Documenti - albero con classi documentali come foglie
var documentsMenu = new ApplicationMenuItem(
Suite4SolutionsMenus.Documents,
l["Menu:Documents"],
icon: "fa fa-paperclip");
try
{
if (await context.IsGrantedAsync(Suite4SolutionsPermissions.Documents.Default))
{
var documentMenuState = context.ServiceProvider.GetRequiredService<DocumentMenuState>();
await documentMenuState.EnsureLoadedAsync();
foreach (var dc in documentMenuState.DocumentClasses)
{
documentsMenu.AddItem(new ApplicationMenuItem(
Suite4SolutionsMenus.Documents + "." + dc.Id.ToString("N"),
dc.Name,
url: $"/documents/{dc.Id}",
icon: "fa fa-file-alt",
requiredPermissionName: Suite4SolutionsPermissions.Documents.Default));
}
}
}
catch
{
// Se la chiamata API fallisce, mostra solo il menù padre
}
context.Menu.AddItem(documentsMenu);
2 Answer(s)
-
0
In ABP Blazor (including WASM) the expand/collapse behavior of parent menu items is purely client-side (handled by the theme’s Blazor/JS code). If the menu items are added/changed after the side menu has already been initialized (e.g., after an async call like
EnsureLoadedAsync()), the DOM gets updated but the theme’s “bind click handlers / initialize side-menu” step won’t automatically re-run, so parent items stay collapsed and not expandable.Fix: Re-initialize the LeptonX side menu after you dynamically build the menu.
- Create a small JS function in your Blazor WASM
wwwroot/index.html(or a JS file included by it) to re-run LeptonX’s side-menu initializer if it exists:
- Create a small JS function in your Blazor WASM
<script> window.reinitLeptonXSideMenu = function () { const fn = window.leptonx && window.leptonx.init && window.leptonx.init.initializers && window.leptonx.init.initializers.get('initSideMenu'); if (fn) { fn(); // rebind expand/collapse handlers } }; </script>- Call that JS method after the menu is loaded/changed on the client. The clean ABP way is to do it from a component that runs after render (e.g., your main layout or a component that hosts the menu) after the menu state changes:
@inject IJSRuntime JS protected override async Task OnAfterRenderAsync(bool firstRender) { // If your menu can change later (async), you can also call this whenever it changes. await JS.InvokeVoidAsync("reinitLeptonXSideMenu"); }If you have a specific place where you know the menu has just been updated (e.g., after your state/service finishes loading), call it there too.Important: Do not try to build menus from an AppService at runtime expecting the UI to “just expand” without re-init. ABP’s
IMenuContributoris designed for building the menu, but the theme initialization still must see the final DOM to wire expand/collapse.Your
ApplicationMenuItemcode is fine. The symptom you describe (HTML contains sub-items but groups can’t expand) matches exactly “menu JS init ran before items existed”.
Links (verified):
- https://abp.io/docs/latest/framework/ui/blazor/navigation-menu
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
Thanks for reporting this. The "collapse but not expanding" behavior you see (and the fact that it also breaks unrelated menus like SaaS / Administration) is caused by a timing issue in LeptonX 5.3.x sidebar initialization, triggered when an
IMenuContributorawaits a real HTTP call.What's happening
LeptonX's
SideMenuLayoutrunsinitLeptonX(which wires up the click handlers for sidebar items) once, on its first render. Your contributor awaitsdocumentMenuState.EnsureLoadedAsync(), which goes over HTTP. While that HTTP is in flight, theMainMenuchild component hasn't rendered yet, so the menu DOM (<a class="lpx-menu-item">for each main menu) doesn't exist when LeptonX scans the page. By the time your HTTP returns and the menu items appear, nothing re-binds the click handlers — so every main menu (yours and the built-in ones) ends up collapsed and unclickable.We've already fixed this on the LeptonX side:
BaseSideMenunow uses aMutationObserveron#lpx-sidebarplus an idempotentdata-lpx-boundmarker, so menu items added after the first init get bound automatically. The fix is referenced from a related ticket https://abp.io/support/questions/10622 and will ship in the next LeptonX 5.3.x patch.Workaround until that patch is out
Drop a small JS file into your Blazor.Client
wwwroot/, e.g.lpx-sidebar-rebind-polyfill.js:(function () { 'use strict'; var DONE_ATTR = 'data-lpx-polyfill-rebound'; function rebind() { var sidebar = document.querySelector('#lpx-sidebar'); if (!sidebar || sidebar.hasAttribute(DONE_ATTR)) return false; if (!sidebar.querySelector('.outer-menu-item > .lpx-menu-item')) return false; sidebar.setAttribute(DONE_ATTR, ''); var initFn = window.leptonx && window.leptonx.init && window.leptonx.init.initializers && typeof window.leptonx.init.initializers.get === 'function' && window.leptonx.init.initializers.get('initSideMenu'); if (typeof initFn === 'function') { initFn(); return true; } return false; } function start() { if (rebind()) return; var observer = new MutationObserver(function () { if (rebind()) observer.disconnect(); }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(function () { observer.disconnect(); }, 30000); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); else start(); })();Reference it in
wwwroot/index.htmlafter the LeptonX bundle:<script src="lpx-sidebar-rebind-polyfill.js"></script>It watches for the sidebar menu DOM to appear, then triggers
initSideMenuexactly once, so the click handlers land on the real menu items. TheDONE_ATTRflag and the 30-second observer timeout keep it from double-binding on later navigations (which would otherwise cause one click to toggle twice on this LeptonX version).Two small unrelated suggestions on your contributor:
- The empty
catch { }swallows everything — considerLogger.LogException(ex)so you can actually see when the HTTP call fails. - You can drop
requiredPermissionNamefrom the child items; the parent already gates them viaIsGrantedAsync, so re-checking on each child is just wasted work.
Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) - The empty