Open Closed

User Menu Click not working when loading layout dynamically #8851


User avatar
0
Spospisil created
  • ABP Framework version: v7.3.0

  • UI Type: Blazor WASM

  • Database System: EF Core (PostgreSQL)

  • Tiered (for MVC) or Auth Server Separated (for Angular): yes

I had previously created a ticket on loading a pages layout dynamically which is now working for me, however when set the layout to the default 'SideMenuLayout' the user menu in the upper right does not seem to be able to be clicked on. I have experimented with overriding the OnAferRenderAsync of the SideMenuLayout class and when invoke 'initleptonX' and 'AfterLeptonXInitialization' functions every time that seems to fix the click issue, but then it causes the stylesheets to load multiple times.

I have 3 different layouts that I switch between (see DynamicLayoutPicker.razor.cs), so I have the same OnAfterRenderAsync method on my other two layouts defined the same way I have the SideMenuLayout.cs

Any idea how I can resolve this?

DynamicLayoutPicker.razor

@inherits LayoutComponentBase

<LayoutView Layout="@CurrentLayout">
    @Body
</LayoutView>

DynamicLayoutPicker.razor.cs

using CFDataSystems.StructureCloud.Blazor.Themes.LeptonX.Layouts.TenantLayout;
using CFDataSystems.StructureCloud.Components.Layouts;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using System.Threading.Tasks;
using System;
using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout;

namespace CFDataSystems.StructureCloud.Blazor.Themes.LeptonX.Layouts;

public partial class DynamicLayoutPicker : LayoutComponentBase, IDisposable
{
    [Inject] private NavigationManager _navManager { get; set; }
    public Type CurrentLayout { get; set; } = typeof(SideMenuLayout);

    protected override Task OnInitializedAsync()
    {
        _navManager.LocationChanged += OnLocationChanged;
        SetLayout(_navManager.Uri);
        return base.OnInitializedAsync();
    }

    public void Dispose()
    {
        _navManager.LocationChanged -= OnLocationChanged;
    }

    private void OnLocationChanged(object sender, LocationChangedEventArgs e)
    {
        SetLayout(e.Location);
    }

    private void SetLayout(string location)
    {
        if (location.Contains("tenant-procedure-list"))
        {
            CurrentLayout = typeof(ProceduresPageLayout);
       }
        else if (location.Contains("SampleTenantDefault"))
        {
            CurrentLayout = typeof(TenantDefaultLayout);
        }
        else
        {
            CurrentLayout = typeof(SideMenuLayout);
        }

        StateHasChanged();
    }
}

ToolbarContributor.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Threading.Tasks;
using System;
using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme;
using Volo.Abp.AspNetCore.Components.Web.Theming.Toolbars;
using SideMenuUserMenu = Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXTheme.Components.ApplicationLayout.SideMenu.MainHeader.MainHeaderToolbarUserMenu;
using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout;
using CFDataSystems.StructureCloud.Blazor.Themes.LeptonX.Layouts;

namespace CFDataSystems.StructureCloud.Blazor.Toolbars;


public class StructureToolbarContributor : IToolbarContributor
{
    public Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
    {
        if (context.Toolbar.Name == StandardToolbars.Main)
        {
            var options = context.ServiceProvider.GetRequiredService<IOptions<LeptonXThemeBlazorOptions>>().Value;

            if (options.Layout == typeof(DynamicLayoutPicker))
            {
                context.Toolbar.Items.Add(new ToolbarItem(typeof(SideMenuUserMenu)));
            }

        }
        return Task.CompletedTask;
    }
}

Abp Module Setting Toolbar options

        Configure<AbpToolbarOptions>(options =>
        {
            //Add the standard toolbar contributors as we are using the leptonX theme
            options.Contributors.Add(new LeptonXThemeToolbarContributor());

            //Add our own toolbar contributor as we are using the DynamicLayoutPicker component to dynamically change the layout
            //based on specific changes and we need to explicitly add the TopMenuUserMenu component to the toolbar.items collection since
            //the base LeptonX theme toolbar contributor does not factor in out custom layout.
            var layoutOptions = context.Services.GetRequiredService<IOptions<LeptonXThemeBlazorOptions>>().Value;
            if (layoutOptions.Layout == typeof(DynamicLayoutPicker))
            {
                options.Contributors.Add(new StructureToolbarContributor());
            }   
            //This is going to be used to receiving the messages coming from cobol to fire off popup's etc coming from
            //the Acu2Web pages
            //options.Contributors.Add(new MessageToolBarContributor());
        });
        

    private void ConfigureTheme()
    {
        Configure<LeptonXThemeBlazorOptions>(options =>
        {
            //options.Layout = LeptonXBlazorLayouts.SideMenu;
            options.Layout = typeof(DynamicLayoutPicker);
        });

        //https://abp.io/docs/latest/ui-themes/lepton-x/blazor?UI=Blazor#customization
        Configure<LeptonXThemeOptions>(options =>
        {
            options.Styles.Add("structurecloud",
                    new LeptonXThemeStyle(
                    LocalizableString.Create<StructureCloudResource>("Theme:StructueCloud"),
            "bi bi-circle-fill"));

            options.Styles.Remove(LeptonXStyleNames.System);
            options.Styles.Remove(LeptonXStyleNames.Light);
            options.Styles.Remove(LeptonXStyleNames.Dark);
            options.Styles.Remove(LeptonXStyleNames.Dim);


            options.DefaultStyle = "structurecloud";

        });
    }

SideMenuLayoutOverride.cs (I seem to have to do this to prevent the css stylesheets from loading muliple times)

using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web;
using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout;
using Volo.Abp.DependencyInjection;
using Volo.Abp.LeptonX.Shared;

namespace CFDataSystems.StructureCloud.Blazor.Themes.LeptonX.Layouts.TenantLayout;

[ExposeServices(typeof(SideMenuLayout))]
[Dependency(ReplaceServices = true)]
public partial class SideMenuLayoutOverride : SideMenuLayout
{

    [Inject]
    IJSRuntime JSRuntime { get; set; }


    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        var bExists = await UtilsService.HasClassOnTagAsync("body", "lpx-theme-" + Options.Value.DefaultStyle);

        if (!bExists)
        {
            await UtilsService.AddClassToTagAsync("body", GetBodyClassName());
            await JSRuntime.InvokeVoidAsync("initLeptonX", new[] { "side-menu", Options.Value.DefaultStyle });
            await JSRuntime.InvokeVoidAsync("afterLeptonXInitialization", new[] { "side-menu", Options.Value.DefaultStyle });
        }
    }

    private string GetBodyClassName()
    {
        return "lpx-theme-" + Options.Value.DefaultStyle;
    }
}


14 Answer(s)
  • User Avatar
    0
    Spospisil created

    Any update on this?

  • User Avatar
    0
    berkansasmaz created
    Support Team .NET Developer

    Hi,

    I had difficulty understanding your question, but if I understand correctly, we can try the following methods:

    Move the responsibility of calling initLeptonX and afterLeptonXInitialization to DynamicLayoutPicker, triggering it only when the layout changes like below:

    namespace CFDataSystems.StructureCloud.Blazor.Themes.LeptonX.Layouts;
    
    public partial class DynamicLayoutPicker : LayoutComponentBase, IDisposable
    {
        [Inject] private NavigationManager _navManager { get; set; }
        [Inject] private IJSRuntime JSRuntime { get; set; }
    
        public Type CurrentLayout { get; set; } = typeof(SideMenuLayout);
        private Type _previousLayout = null;
    
        protected override Task OnInitializedAsync()
        {
            _navManager.LocationChanged += OnLocationChanged;
            SetLayout(_navManager.Uri);
            return base.OnInitializedAsync();
        }
    
        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender || _previousLayout != CurrentLayout)
            {
                // Reinitialize LeptonX only when layout changes
                await JSRuntime.InvokeVoidAsync("initLeptonX", new[] { "side-menu", "structurecloud" });
                await JSRuntime.InvokeVoidAsync("afterLeptonXInitialization", new[] { "side-menu", "structurecloud" });
                _previousLayout = CurrentLayout;
            }
        }
    
        public void Dispose()
        {
            _navManager.LocationChanged -= OnLocationChanged;
        }
    
        private void OnLocationChanged(object sender, LocationChangedEventArgs e)
        {
            SetLayout(e.Location);
        }
    
        private void SetLayout(string location)
        {
            if (location.Contains("tenant-procedure-list"))
            {
                CurrentLayout = typeof(ProceduresPageLayout);
            }
            else if (location.Contains("SampleTenantDefault"))
            {
                CurrentLayout = typeof(TenantDefaultLayout);
            }
            else
            {
                CurrentLayout = typeof(SideMenuLayout);
            }
            StateHasChanged();
        }
    }
    
    

    If this does not solve your problem, I can help better if you can provide a minimal reproducible example where I can replicate the problem.

  • User Avatar
    0
    Spospisil created

    Hi,

    I have created a private repo on github for you to view, however currently it's not working/running as typically a generated solution using abp tools does not work without coding changes. I have made my specific changes to demostrate the issue but can't really go any further with it until you tell me why I can run this solution that was produced by abp studio.

    You should have received an invite to the repo.

  • User Avatar
    0
    Spospisil created

    Any update?

  • User Avatar
    0
    berkansasmaz created
    Support Team .NET Developer

    Hi,

    I can run it properly, I didn't have any problems. I just changed the database connection strings I made. Then I ran DbMigrator. After the database was created, I ran other projects. What kind of error do you get when running?

    Screenshot 2025-02-25 at 13.22.39.png

    Screenshot 2025-02-25 at 13.22.44.png

  • User Avatar
    0
    Spospisil created

    Hi,

    For starters change the .RequiredPermissions on sample1, sample2 and sample3 in the menu contribution to host

            context.Menu.AddItem(
                new ApplicationMenuItem(
                    "Sample1",
                    "Sample1",
                    "/Sample1",
                    icon: "fa fa-chart-line",
                    order: 3
                ).RequirePermissions(ABPSamplePermissions.Dashboard.Host)
            );
    
            context.Menu.AddItem(
                new ApplicationMenuItem(
                    "Sample2",
                    "Sample2",
                    "/Sample2",
                    icon: "fa fa-chart-line",
                    order: 4
                ).RequirePermissions(ABPSamplePermissions.Dashboard.Host)
            );
    
            context.Menu.AddItem(
                new ApplicationMenuItem(
                    "Sample3",
                    "Sample3",
                    "/Sample3",
                    icon: "fa fa-chart-line",
                    order: 5
                ).RequirePermissions(ABPSamplePermissions.Dashboard.Host)
            );
    

    and then start gong to them. you'll see the css stylesheet files bein loaded several times.

    image.png

  • User Avatar
    0
    Spospisil created

    I've updated the solution in git for you

  • User Avatar
    0
    Spospisil created

    Hi,

    So the sample has been updated completely now it Git to show you the problems I'm having.

    1. The CSS styles are loading multiple times as you navigate from Sample1, 2 and 3.

    2. On the Sample3 page (the one that uses ABP's SideMenuLayout component), the user menu in the upper right corner of the page does not work.

    Thanks.

  • User Avatar
    0
    berkansasmaz created
    Support Team .NET Developer

    Hello,

    I sent a commit that solved the problem. Basically what we did was to call the following code block when SideMenuLayout loading.

        leptonx.init.initializers.get('initLpxToolbar')();
    
  • User Avatar
    0
    Spospisil created

    Hi,

    You have not address the issue with the stylesheets loading multiple times as stated in bullet point #1 in my previous response. Additionally when I apply these fixes to my actual project which is based on ABP 7.3.0-rc.3 I get the following error when my blazor app launches.

    image.png

  • User Avatar
    0
    berkansasmaz created
    Support Team .NET Developer

    Hi,

    Yes, I missed the problem of repeating CSS's. I sent a commit again. Can you check it?

  • User Avatar
    0
    Spospisil created

    checking it now, but any resolution to the js function that does not exist in 7.3.0-rc.3?

  • User Avatar
    0
    Spospisil created

    Hi,

    Ok, I've implemented the changes in your last commit and that has resolved the issue I'm having with the stylesheets loading multiple times as well as the user menu not being clickable. I appreciate your help in resolving this issue.

    Curious though if with abp9+ there is a better way of implementing custom layouts on a page by page basis while utilizing all the underlying leptonx components, etc. We have purchased the full source code and I see abp developer comments indicating that there should be a better way of doing this in the future.

    Thanks again.

  • User Avatar
    0
    berkansasmaz created
    Support Team .NET Developer

    Hi,

    I'm very glad that the problem is solved. Yes, we can make it even easier in the future. As you can see, we have related issues, but it is not among our priorities at the moment because there is already a way.


    Closing the issue. Feel free to create a new issue if you have further questions.

Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
Do you need assistance from an ABP expert?
Schedule a Meeting
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v9.2.0-preview. Updated on March 13, 2025, 04:08