Open Closed

After upgrading from ABP 10.1.1 to 10.3.0, parent menu (with sub-items) randomly doesn't open when clicked #10671


User avatar
0
rferrarin created

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

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

4 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • 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-sidebar exists 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: menuItems is empty until you manually call createMenuItems() / re-init.

    • Your TelerikRootComponent placement 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.

    1. 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();
      }
    })();
    
    1. 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), initSideMenu runs 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)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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, BaseSideMenu binds click handlers only once, in its constructor. ABP's IMenuContributor is async, so the sidebar <ul>/<li> DOM may appear after initSideMenu already ran (or get replaced later when Blazor re-renders the sidebar, e.g. on impersonation / permission refresh). When that happens menuItems ends up empty — exactly what your DevTools shows. Calling createMenuItems() 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 a data-lpx-bound marker) and adds a MutationObserver on #lpx-sidebar that 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 TelerikSideMenuLayout

    Not the cause of this bug — your override inherits OnAfterRenderAsync from SideMenuLayout and keeps #lpx-sidebar intact, 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 of App.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)
  • User Avatar
    0
    rferrarin created

    thank you!

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    : )

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 16, 2026, 14:50
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.