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.
Any update on this?
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;
}
}
Hi,
So you can disregard this last comment as I tracked down the logic and see that I needed to create my own ToolbarContributor that specificaly look at the LeptonXThemeBlazorOptions and factor in my DynamicLayoutPicker in order to add the SideMenuUserMenu component to the ToolBar.Items collection.
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;
}
}
Changes to the Blazor ABP Module class
Configure<AbpToolbarOptions>(options =>
{
options.Contributors.Add(new LeptonXThemeToolbarContributor());
options.Contributors.Add(new StructureToolbarContributor());
//options.Contributors.Add(new MessageToolBarContributor());
});
Thanks
Ok, so now it appears all the pages that still use the standard ABP SideMenuLayout layout are not adding the MainHeaderToolbarUserMenu component to upper right corner of the page.
Here is my code
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)
{
Console.WriteLine($"***** CURRENT URL: {location} *****");
if (location.Contains("tenant-procedure-list"))
{
CurrentLayout = typeof(ProceduresPageLayout);
Console.WriteLine($"***** CURRENT Layout: ProceduresPageLayout *****");
}
else if (location.Contains("SampleTenantDefault"))
{
CurrentLayout = typeof(TenantDefaultLayout);
Console.WriteLine($"***** CURRENT Layout: TenantDefaultLayout *****");
}
else
{
CurrentLayout = typeof(SideMenuLayout);
Console.WriteLine($"***** CURRENT Layout: SideMenuLayout *****");
}
StateHasChanged();
}
}
BlazorModule.cs
Configure<LeptonXThemeBlazorOptions>(options =>
{
//options.Layout = LeptonXBlazorLayouts.SideMenu;
options.Layout = typeof(DynamicLayoutPicker);
});
<br> <br> <br> <br> <br> <br> <br>
Ok. Thank you! That solution worked as how I needed.
Ok. Thank you! That solution worked as how I needed.
This does not allow me to have some pages in my site to use the new application layout and the rest use the standard leptonx application layout. This globally replaces one layout with another and that’s not what I want to do. I need to define a new layout and have a couple of pages use the new layout and the rest the standard one
When I implemented the suggestion for this ticket (https://abp.io/support/questions/7615/Different-Layouts-for-Admin-Pages-vs-Blazor-Application-Pages#answer-3a142526-dea6-14ec-9136-67f84bcec9bd), the application just freezes. If I put a console write statement in the code I see that this method just gets called thousands of times hence why it appears to be frozen.
How can I fix this?
@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout
@using Volo.Abp.DependencyInjection
@inherits SideMenuLayout
@attribute [ExposeServices(typeof(SideMenuLayout))]
@attribute [Dependency(ReplaceServices = true)]
<LayoutView Layout="GetLayout()">
@Body
</LayoutView>
@code
{
[Inject] private NavigationManager _navManager { get; set; }
private Type GetLayout()
{
var currentUrl = _navManager.Uri;
//Console.WriteLine($"***** CURRENT URL: {currentUrl} *****");
// if (currentUrl == "xxx")
//{
// return typeof(AdminLayout);
// }else if (currentUrl == xxxx)
// {
// return typeof(ApplicationLayout);
// }
return typeof(SideMenuLayout);
}
}
We are using Blazor WASM and for our application we have unique layout requirements for some of our application pages that are not in line with the 3 layouts available(Account, Application, Empty) in LeptonX. I have tried creating a new layout page in the Blazor WASM app and then setting the @Layout NewDefaultLayout at the top of the page I want to use that layout, but I am experiencing an issue with all the css loading several times as I navigate through my side including going to pages of ABP created modules.
What is the best/only way to create new base layouts that I can utilize ABP components in (<MainMenu>, <PageAlert>, <UiNotificationAlert>, etc) that gives me the flexibility to create unique base layouts that differ from LeptonX but that utilizes all the underlying functionality that an LeptonX layout give me, such as localizations.
Can you give me a practical example of how this can be done?