We are running ABP Commercial 10.3.0 with LeptonX Blazor Server theme. After upgrading from 10.1.1 → 10.3.0, menu items with children (randomly) no longer expand on click
Verified via DevTools:
- window.leptonx.sideMenu.menuItems → Array(0) ← broken
- Manually calling window.leptonx.sideMenu.createMenuItems() → 12 items, menu works correctly
What I think migth be the proble is: if LeptonX JS loads slowly (CDN cache miss) and completes after the interactive enhancement, createMenuItems() finds the final DOM → works. If it loads fast (CDN cache hit) and completes before → handlers land on replaced nodes → broken.
Another hint, we override SideMenuLayout with a custom TelerikSideMenuLayout that wraps @Body inside :
@inherits SideMenuLayout
@attribute [ExposeServices(typeof(SideMenuLayout))]
@attribute [Dependency(ReplaceServices = true)]
<div>
<div id="lpx-wrapper" class="@MenuStateClass">
<MainHeader />
<div class="lpx-content-container">
<div class="lpx-topbar-container">
<div class="lpx-topbar">
<Breadcrumbs />
</div>
</div>
<div class="lpx-content-wrapper">
<div class="lpx-content">
<ContentToolbar />
<PageAlert />
<UiPageProgress />
<LayoutHook Name="@LayoutHooks.Body.First" Layout="@StandardLayouts.Application" />
<TelerikRootComponent>
@Body
</TelerikRootComponent>
<LayoutHook Name="@LayoutHooks.Body.Last" Layout="@StandardLayouts.Application" />
<DynamicLayoutComponent />
<UiMessageAlert />
<UiNotificationAlert />
</div>
</div>
<footer>
<Footer />
</footer>
</div>
<MobileNavbar />
<div class="lpx-toolbar-container">
<MainHeaderToolbar />
</div>
</div>
</div>
Is this the correct way? Is SideMenuLayout designed to be compatible with <TelerikRootComponent> nested inside it, or should Telerik integration always be placed above the layout (Routes.razor)? Do you have any hint about what it could be the cause of this (known) problem? Everything was warking fine till 10.1.1
4 Answer(s)
-
0
This is a known timing/regression issue in LeptonX 5.3.x (ABP 10.3.x): the side-menu JS initializer (
initSideMenu) can run when#lpx-sidebarexists but before the real menu items are rendered (or after Blazor later replaces them, e.g., on app configuration changes / impersonation). In that case, LeptonX binds zero click handlers, so parent items don’t expand. Your DevTools observation matches this exactly:menuItemsis empty until you manually callcreateMenuItems()/ re-init.Your
TelerikRootComponentplacement is not the root cause. The issue is that LeptonX side menu binding is done once on first render, and if the DOM changes later, handlers can be lost. (So moving Telerik “above Routes” is not required just for this.)
Workaround (until you update to a LeptonX patch that fixes it)
Add a small script that re-runs only the side-menu initializer once the sidebar items appear (or re-appear) after Blazor updates the DOM.
- Create
wwwroot/lpx-sidebar-rebind-polyfill.js(Server UI project that serves_Host.cshtml/ static files):
(function () { 'use strict'; const DONE_ATTR = 'data-lpx-polyfill-rebound'; function tryRebind() { const sidebar = document.querySelector('#lpx-sidebar'); if (!sidebar) return false; // Wait until real items exist if (!sidebar.querySelector('.outer-menu-item > .lpx-menu-item')) return false; // Prevent endless re-binding loops if (sidebar.hasAttribute(DONE_ATTR)) return true; sidebar.setAttribute(DONE_ATTR, ''); const 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(); // binds expand/collapse handlers return true; } return false; } function start() { if (tryRebind()) return; const obs = new MutationObserver(() => { if (tryRebind()) obs.disconnect(); }); obs.observe(document.body, { childList: true, subtree: true }); // safety stop setTimeout(() => obs.disconnect(), 30000); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', start); } else { start(); } })();- Reference it after the LeptonX global script bundle in
_Host.cshtml:
<abp-script-bundle name="...LeptonX...Global" /> <script src="~/lpx-sidebar-rebind-polyfill.js"></script>This ensures that when the menu DOM is finally present (or re-rendered),
initSideMenuruns against the final nodes and the parent items expand reliably.Sources
- https://abp.io/support/questions/10622
- https://abp.io/support/questions/10642
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,
This is a known regression in LeptonX 5.3.x (Blazor) and we already shipped a fix in ABP 10.4.0 / LeptonX 5.4.0 (released a few days ago). The simplest path forward is to upgrade.
What's going on
In 5.3.x,
BaseSideMenubinds click handlers only once, in its constructor. ABP'sIMenuContributoris async, so the sidebar<ul>/<li>DOM may appear afterinitSideMenualready ran (or get replaced later when Blazor re-renders the sidebar, e.g. on impersonation / permission refresh). When that happensmenuItemsends up empty — exactly what your DevTools shows. CallingcreateMenuItems()manually rebinds against the final DOM, which is why your manual test works.So your "CDN cache miss vs hit" observation is correct about direction (timing-related), but the root cause is the single-shot binding, not network speed. CDN speed just changes which side of the race you land on.
Fix — upgrade to ABP 10.4.0
5.4.0 makes
createMenuItems()idempotent (skips items already bound via adata-lpx-boundmarker) and adds aMutationObserveron#lpx-sidebarthat re-binds whenever the menu DOM changes. After upgrading you don't need any extra script.Related cases with the same root cause:
- https://abp.io/support/questions/10622
- https://abp.io/support/questions/10642
Workaround if you can't upgrade right now
Add a small script in your Blazor Server project (e.g.
wwwroot/lpx-sidemenu-rebind.js):(function () { const sidebar = document.querySelector('#lpx-sidebar'); if (!sidebar) return; function rebind() { if (window.leptonx?.sideMenu?.createMenuItems) { // Clear so we don't push duplicates into menuItems[] window.leptonx.sideMenu.menuItems = []; window.leptonx.sideMenu.createMenuItems(); } } const observer = new MutationObserver(() => { if (sidebar.querySelector('.outer-menu-item > .lpx-menu-item')) { rebind(); } }); observer.observe(sidebar, { childList: true, subtree: true }); rebind(); })();Reference it after the LeptonX bundle in your host page:
<abp-script-bundle name="@typeof(Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Bundling.BlazorLeptonXThemeBundles.Scripts.Global).FullName" /> <script src="~/lpx-sidemenu-rebind.js"></script>This is essentially the same approach we took in 5.4.0, just done from your project.
About your
TelerikSideMenuLayoutNot the cause of this bug — your override inherits
OnAfterRenderAsyncfromSideMenuLayoutand keeps#lpx-sidebarintact, so LeptonX init still runs the same way. You can keep it as-is for this issue.One unrelated note though: Telerik recommends placing
<TelerikRootComponent>at the top ofApp.razor/Routes.razor(wrapping<Routes>), not inside a layout around@Body. Putting it inside the layout means it gets remounted on every navigation and can cause subtle problems with Telerik popups/dialogs/portals later. Worth fixing when convenient, but it's not related to your menu issue.Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)