Open Closed

Regarding the issues affecting the user experience of LeptonXTheme UI #6395


User avatar
0
327992883@qq.com created

ABP Framework version: v7.2.3

UI Type: Blazor Server

Database System: EF Core

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

Exception message and full stack trace:

Steps to reproduce the issue: seems to be related to resource release.

Hello I am using LeptonXTheme UI. I want to be able to see which pages have been opened like the picture above. Display all open tabs at the top and click on the tab above to achieve the effect of switching pages. This is what I have here. A usage habit, does LeptonXTheme UI support this function? In addition, every time I open the page and return to the previous page, the page status will not be retained. Is it possible to maintain the page status like vue keep-alive or do A short-lived storage; now I cannot use LeptonXTheme UI to achieve the above functions. Can you give me some methods or suggestions to achieve these functions?


42 Answer(s)
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    Here is the code, But there is a serious problem when deleting the tab, which may be related to blazor.zone. There is nothing I can do. so, I don't recommend you to use blazor.zone

    <BootstrapBlazor.Components.Tab @ref="_tabComponent" class="mt-3"
                                    ShowClose="true"
                                    OnClickTabItemAsync="OnSelectedTabChanged"
                                    OnCloseTabItemAsync="ClosePage">
         @foreach (var tab in RouteDataList)
        {
            <BootstrapBlazor.Components.TabItem Text="@tab.Title">
                    <ContentTabToolbar RouteData="tab"></ContentTabToolbar>
                    @tab.Body
            </BootstrapBlazor.Components.TabItem>
        }
    </BootstrapBlazor.Components.Tab>
    
    @code {
    
        private BootstrapBlazor.Components.Tab _tabComponent;
        
        [Inject]
        NavigationManager NavigationManager { get; set; }
    
        [Inject]
        PageHeaderService PageHeaderService { get; set; }
    
        [Inject]
        IMenuManager MenuManager { get; set; }
    
        [CascadingParameter(Name = "RouteData")]
        RouteData? RouteData { get; set; }
    
        List<ContentTabRouteData> RouteDataList { get; set; } = new();
    
        [Parameter]
        public RenderFragment Body { get; set; }
    
        string? CurrentUrl { get; set; }
    
        ApplicationMenu? ApplicationMenu { get; set; }
    
        protected override async Task OnInitializedAsync()
        {
            ApplicationMenu = await MenuManager.GetMainMenuAsync();
            PageHeaderService.PageHeaderDataChanged += OnPageHeaderDataChanged;
            NavigationManager.LocationChanged += OnLocationChanged;
        }
    
        protected override void OnAfterRender(bool firstRender)
        {
            if (firstRender)
            {
                OnLocationChanged(this, new LocationChangedEventArgs(NavigationManager.Uri, false));
            }
            
            base.OnAfterRender(firstRender);
        }
    
        private async void OnPageHeaderDataChanged(object? sender, PageHeaderData? e)
        {
            if (e == null)
            {
                return;
            }
            
            var route = RouteDataList.FirstOrDefault(x => x.PageType == e.Type);
            route?.SetPageLayout(e.Title, e.BreadcrumbItems, e.PageToolbarItems);
    
            await InvokeAsync(StateHasChanged);
        }
    
        private async Task OnSelectedTabChanged(BootstrapBlazor.Components.TabItem tabItem)
        {
            var route = RouteDataList.FirstOrDefault(x => x.Title == tabItem.Text);
    
            if (route != null)
            {
                CurrentUrl = route.Url;
    
                var data = PageHeaderService.GetPageHeaderData(route.PageType);
                if (data != null)
                {
                    route.SetPageLayout(data.Title, data.BreadcrumbItems, data.PageToolbarItems);
                }
             
                _tabComponent.ActiveTab(tabItem);
                
                NavigationManager.NavigateTo(route.Url);
            }
    
            await InvokeAsync(StateHasChanged);
        }
    
        private RenderFragment GenerateBody(ContentTabRouteData routeData)
        {
            return builder =>
            {
                builder.OpenComponent(0, routeData.PageType);
                foreach (var routeValue in routeData.RouteValues)
                {
                    builder.AddAttribute(1, routeValue.Key, routeValue.Value);
                }
                builder.CloseComponent();
            };
        }
    
        private async Task<bool> ClosePage(TabItem tabItem)
        {
            var routeData = GetRouteData(tabItem);
            if (routeData == null)
            {
                return true;
            }
            
            RouteDataList.Remove(routeData);
            await _tabComponent.RemoveTab(tabItem);
            
            var activeTab = _tabComponent.GetActiveTab();
            if (activeTab != null)
            {
                await OnSelectedTabChanged(activeTab);
            }
           
            return false;
            
        }
        
        private ContentTabRouteData? GetRouteData(TabItem? tabItem)
        {
            return tabItem == null ? null : RouteDataList.FirstOrDefault(x => x.Title == tabItem.Text);
        }
    
        private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
        {
            if (RouteData == null || CurrentUrl == e.Location)
            {
                return;
            }
            
            var contentTabRouteData = new ContentTabRouteData
            {
                PageType = RouteData.PageType,
                RouteValues = new Dictionary<string, object>(RouteData.RouteValues),
                TabName = RouteData.PageType.Name.ToLower(),
                Url = e.Location,
                Title = TryGetTitle(ApplicationMenu.Items, e.Location)
            };
            contentTabRouteData.Body = GenerateBody(contentTabRouteData);
            RouteDataList.AddIfNotContains(x => x.PageType == RouteData.PageType, () => contentTabRouteData);
    
            StateHasChanged();
            
            OnSelectedTabChanged(_tabComponent.Items.First(x => x.Text == contentTabRouteData.Title));
    
        }
    
        protected override void Dispose(bool disposing)
        {
            PageHeaderService.PageHeaderDataChanged -= OnPageHeaderDataChanged;
            NavigationManager.LocationChanged -= OnLocationChanged;
            base.Dispose(disposing);
        }
    
        private string? TryGetTitle(ApplicationMenuItemList items, string url)
        {
            foreach (var item in items)
            {
                if (item.Items.Any())
                {
                    var result = TryGetTitle(item.Items, url);
                    if (result != null)
                    {
                        return result;
                    }
    
                    continue;
                }
    
                if (item.Url.IsNullOrWhiteSpace())
                {
                    continue;
                }
    
                if (url.EndsWith(item.Url!.Replace("~/", "")))
                {
                    return item.DisplayName;
                }
            }
    
            // you can custom here
            return null;
        }
    }
    
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    You can consider https://mudblazor.com/components/tabs#scrolling-tabs or wait for Blazorise implements this function

  • User Avatar
    0
    327992883@qq.com created

    Hi,

    Here is the code,
    But there is a serious problem when deleting the tab, which may be related to blazor.zone. There is nothing I can do. so, I don't recommend you to use blazor.zone

    <BootstrapBlazor.Components.Tab @ref="_tabComponent" class="mt-3" 
                                    ShowClose="true" 
                                    OnClickTabItemAsync="OnSelectedTabChanged" 
                                    OnCloseTabItemAsync="ClosePage"> 
         @foreach (var tab in RouteDataList) 
        { 
            <BootstrapBlazor.Components.TabItem Text="@tab.Title"> 
                    <ContentTabToolbar RouteData="tab"></ContentTabToolbar> 
                    @tab.Body 
            </BootstrapBlazor.Components.TabItem> 
        } 
    </BootstrapBlazor.Components.Tab> 
     
    @code { 
     
        private BootstrapBlazor.Components.Tab _tabComponent; 
         
        [Inject] 
        NavigationManager NavigationManager { get; set; } 
     
        [Inject] 
        PageHeaderService PageHeaderService { get; set; } 
     
        [Inject] 
        IMenuManager MenuManager { get; set; } 
     
        [CascadingParameter(Name = "RouteData")] 
        RouteData? RouteData { get; set; } 
     
        List<ContentTabRouteData> RouteDataList { get; set; } = new(); 
     
        [Parameter] 
        public RenderFragment Body { get; set; } 
     
        string? CurrentUrl { get; set; } 
     
        ApplicationMenu? ApplicationMenu { get; set; } 
     
        protected override async Task OnInitializedAsync() 
        { 
            ApplicationMenu = await MenuManager.GetMainMenuAsync(); 
            PageHeaderService.PageHeaderDataChanged += OnPageHeaderDataChanged; 
            NavigationManager.LocationChanged += OnLocationChanged; 
        } 
     
        protected override void OnAfterRender(bool firstRender) 
        { 
            if (firstRender) 
            { 
                OnLocationChanged(this, new LocationChangedEventArgs(NavigationManager.Uri, false)); 
            } 
             
            base.OnAfterRender(firstRender); 
        } 
     
        private async void OnPageHeaderDataChanged(object? sender, PageHeaderData? e) 
        { 
            if (e == null) 
            { 
                return; 
            } 
             
            var route = RouteDataList.FirstOrDefault(x => x.PageType == e.Type); 
            route?.SetPageLayout(e.Title, e.BreadcrumbItems, e.PageToolbarItems); 
     
            await InvokeAsync(StateHasChanged); 
        } 
     
        private async Task OnSelectedTabChanged(BootstrapBlazor.Components.TabItem tabItem) 
        { 
            var route = RouteDataList.FirstOrDefault(x => x.Title == tabItem.Text); 
     
            if (route != null) 
            { 
                CurrentUrl = route.Url; 
     
                var data = PageHeaderService.GetPageHeaderData(route.PageType); 
                if (data != null) 
                { 
                    route.SetPageLayout(data.Title, data.BreadcrumbItems, data.PageToolbarItems); 
                } 
              
                _tabComponent.ActiveTab(tabItem); 
                 
                NavigationManager.NavigateTo(route.Url); 
            } 
     
            await InvokeAsync(StateHasChanged); 
        } 
     
        private RenderFragment GenerateBody(ContentTabRouteData routeData) 
        { 
            return builder => 
            { 
                builder.OpenComponent(0, routeData.PageType); 
                foreach (var routeValue in routeData.RouteValues) 
                { 
                    builder.AddAttribute(1, routeValue.Key, routeValue.Value); 
                } 
                builder.CloseComponent(); 
            }; 
        } 
     
        private async Task<bool> ClosePage(TabItem tabItem) 
        { 
            var routeData = GetRouteData(tabItem); 
            if (routeData == null) 
            { 
                return true; 
            } 
             
            RouteDataList.Remove(routeData); 
            await _tabComponent.RemoveTab(tabItem); 
             
            var activeTab = _tabComponent.GetActiveTab(); 
            if (activeTab != null) 
            { 
                await OnSelectedTabChanged(activeTab); 
            } 
            
            return false; 
             
        } 
         
        private ContentTabRouteData? GetRouteData(TabItem? tabItem) 
        { 
            return tabItem == null ? null : RouteDataList.FirstOrDefault(x => x.Title == tabItem.Text); 
        } 
     
        private void OnLocationChanged(object? sender, LocationChangedEventArgs e) 
        { 
            if (RouteData == null || CurrentUrl == e.Location) 
            { 
                return; 
            } 
             
            var contentTabRouteData = new ContentTabRouteData 
            { 
                PageType = RouteData.PageType, 
                RouteValues = new Dictionary<string, object>(RouteData.RouteValues), 
                TabName = RouteData.PageType.Name.ToLower(), 
                Url = e.Location, 
                Title = TryGetTitle(ApplicationMenu.Items, e.Location) 
            }; 
            contentTabRouteData.Body = GenerateBody(contentTabRouteData); 
            RouteDataList.AddIfNotContains(x => x.PageType == RouteData.PageType, () => contentTabRouteData); 
     
            StateHasChanged(); 
             
            OnSelectedTabChanged(_tabComponent.Items.First(x => x.Text == contentTabRouteData.Title)); 
     
        } 
     
        protected override void Dispose(bool disposing) 
        { 
            PageHeaderService.PageHeaderDataChanged -= OnPageHeaderDataChanged; 
            NavigationManager.LocationChanged -= OnLocationChanged; 
            base.Dispose(disposing); 
        } 
     
        private string? TryGetTitle(ApplicationMenuItemList items, string url) 
        { 
            foreach (var item in items) 
            { 
                if (item.Items.Any()) 
                { 
                    var result = TryGetTitle(item.Items, url); 
                    if (result != null) 
                    { 
                        return result; 
                    } 
     
                    continue; 
                } 
     
                if (item.Url.IsNullOrWhiteSpace()) 
                { 
                    continue; 
                } 
     
                if (url.EndsWith(item.Url!.Replace("~/", ""))) 
                { 
                    return item.DisplayName; 
                } 
            } 
     
            // you can custom here 
            return null; 
        } 
    } 
    

    Hi.What's the problem with closing it?

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    You can give it a try

  • User Avatar
    0
    327992883@qq.com created

    You can give it a try

    Hi,When I close other tabs, the selected tab will change to the one after the closed tab I use the tab component of blazorise

  • User Avatar
    0
    327992883@qq.com created

    Clicking the close button will trigger the SelectedTabChanged method of Tabs

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    This may be the limitation of blazorise and blazor (I'm not sure)

    Add custom class and stopPropagation: The page will flash, but it works.

    <Tab Name="@tab.TabName" Class="keep-tab-active">
        @tab.Title
        <CloseButton @onclick:stopPropagation="true" Clicked="() => ClosePage(tab)"/>
    </Tab>
    ....
    
    <TabPanel Name="@route.TabName" Class="keep-panel-active"  >
    ....
    
  • User Avatar
    0
    327992883@qq.com created

    This may be the limitation of blazorise and blazor (I'm not sure)

    Add custom class and stopPropagation: The page will flash, but it works.

    <Tab Name="@tab.TabName" Class="keep-tab-active"> 
        @tab.Title 
        <CloseButton @onclick:stopPropagation="true" Clicked="() => ClosePage(tab)"/> 
    </Tab> 
    .... 
     
    <TabPanel Name="@route.TabName" Class="keep-panel-active"  > 
    .... 
    

    Hi,This flash will clear all input boxes and other conditions,It's like refreshing

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Yes, but I have no idea about this now.

  • User Avatar
    0
    327992883@qq.com created

    Yes, but I have no idea about this now.

    Sometimes clicking the close button won't refresh, it's really weird

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Yes, it's weird

  • User Avatar
    0
    327992883@qq.com created

    Toolbar

    The Toolbar will rendered by ContentTabToolbar

    breadcrumbItems

    Yes, I removed the breadcrumbItems from layout, It is in the storage of routing data, you can show it if you need.

    If I want to showcase this breadcrumb project, how should I do it

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    Here is the code: https://github.com/abpframework/abp/blob/956706eb62dba926ecd8876a9ba4b490088b8259/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/PageHeader.razor#L16-L39

  • User Avatar
    0
    327992883@qq.com created

    Yes, but I have no idea about this now.

    It is related to Blazor's comparison algorithm. @key specifies the key used by the diffing algorithm to retain the elements in the set. I have solved this problem, thank you.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Can you share your solution, it might help others, thanks

  • User Avatar
    0
    327992883@qq.com created

    Can you share your solution, it might help others, thanks

    Sure.Set key for TabPanel in foreach

    <Tabs Class="custom-nav-tabs" SelectedTab="@selectedTab" SelectedTabChanged="@OnSelectedTabChanged">
         <Items>
             @foreach (var tab in RouteDataList)
             {
                    <Tab Class="tab keep-tab-active" Name="@tab.TabName">
                        <Span Class="tab-span">@tab.Title</Span>
                            <Button @onclick:stopPropagation="true" Clicked="()=>ClosePage(tab)" Class="close-button">×</Button>
                     </Tab>
             }
         </Items>
         <Content>
             @foreach (var route in RouteDataList)
             {
                    <TabPanel @key="route.Url" Class="keep-panel-active" Name="@route.TabName">
                     <ContentTabToolbar RouteData="route"></ContentTabToolbar>
                     @route.Body
                 </TabPanel>
             }
         </Content>
     </Tabs>
    
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Thanks

Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11