Skip to content

Commit bcb968f

Browse files
authored
feat(Tab): add TabHeader parameter (#5787)
* refactor: 重构 Tab 组件增加 TabHeader 参数代替 Layout * refactor: 移除 _tab 变量 * test: 更新单元测试 * test: 更新单元测试
1 parent 5d96376 commit bcb968f

File tree

9 files changed

+109
-46
lines changed

9 files changed

+109
-46
lines changed

src/BootstrapBlazor/Components/Layout/Layout.razor

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
}
8282
@if (ShowTabInHeader)
8383
{
84-
<div class="tabs tabs-chrome">
84+
<div class="tabs tabs-chrome" id="@GetId()">
8585
@RenderTabHeader()
8686
</div>
8787
}
@@ -131,7 +131,7 @@
131131
</main>;
132132

133133
RenderFragment RenderTab =>
134-
@<Tab ClickTabToNavigation="ClickTabToNavigation" AdditionalAssemblies="@AdditionalAssemblies" @ref="_tab"
134+
@<Tab ClickTabToNavigation="ClickTabToNavigation" AdditionalAssemblies="@AdditionalAssemblies"
135135
ShowExtendButtons="ShowTabExtendButtons" ShowClose="ShowTabItemClose" AllowDrag="AllowDragTab"
136136
DefaultUrl="@TabDefaultUrl" ExcludeUrls="@ExcludeUrls" IsOnlyRenderActiveTab="IsOnlyRenderActiveTab"
137137
TabStyle="TabStyle" ShowToolbar="@ShowToolbar" ToolbarTemplate="@ToolbarTemplate"
@@ -143,7 +143,7 @@
143143
ShowRefreshToolbarButton="ShowRefreshToolbarButton" ShowFullscreenToolbarButton="ShowFullscreenToolbarButton"
144144
RefreshToolbarButtonIcon="@RefreshToolbarButtonIcon" FullscreenToolbarButtonIcon="@FullscreenToolbarButtonIcon"
145145
RefreshToolbarTooltipText="@RefreshToolbarTooltipText" FullscreenToolbarTooltipText="@FullscreenToolbarTooltipText"
146-
OnToolbarRefreshCallback="OnToolbarRefreshCallback" Layout="this"
146+
OnToolbarRefreshCallback="OnToolbarRefreshCallback" TabHeader="TabHeader"
147147
Body="@Main" NotAuthorized="NotAuthorized!" NotFound="NotFound!" NotFoundTabText="@NotFoundTabText">
148148
</Tab>;
149149

src/BootstrapBlazor/Components/Layout/Layout.razor.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace BootstrapBlazor.Components;
1313
/// <summary>
1414
/// Layout 组件
1515
/// </summary>
16-
public partial class Layout : IHandlerException
16+
public partial class Layout : IHandlerException, ITabHeader
1717
{
1818
private bool IsSmallScreen { get; set; }
1919

@@ -457,8 +457,9 @@ public partial class Layout : IHandlerException
457457
private IStringLocalizer<Layout>? Localizer { get; set; }
458458

459459
private bool _init;
460-
private Tab? _tab = null;
461-
private ITabHeader? _tabHeader = null;
460+
private LayoutHeader? _layoutHeader = null;
461+
462+
private ITabHeader? TabHeader => ShowTabInHeader ? this : null;
462463

463464
/// <summary>
464465
/// <inheritdoc/>
@@ -637,15 +638,26 @@ public virtual Task HandlerException(Exception ex, RenderFragment<Exception> err
637638
private RenderFragment RenderTabHeader() => builder =>
638639
{
639640
builder.OpenComponent<LayoutHeader>(0);
640-
builder.AddComponentReferenceCapture(1, instance => _tabHeader = (ITabHeader)instance);
641+
builder.AddComponentReferenceCapture(1, instance => _layoutHeader = (LayoutHeader)instance);
641642
builder.CloseComponent();
642643
};
643644

644-
internal void RegisterTab(Tab tab)
645+
/// <summary>
646+
/// <inheritdoc/>
647+
/// </summary>
648+
/// <param name="renderFragment"></param>
649+
/// <exception cref="NotImplementedException"></exception>
650+
public void Render(RenderFragment renderFragment)
645651
{
646-
tab.TabHeader = _tabHeader;
652+
_layoutHeader?.Render(renderFragment);
647653
}
648654

655+
/// <summary>
656+
/// <inheritdoc/>
657+
/// </summary>
658+
/// <returns></returns>
659+
public string GetId() => $"{Id}_tab_header";
660+
649661
/// <summary>
650662
/// <inheritdoc/>
651663
/// </summary>

src/BootstrapBlazor/Components/Layout/LayoutHeader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace BootstrapBlazor.Components;
77

8-
internal class LayoutHeader : IComponent, ITabHeader
8+
internal class LayoutHeader : IComponent
99
{
1010
private RenderHandle _renderHandle;
1111

src/BootstrapBlazor/Components/Tab/ITabHeader.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@ public interface ITabHeader
1515
/// </summary>
1616
/// <param name="renderFragment"></param>
1717
void Render(RenderFragment renderFragment);
18+
19+
/// <summary>
20+
/// Get the id of the tab header
21+
/// </summary>
22+
/// <returns></returns>
23+
string GetId();
1824
}

src/BootstrapBlazor/Components/Tab/Tab.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
}
99
else
1010
{
11-
<div @attributes="@AdditionalAttributes" id="@Id" class="@ClassString" style="@StyleString">
11+
<div @attributes="@AdditionalAttributes" id="@Id" data-bb-header-id="@HeaderId" class="@ClassString" style="@StyleString">
1212
@if (TabHeader != null)
1313
{
1414
TabHeader.Render(RenderTabHeader);

src/BootstrapBlazor/Components/Tab/Tab.razor.cs

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -423,10 +423,10 @@ public partial class Tab : IHandlerException
423423
/// Gets or sets the <see cref="ITabHeader"/> instance. Default is null.
424424
/// </summary>
425425
[Parameter]
426-
public Layout? Layout { get; set; }
426+
public ITabHeader? TabHeader { get; set; }
427427

428428
[CascadingParameter]
429-
private Layout? CascadeLayout { get; set; }
429+
private Layout? Layout { get; set; }
430430

431431
[Inject]
432432
[NotNull]
@@ -471,8 +471,6 @@ public partial class Tab : IHandlerException
471471

472472
private bool IsPreventDefault => _contextMenuZone != null;
473473

474-
internal ITabHeader? TabHeader { get; set; }
475-
476474
/// <summary>
477475
/// <inheritdoc/>
478476
/// </summary>
@@ -494,15 +492,6 @@ protected override void OnParametersSet()
494492
IsBorderCard = true;
495493
}
496494

497-
if (Layout is { ShowTabInHeader: true })
498-
{
499-
Layout.RegisterTab(this);
500-
}
501-
else
502-
{
503-
TabHeader = null;
504-
}
505-
506495
CloseOtherTabsText ??= Localizer[nameof(CloseOtherTabsText)];
507496
CloseAllTabsText ??= Localizer[nameof(CloseAllTabsText)];
508497
CloseCurrentTabText ??= Localizer[nameof(CloseCurrentTabText)];
@@ -584,9 +573,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
584573
/// <inheritdoc/>
585574
/// </summary>
586575
/// <returns></returns>
587-
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(DragItemCallback), LayoutId);
588-
589-
private string? LayoutId => Layout is { ShowTabInHeader: true } ? Layout.Id : null;
576+
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(DragItemCallback));
590577

591578
private void RemoveLocationChanged()
592579
{
@@ -798,8 +785,6 @@ public void AddTab(string url, string text, string? icon = null, bool active = t
798785
StateHasChanged();
799786
}
800787

801-
private Layout? LayoutInstance => Layout ?? CascadeLayout;
802-
803788
private void AddTabItem(string url)
804789
{
805790
var parameters = new Dictionary<string, object?>
@@ -837,7 +822,7 @@ private void AddTabItem(string url)
837822
builder.AddAttribute(1, nameof(BootstrapBlazorAuthorizeView.Type), context.Handler);
838823
builder.AddAttribute(2, nameof(BootstrapBlazorAuthorizeView.Parameters), context.Parameters);
839824
builder.AddAttribute(3, nameof(BootstrapBlazorAuthorizeView.NotAuthorized), NotAuthorized);
840-
builder.AddAttribute(4, nameof(BootstrapBlazorAuthorizeView.Resource), LayoutInstance?.Resource);
825+
builder.AddAttribute(4, nameof(BootstrapBlazorAuthorizeView.Resource), Layout?.Resource);
841826
builder.CloseComponent();
842827
}));
843828
}
@@ -1019,7 +1004,7 @@ private RenderFragment RenderTabItemContent(TabItem item) => builder =>
10191004
private IEnumerable<MenuItem>? _menuItems;
10201005
private MenuItem? GetMenuItem(string url)
10211006
{
1022-
_menuItems ??= (Menus ?? LayoutInstance?.Menus).GetAllItems();
1007+
_menuItems ??= (Menus ?? Layout?.Menus).GetAllItems();
10231008
return _menuItems?.FirstOrDefault(i => !string.IsNullOrEmpty(i.Url) && (i.Url.TrimStart('/').Equals(url.TrimStart('/'), StringComparison.OrdinalIgnoreCase)));
10241009
}
10251010

@@ -1188,30 +1173,36 @@ private RenderFragment RenderContextMenuZoneContent() => builder =>
11881173

11891174
private RenderFragment RenderTabItems() => builder =>
11901175
{
1191-
for (var index = 0; index < _items.Count; index++)
1176+
foreach (var item in Items)
11921177
{
1193-
var item = _items[index];
1194-
var sequence = (index + 1) * 100;
11951178
if (item.HeaderTemplate != null)
11961179
{
1197-
builder.OpenElement(sequence, "div");
1180+
builder.OpenElement(0, "div");
11981181
builder.SetKey(item);
1199-
builder.AddAttribute(sequence + 10, "class", GetItemWrapClassString(item));
1200-
builder.AddAttribute(sequence + 20, "draggable", DraggableString);
1201-
builder.AddContent(sequence + 30, item.HeaderTemplate(item));
1182+
builder.AddAttribute(10, "class", GetItemWrapClassString(item));
1183+
builder.AddAttribute(20, "draggable", DraggableString);
1184+
builder.AddContent(30, item.HeaderTemplate(item));
12021185
builder.CloseElement();
12031186
}
12041187
else if (item.IsDisabled)
12051188
{
1206-
builder.AddContent(sequence + 40, RenderDisabledHeaderItem(item));
1189+
builder.AddContent(40, RenderDisabledHeaderItem(item));
12071190
}
12081191
else
12091192
{
1210-
builder.AddContent(sequence + 50, RenderHeaderItem(item));
1193+
builder.AddContent(50, RenderHeaderItem(item));
12111194
}
12121195
}
12131196
};
12141197

1198+
/// <summary>
1199+
/// Sets the <see cref="ITabHeader"/> instance.
1200+
/// </summary>
1201+
/// <param name="tabHeader"></param>
1202+
public void SetTabHeader(ITabHeader tabHeader) => TabHeader = tabHeader;
1203+
1204+
private string? HeaderId => TabHeader?.GetId();
1205+
12151206
/// <summary>
12161207
/// <inheritdoc/>
12171208
/// </summary>

src/BootstrapBlazor/Components/Tab/Tab.razor.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ const disposeDragItems = items => {
171171
})
172172
}
173173

174-
export function init(id, invoke, method, layoutId) {
174+
export function init(id, invoke, method) {
175175
const el = document.getElementById(id)
176176
if (el === null) {
177177
return
@@ -180,9 +180,10 @@ export function init(id, invoke, method, layoutId) {
180180
const tab = { el, invoke, method }
181181
Data.set(id, tab)
182182

183-
if (layoutId) {
184-
const layout = document.getElementById(layoutId)
185-
tab.header = layout.querySelector('.layout-header .tabs > .tabs-header');
183+
const headerId = el.getAttribute("data-bb-header-id");
184+
if (headerId) {
185+
const header = document.getElementById(headerId)
186+
tab.header = header.querySelector('.tabs > .tabs-header');
186187
}
187188
else {
188189
tab.header = el.firstChild

test/UnitTest/Components/LayoutTest.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,30 @@ public async Task TabStyle_Ok()
9393
}
9494

9595
[Fact]
96-
public void ShowTabInHeader_Ok()
96+
public async Task ShowTabInHeader_Ok()
9797
{
9898
var cut = Context.RenderComponent<Layout>(pb =>
9999
{
100+
pb.Add(a => a.Id, "LayoutId");
100101
pb.Add(a => a.UseTabSet, true);
101-
pb.Add(a => a.ShowTabInHeader, true);
102+
pb.Add(a => a.ShowTabInHeader, false);
102103
pb.Add(a => a.Header, CreateHeader());
103104
});
105+
await cut.InvokeAsync(() => cut.Instance.Render(bulder => bulder.AddContent(0, "")));
106+
107+
cut.SetParametersAndRender(pb =>
108+
{
109+
pb.Add(a => a.ShowTabInHeader, true);
110+
});
111+
cut.Contains("data-bb-header-id=\"LayoutId_tab_header\"");
104112
cut.Contains("tabs tabs-chrome");
113+
await cut.InvokeAsync(() => cut.Instance.Render(bulder => bulder.AddContent(0, "")));
114+
115+
cut.SetParametersAndRender(pb =>
116+
{
117+
pb.Add(a => a.ShowTabInHeader, false);
118+
});
119+
await cut.InvokeAsync(() => cut.Instance.Render(bulder => bulder.AddContent(0, "")));
105120
}
106121

107122
[Fact]

test/UnitTest/Components/TabTest.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,33 @@ public async Task ShowToolbar_Ok()
11041104
cut.DoesNotContain("tabs-nav-toolbar-fs");
11051105
}
11061106

1107+
[Fact]
1108+
public void TabHeader_Ok()
1109+
{
1110+
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
1111+
{
1112+
pb.AddChildContent<MockTabHeader>();
1113+
pb.AddChildContent<Tab>(pb =>
1114+
{
1115+
pb.Add(a => a.ShowToolbar, false);
1116+
pb.AddChildContent<TabItem>(pb =>
1117+
{
1118+
pb.Add(a => a.ShowFullScreen, true);
1119+
pb.Add(a => a.Text, "Text1");
1120+
pb.Add(a => a.ChildContent, builder => builder.AddContent(0, "Test1"));
1121+
});
1122+
});
1123+
});
1124+
var header = cut.FindComponent<MockTabHeader>();
1125+
var tab = cut.FindComponent<Tab>();
1126+
var headerElement = cut.Find(".tabs-header");
1127+
Assert.NotNull(headerElement);
1128+
1129+
tab.Instance.SetTabHeader(header.Instance);
1130+
tab.SetParametersAndRender();
1131+
tab.DoesNotContain("tabs-header");
1132+
}
1133+
11071134
class DisableTabItemButton : ComponentBase
11081135
{
11091136
[CascadingParameter, NotNull]
@@ -1122,4 +1149,15 @@ public Task OnDisabledTabItem()
11221149
return Task.CompletedTask;
11231150
}
11241151
}
1152+
1153+
class MockTabHeader : ComponentBase, ITabHeader
1154+
{
1155+
public string GetId() => "MockTabHeader";
1156+
1157+
private RenderFragment? _renderFragment;
1158+
1159+
public void Render(RenderFragment renderFragment) => _renderFragment = renderFragment;
1160+
1161+
protected override void BuildRenderTree(RenderTreeBuilder builder) => builder.AddContent(0, _renderFragment);
1162+
}
11251163
}

0 commit comments

Comments
 (0)