From c10e5736a52d501383edc11bc650bdd9686ec7eb Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sun, 6 Apr 2025 12:22:58 +0800 Subject: [PATCH 1/4] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=20Tab=20?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E5=A2=9E=E5=8A=A0=20TabHeader=20=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E4=BB=A3=E6=9B=BF=20Layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Layout/Layout.razor | 4 +- .../Components/Layout/Layout.razor.cs | 23 +++++++-- .../Components/Layout/LayoutHeader.cs | 2 +- .../Components/Tab/ITabHeader.cs | 6 +++ src/BootstrapBlazor/Components/Tab/Tab.razor | 2 +- .../Components/Tab/Tab.razor.cs | 49 ++++++++----------- .../Components/Tab/Tab.razor.js | 9 ++-- 7 files changed, 53 insertions(+), 42 deletions(-) diff --git a/src/BootstrapBlazor/Components/Layout/Layout.razor b/src/BootstrapBlazor/Components/Layout/Layout.razor index 137af65e4b1..d5bf8748ec5 100644 --- a/src/BootstrapBlazor/Components/Layout/Layout.razor +++ b/src/BootstrapBlazor/Components/Layout/Layout.razor @@ -81,7 +81,7 @@ } @if (ShowTabInHeader) { -
+
@RenderTabHeader()
} @@ -143,7 +143,7 @@ ShowRefreshToolbarButton="ShowRefreshToolbarButton" ShowFullscreenToolbarButton="ShowFullscreenToolbarButton" RefreshToolbarButtonIcon="@RefreshToolbarButtonIcon" FullscreenToolbarButtonIcon="@FullscreenToolbarButtonIcon" RefreshToolbarTooltipText="@RefreshToolbarTooltipText" FullscreenToolbarTooltipText="@FullscreenToolbarTooltipText" - OnToolbarRefreshCallback="OnToolbarRefreshCallback" Layout="this" + OnToolbarRefreshCallback="OnToolbarRefreshCallback" TabHeader="TabHeader" Body="@Main" NotAuthorized="NotAuthorized!" NotFound="NotFound!" NotFoundTabText="@NotFoundTabText"> ; diff --git a/src/BootstrapBlazor/Components/Layout/Layout.razor.cs b/src/BootstrapBlazor/Components/Layout/Layout.razor.cs index 1938cd1ed95..c1a71bbb7f3 100644 --- a/src/BootstrapBlazor/Components/Layout/Layout.razor.cs +++ b/src/BootstrapBlazor/Components/Layout/Layout.razor.cs @@ -13,7 +13,7 @@ namespace BootstrapBlazor.Components; /// /// Layout 组件 /// -public partial class Layout : IHandlerException +public partial class Layout : IHandlerException, ITabHeader { private bool IsSmallScreen { get; set; } @@ -458,7 +458,9 @@ public partial class Layout : IHandlerException private bool _init; private Tab? _tab = null; - private ITabHeader? _tabHeader = null; + private LayoutHeader? _layoutHeader = null; + + private ITabHeader? TabHeader => ShowTabInHeader ? this : null; /// /// @@ -637,15 +639,26 @@ public virtual Task HandlerException(Exception ex, RenderFragment err private RenderFragment RenderTabHeader() => builder => { builder.OpenComponent(0); - builder.AddComponentReferenceCapture(1, instance => _tabHeader = (ITabHeader)instance); + builder.AddComponentReferenceCapture(1, instance => _layoutHeader = (LayoutHeader)instance); builder.CloseComponent(); }; - internal void RegisterTab(Tab tab) + /// + /// + /// + /// + /// + public void Render(RenderFragment renderFragment) { - tab.TabHeader = _tabHeader; + _layoutHeader?.Render(renderFragment); } + /// + /// + /// + /// + public string GetId() => $"{Id}_tab_header"; + /// /// /// diff --git a/src/BootstrapBlazor/Components/Layout/LayoutHeader.cs b/src/BootstrapBlazor/Components/Layout/LayoutHeader.cs index 3d2d97f4b4d..d4472166f88 100644 --- a/src/BootstrapBlazor/Components/Layout/LayoutHeader.cs +++ b/src/BootstrapBlazor/Components/Layout/LayoutHeader.cs @@ -5,7 +5,7 @@ namespace BootstrapBlazor.Components; -internal class LayoutHeader : IComponent, ITabHeader +internal class LayoutHeader : IComponent { private RenderHandle _renderHandle; diff --git a/src/BootstrapBlazor/Components/Tab/ITabHeader.cs b/src/BootstrapBlazor/Components/Tab/ITabHeader.cs index 5b4ec19111f..d5814d662fa 100644 --- a/src/BootstrapBlazor/Components/Tab/ITabHeader.cs +++ b/src/BootstrapBlazor/Components/Tab/ITabHeader.cs @@ -15,4 +15,10 @@ public interface ITabHeader /// /// void Render(RenderFragment renderFragment); + + /// + /// Get the id of the tab header + /// + /// + string GetId(); } diff --git a/src/BootstrapBlazor/Components/Tab/Tab.razor b/src/BootstrapBlazor/Components/Tab/Tab.razor index 6b4a79f2d12..c029317034c 100644 --- a/src/BootstrapBlazor/Components/Tab/Tab.razor +++ b/src/BootstrapBlazor/Components/Tab/Tab.razor @@ -8,7 +8,7 @@ } else { -
+
@if (TabHeader != null) { TabHeader.Render(RenderTabHeader); diff --git a/src/BootstrapBlazor/Components/Tab/Tab.razor.cs b/src/BootstrapBlazor/Components/Tab/Tab.razor.cs index ab6cf12e9d7..5f1f6c71128 100644 --- a/src/BootstrapBlazor/Components/Tab/Tab.razor.cs +++ b/src/BootstrapBlazor/Components/Tab/Tab.razor.cs @@ -423,10 +423,10 @@ public partial class Tab : IHandlerException /// Gets or sets the instance. Default is null. /// [Parameter] - public Layout? Layout { get; set; } + public ITabHeader? TabHeader { get; set; } [CascadingParameter] - private Layout? CascadeLayout { get; set; } + private Layout? Layout { get; set; } [Inject] [NotNull] @@ -471,8 +471,6 @@ public partial class Tab : IHandlerException private bool IsPreventDefault => _contextMenuZone != null; - internal ITabHeader? TabHeader { get; set; } - /// /// /// @@ -494,15 +492,6 @@ protected override void OnParametersSet() IsBorderCard = true; } - if (Layout is { ShowTabInHeader: true }) - { - Layout.RegisterTab(this); - } - else - { - TabHeader = null; - } - CloseOtherTabsText ??= Localizer[nameof(CloseOtherTabsText)]; CloseAllTabsText ??= Localizer[nameof(CloseAllTabsText)]; CloseCurrentTabText ??= Localizer[nameof(CloseCurrentTabText)]; @@ -584,9 +573,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) /// /// /// - protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(DragItemCallback), LayoutId); - - private string? LayoutId => Layout is { ShowTabInHeader: true } ? Layout.Id : null; + protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(DragItemCallback)); private void RemoveLocationChanged() { @@ -798,8 +785,6 @@ public void AddTab(string url, string text, string? icon = null, bool active = t StateHasChanged(); } - private Layout? LayoutInstance => Layout ?? CascadeLayout; - private void AddTabItem(string url) { var parameters = new Dictionary @@ -837,7 +822,7 @@ private void AddTabItem(string url) builder.AddAttribute(1, nameof(BootstrapBlazorAuthorizeView.Type), context.Handler); builder.AddAttribute(2, nameof(BootstrapBlazorAuthorizeView.Parameters), context.Parameters); builder.AddAttribute(3, nameof(BootstrapBlazorAuthorizeView.NotAuthorized), NotAuthorized); - builder.AddAttribute(4, nameof(BootstrapBlazorAuthorizeView.Resource), LayoutInstance?.Resource); + builder.AddAttribute(4, nameof(BootstrapBlazorAuthorizeView.Resource), Layout?.Resource); builder.CloseComponent(); })); } @@ -1019,7 +1004,7 @@ private RenderFragment RenderTabItemContent(TabItem item) => builder => private IEnumerable? _menuItems; private MenuItem? GetMenuItem(string url) { - _menuItems ??= (Menus ?? LayoutInstance?.Menus).GetAllItems(); + _menuItems ??= (Menus ?? Layout?.Menus).GetAllItems(); return _menuItems?.FirstOrDefault(i => !string.IsNullOrEmpty(i.Url) && (i.Url.TrimStart('/').Equals(url.TrimStart('/'), StringComparison.OrdinalIgnoreCase))); } @@ -1188,30 +1173,36 @@ private RenderFragment RenderContextMenuZoneContent() => builder => private RenderFragment RenderTabItems() => builder => { - for (var index = 0; index < _items.Count; index++) + foreach (var item in Items) { - var item = _items[index]; - var sequence = (index + 1) * 100; if (item.HeaderTemplate != null) { - builder.OpenElement(sequence, "div"); + builder.OpenElement(0, "div"); builder.SetKey(item); - builder.AddAttribute(sequence + 10, "class", GetItemWrapClassString(item)); - builder.AddAttribute(sequence + 20, "draggable", DraggableString); - builder.AddContent(sequence + 30, item.HeaderTemplate(item)); + builder.AddAttribute(10, "class", GetItemWrapClassString(item)); + builder.AddAttribute(20, "draggable", DraggableString); + builder.AddContent(30, item.HeaderTemplate(item)); builder.CloseElement(); } else if (item.IsDisabled) { - builder.AddContent(sequence + 40, RenderDisabledHeaderItem(item)); + builder.AddContent(40, RenderDisabledHeaderItem(item)); } else { - builder.AddContent(sequence + 50, RenderHeaderItem(item)); + builder.AddContent(50, RenderHeaderItem(item)); } } }; + /// + /// Sets the instance. + /// + /// + public void SetTabHeader(ITabHeader tabHeader) => TabHeader = tabHeader; + + private string? HeaderId => TabHeader?.GetId(); + /// /// /// diff --git a/src/BootstrapBlazor/Components/Tab/Tab.razor.js b/src/BootstrapBlazor/Components/Tab/Tab.razor.js index cd20c1a4fd6..346f939abb2 100644 --- a/src/BootstrapBlazor/Components/Tab/Tab.razor.js +++ b/src/BootstrapBlazor/Components/Tab/Tab.razor.js @@ -171,7 +171,7 @@ const disposeDragItems = items => { }) } -export function init(id, invoke, method, layoutId) { +export function init(id, invoke, method) { const el = document.getElementById(id) if (el === null) { return @@ -180,9 +180,10 @@ export function init(id, invoke, method, layoutId) { const tab = { el, invoke, method } Data.set(id, tab) - if (layoutId) { - const layout = document.getElementById(layoutId) - tab.header = layout.querySelector('.layout-header .tabs > .tabs-header'); + const headerId = el.getAttribute("data-bb-header-id"); + if (headerId) { + const header = document.getElementById(headerId) + tab.header = header.querySelector('.tabs > .tabs-header'); } else { tab.header = el.firstChild From e3bc9ab44f8678f8c199e2e121e956fb8783e082 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sun, 6 Apr 2025 16:09:36 +0800 Subject: [PATCH 2/4] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=20=5Ftab=20?= =?UTF-8?q?=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/Components/Layout/Layout.razor | 2 +- src/BootstrapBlazor/Components/Layout/Layout.razor.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/BootstrapBlazor/Components/Layout/Layout.razor b/src/BootstrapBlazor/Components/Layout/Layout.razor index d5bf8748ec5..b01e4b73150 100644 --- a/src/BootstrapBlazor/Components/Layout/Layout.razor +++ b/src/BootstrapBlazor/Components/Layout/Layout.razor @@ -131,7 +131,7 @@ ; RenderFragment RenderTab => - @? Localizer { get; set; } private bool _init; - private Tab? _tab = null; private LayoutHeader? _layoutHeader = null; private ITabHeader? TabHeader => ShowTabInHeader ? this : null; From 3ae7950e0b75719c7ca1f6534db5ea31a0c14a51 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sun, 6 Apr 2025 16:10:08 +0800 Subject: [PATCH 3/4] =?UTF-8?q?test:=20=E6=9B=B4=E6=96=B0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTest/Components/LayoutTest.cs | 2 ++ test/UnitTest/Components/TabTest.cs | 38 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/test/UnitTest/Components/LayoutTest.cs b/test/UnitTest/Components/LayoutTest.cs index 2c6e4e9a3b4..3014be8e84d 100644 --- a/test/UnitTest/Components/LayoutTest.cs +++ b/test/UnitTest/Components/LayoutTest.cs @@ -97,10 +97,12 @@ public void ShowTabInHeader_Ok() { var cut = Context.RenderComponent(pb => { + pb.Add(a => a.Id, "LayoutId"); pb.Add(a => a.UseTabSet, true); pb.Add(a => a.ShowTabInHeader, true); pb.Add(a => a.Header, CreateHeader()); }); + cut.Contains("data-bb-header-id=\"LayoutId__tab_header\""); cut.Contains("tabs tabs-chrome"); } diff --git a/test/UnitTest/Components/TabTest.cs b/test/UnitTest/Components/TabTest.cs index 710ec6846b5..bc2dc19f0f3 100644 --- a/test/UnitTest/Components/TabTest.cs +++ b/test/UnitTest/Components/TabTest.cs @@ -1104,6 +1104,33 @@ public async Task ShowToolbar_Ok() cut.DoesNotContain("tabs-nav-toolbar-fs"); } + [Fact] + public void TabHeader_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.AddChildContent(); + pb.AddChildContent(pb => + { + pb.Add(a => a.ShowToolbar, false); + pb.AddChildContent(pb => + { + pb.Add(a => a.ShowFullScreen, true); + pb.Add(a => a.Text, "Text1"); + pb.Add(a => a.ChildContent, builder => builder.AddContent(0, "Test1")); + }); + }); + }); + var header = cut.FindComponent(); + var tab = cut.FindComponent(); + var headerElement = cut.Find(".tabs-header"); + Assert.NotNull(headerElement); + + tab.Instance.SetTabHeader(header.Instance); + tab.SetParametersAndRender(); + tab.DoesNotContain("tabs-header"); + } + class DisableTabItemButton : ComponentBase { [CascadingParameter, NotNull] @@ -1122,4 +1149,15 @@ public Task OnDisabledTabItem() return Task.CompletedTask; } } + + class MockTabHeader : ComponentBase, ITabHeader + { + public string GetId() => "MockTabHeader"; + + private RenderFragment? _renderFragment; + + public void Render(RenderFragment renderFragment) => _renderFragment = renderFragment; + + protected override void BuildRenderTree(RenderTreeBuilder builder) => builder.AddContent(0, _renderFragment); + } } From fdb2a70cefcdf04f646a6ad5a7679fb1adaa1610 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sun, 6 Apr 2025 16:17:24 +0800 Subject: [PATCH 4/4] =?UTF-8?q?test:=20=E6=9B=B4=E6=96=B0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTest/Components/LayoutTest.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/test/UnitTest/Components/LayoutTest.cs b/test/UnitTest/Components/LayoutTest.cs index 3014be8e84d..289ea27ecb8 100644 --- a/test/UnitTest/Components/LayoutTest.cs +++ b/test/UnitTest/Components/LayoutTest.cs @@ -93,17 +93,30 @@ public async Task TabStyle_Ok() } [Fact] - public void ShowTabInHeader_Ok() + public async Task ShowTabInHeader_Ok() { var cut = Context.RenderComponent(pb => { pb.Add(a => a.Id, "LayoutId"); pb.Add(a => a.UseTabSet, true); - pb.Add(a => a.ShowTabInHeader, true); + pb.Add(a => a.ShowTabInHeader, false); pb.Add(a => a.Header, CreateHeader()); }); - cut.Contains("data-bb-header-id=\"LayoutId__tab_header\""); + await cut.InvokeAsync(() => cut.Instance.Render(bulder => bulder.AddContent(0, ""))); + + cut.SetParametersAndRender(pb => + { + pb.Add(a => a.ShowTabInHeader, true); + }); + cut.Contains("data-bb-header-id=\"LayoutId_tab_header\""); cut.Contains("tabs tabs-chrome"); + await cut.InvokeAsync(() => cut.Instance.Render(bulder => bulder.AddContent(0, ""))); + + cut.SetParametersAndRender(pb => + { + pb.Add(a => a.ShowTabInHeader, false); + }); + await cut.InvokeAsync(() => cut.Instance.Render(bulder => bulder.AddContent(0, ""))); } [Fact]