Skip to content

Commit 0779916

Browse files
authored
feat(DropdownWidget): add OnItemShown/CloseAsync parameter (#4419)
* feat: add OnItemCloseAsync method * doc: 更新 DropdownWidget 示例代码 * feat: 增加 OnItemShownAsync 回调方法 * test: 更新单元测试 * chore: 更新配置文件 * refactor: 精简代码 * test: 增加单元测试 * test: 更新单元测试 * chore: bump version 8.10.2-beta05
1 parent bdae9f4 commit 0779916

File tree

10 files changed

+151
-38
lines changed

10 files changed

+151
-38
lines changed

src/BootstrapBlazor.Server/Components/Samples/DropdownWidgets.razor

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
<DemoBlock Title="@Localizer["BasicUsageTitle"]" Introduction="@Localizer["BasicUsageIntro"]" Name="Normal">
99
<div class="widget-demo">
10-
<DropdownWidget>
10+
<DropdownWidget OnItemCloseAsync="OnItemCloseAsync">
1111
<DropdownWidgetItem Icon="fa-regular fa-envelope" BadgeNumber="4">
1212
<HeaderTemplate>
1313
<span>@Localizer["BasicUsageMessage"]</span>
@@ -81,7 +81,9 @@
8181
</DropdownWidgetItem>
8282
</DropdownWidget>
8383
</div>
84-
84+
<section ignore>
85+
<ConsoleLogger @ref="_logger"></ConsoleLogger>
86+
</section>
8587
</DemoBlock>
8688

8789
<AttributeTable Items="@GetAttributes()" Title="@Localizer["AttributeTitle"]" />

src/BootstrapBlazor.Server/Components/Samples/DropdownWidgets.razor.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ namespace BootstrapBlazor.Server.Components.Samples;
99
/// </summary>
1010
public partial class DropdownWidgets
1111
{
12+
private ConsoleLogger _logger = default!;
13+
14+
private Task OnItemCloseAsync(DropdownWidgetItem item)
15+
{
16+
_logger.Log($"Item {item.BadgeNumber} closed");
17+
return Task.CompletedTask;
18+
}
19+
1220
private AttributeItem[] GetAttributes() =>
1321
[
1422
new()
@@ -82,6 +90,14 @@ private AttributeItem[] GetAttributes() =>
8290
Type = "RenderFragment",
8391
ValueList = " — ",
8492
DefaultValue = " — "
93+
},
94+
new()
95+
{
96+
Name = "OnItemCloseAsync",
97+
Description = Localizer["OnItemCloseAsync"],
98+
Type = "Func<DropdownWidgetItem, Task>",
99+
ValueList = " — ",
100+
DefaultValue = " — "
85101
}
86102
];
87103
}

src/BootstrapBlazor.Server/Locales/en-US.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3980,7 +3980,8 @@
39803980
"BasicUsageNotify": "You have 10 unread notifications",
39813981
"BasicUsageViewNotify": "View all notifications",
39823982
"BasicUsageTasks": "You have 3 tasks",
3983-
"BasicUsageViewTasks": "View all tasks"
3983+
"BasicUsageViewTasks": "View all tasks",
3984+
"OnItemCloseAsync": "Close dropdown widget item callback method"
39843985
},
39853986
"BootstrapBlazor.Server.Components.Samples.Empties": {
39863987
"Title": "Empty",

src/BootstrapBlazor.Server/Locales/zh-CN.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3980,7 +3980,8 @@
39803980
"BasicUsageNotify": "您有 10 个未读通知",
39813981
"BasicUsageViewNotify": "查看所有通知",
39823982
"BasicUsageTasks": "您有 3 个任务",
3983-
"BasicUsageViewTasks": "查看所有任务"
3983+
"BasicUsageViewTasks": "查看所有任务",
3984+
"OnItemCloseAsync": "关闭菜单项回调方法"
39843985
},
39853986
"BootstrapBlazor.Server.Components.Samples.Empties": {
39863987
"Title": "Empty 空状态",

src/BootstrapBlazor.Server/Properties/launchSettings.json

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
}
99
},
1010
"profiles": {
11-
"WebConsole": {
11+
"console": {
1212
"commandName": "Project",
1313
"dotnetRunMessages": true,
1414
"launchBrowser": true,
@@ -17,15 +17,6 @@
1717
"ASPNETCORE_ENVIRONMENT": "Development"
1818
}
1919
},
20-
"Watch": {
21-
"commandName": "Executable",
22-
"executablePath": "dotnet.exe",
23-
"workingDirectory": "$(ProjectDir)",
24-
"commandLineArgs": "watch run",
25-
"environmentVariables": {
26-
"ASPNETCORE_ENVIRONMENT": "Development"
27-
}
28-
},
2920
"https": {
3021
"commandName": "Project",
3122
"dotnetRunMessages": true,
@@ -35,26 +26,12 @@
3526
"ASPNETCORE_ENVIRONMENT": "Development"
3627
}
3728
},
38-
"LanConsole": {
39-
"commandName": "Project",
40-
"dotnetRunMessages": true,
41-
"launchBrowser": true,
42-
"applicationUrl": "http://0.0.0.0/",
43-
"environmentVariables": {
44-
"ASPNETCORE_ENVIRONMENT": "Development"
45-
}
46-
},
4729
"IIS Express": {
4830
"commandName": "IISExpress",
4931
"launchBrowser": true,
5032
"environmentVariables": {
5133
"ASPNETCORE_ENVIRONMENT": "Development"
5234
}
53-
},
54-
"Docker": {
55-
"commandName": "Docker",
56-
"launchBrowser": true,
57-
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}"
5835
}
5936
}
6037
}

src/BootstrapBlazor/BootstrapBlazor.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk.Razor">
22

33
<PropertyGroup>
4-
<Version>8.10.2-beta04</Version>
4+
<Version>8.10.2-beta05</Version>
55
</PropertyGroup>
66

77
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">

src/BootstrapBlazor/Components/DropdownWidget/DropdownWidget.razor

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
@namespace BootstrapBlazor.Components
2-
@inherits BootstrapComponentBase
2+
@inherits BootstrapModuleComponentBase
3+
@attribute [BootstrapModuleAutoLoader(JSObjectReference = true)]
34

4-
<div @attributes="@AdditionalAttributes" class="@ClassString">
5+
<div @attributes="@AdditionalAttributes" class="@ClassString" id="@Id">
56
<CascadingValue Value="this" IsFixed="true">
67
@ChildContent
78
</CascadingValue>

src/BootstrapBlazor/Components/DropdownWidget/DropdownWidget.razor.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,26 @@ public sealed partial class DropdownWidget
2525
[Parameter]
2626
public IEnumerable<DropdownWidgetItem>? Items { get; set; }
2727

28+
/// <summary>
29+
/// 获得/设置 下拉项关闭回调方法
30+
/// </summary>
31+
[Parameter]
32+
public Func<DropdownWidgetItem, Task>? OnItemCloseAsync { get; set; }
33+
34+
/// <summary>
35+
/// 获得/设置 下拉项关闭回调方法
36+
/// </summary>
37+
[Parameter]
38+
public Func<DropdownWidgetItem, Task>? OnItemShownAsync { get; set; }
39+
2840
private List<DropdownWidgetItem> Childs { get; } = new List<DropdownWidgetItem>(20);
2941

42+
/// <summary>
43+
/// <inheritdoc/>
44+
/// </summary>
45+
/// <returns></returns>
46+
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new { Method = nameof(TriggerStateChanged) });
47+
3048
/// <summary>
3149
/// 添加 DropdownWidgetItem 方法
3250
/// </summary>
@@ -37,4 +55,28 @@ internal void Add(DropdownWidgetItem item)
3755
}
3856

3957
private IEnumerable<DropdownWidgetItem> GetItems() => Items == null ? Childs : Childs.Concat(Items);
58+
59+
/// <summary>
60+
/// Widget 下拉项关闭回调方法 由 JavaScript 调用
61+
/// </summary>
62+
/// <param name="index"></param>
63+
/// <param name="shown"></param>
64+
/// <returns></returns>
65+
[JSInvokable]
66+
public async Task TriggerStateChanged(int index, bool shown)
67+
{
68+
var items = GetItems().ToList();
69+
var item = index < items.Count ? items[index] : null;
70+
if (item != null)
71+
{
72+
if (OnItemCloseAsync != null && !shown)
73+
{
74+
await OnItemCloseAsync(item);
75+
}
76+
else if (OnItemShownAsync != null && shown)
77+
{
78+
await OnItemShownAsync(item);
79+
}
80+
}
81+
}
4082
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import EventHandler from "../../modules/event-handler.js"
2+
3+
export function init(id, invoke, options) {
4+
const el = document.getElementById(id);
5+
if (el === null) {
6+
return;
7+
}
8+
9+
const invokeMethod = e => {
10+
const item = e.target;
11+
const items = [...el.querySelectorAll("[data-bs-toggle=\"dropdown\"]")];
12+
const index = items.indexOf(item);
13+
invoke.invokeMethodAsync(method, index, e.type === 'shown.bs.dropdown');
14+
}
15+
16+
const { method } = options;
17+
EventHandler.on(el, 'shown.bs.dropdown', invokeMethod);
18+
EventHandler.on(el, 'hidden.bs.dropdown', invokeMethod);
19+
}
20+
21+
export function dispose(id) {
22+
EventHandler.off(el, 'hidden.bs.dropdown');
23+
}

test/UnitTest/Components/DropdownWigetTest.cs renamed to test/UnitTest/Components/DropdownWidgetTest.cs

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace UnitTest.Components;
66

7-
public class DropdownWigetTest : BootstrapBlazorTestBase
7+
public class DropdownWidgetTest : BootstrapBlazorTestBase
88
{
99
[Fact]
1010
public void Items_OK()
@@ -54,12 +54,12 @@ public void Title_OK()
5454
builder.Add(s => s.ChildContent, new RenderFragment(builder =>
5555
{
5656
builder.OpenComponent<DropdownWidgetItem>(0);
57-
builder.AddAttribute(1, nameof(DropdownWidgetItem.Title), "Wiget Title");
57+
builder.AddAttribute(1, nameof(DropdownWidgetItem.Title), "Widget Title");
5858
builder.CloseComponent();
5959
}));
6060
});
6161

62-
Assert.Contains("Wiget Title", cut.Markup);
62+
Assert.Contains("Widget Title", cut.Markup);
6363
}
6464

6565
[Fact]
@@ -181,10 +181,60 @@ public void HeaderColor_OK()
181181
Assert.NotNull(ele);
182182
}
183183

184-
private static IEnumerable<DropdownWidgetItem> GetItems()
184+
[Fact]
185+
public async Task OnItemAsync_OK()
186+
{
187+
var shown = false;
188+
var closed = false;
189+
var cut = Context.RenderComponent<DropdownWidget>(builder =>
190+
{
191+
builder.Add(a => a.OnItemShownAsync, item =>
192+
{
193+
shown = true;
194+
return Task.CompletedTask;
195+
});
196+
builder.Add(s => s.ChildContent, new RenderFragment(builder =>
197+
{
198+
builder.OpenComponent<DropdownWidgetItem>(0);
199+
builder.AddAttribute(1, nameof(DropdownWidgetItem.HeaderColor), Color.Success);
200+
builder.AddAttribute(2, nameof(DropdownWidgetItem.Title), "Test1");
201+
builder.CloseComponent();
202+
203+
builder.OpenComponent<DropdownWidgetItem>(0);
204+
builder.AddAttribute(10, nameof(DropdownWidgetItem.HeaderColor), Color.Success);
205+
builder.AddAttribute(11, nameof(DropdownWidgetItem.Title), "Test2");
206+
builder.CloseComponent();
207+
}));
208+
});
209+
210+
// 索引越界
211+
await cut.InvokeAsync(() => cut.Instance.TriggerStateChanged(2, false));
212+
Assert.False(closed);
213+
214+
// 未注册 OnItemCloseAsync 回调
215+
await cut.InvokeAsync(() => cut.Instance.TriggerStateChanged(1, false));
216+
Assert.False(closed);
217+
218+
// 触发 OnItemShownAsync 回调
219+
await cut.InvokeAsync(() => cut.Instance.TriggerStateChanged(0, true));
220+
Assert.True(shown);
221+
222+
cut.SetParametersAndRender(pb =>
223+
{
224+
pb.Add(a => a.OnItemCloseAsync, item =>
225+
{
226+
closed = true;
227+
return Task.CompletedTask;
228+
});
229+
});
230+
await cut.InvokeAsync(() => cut.Instance.TriggerStateChanged(1, false));
231+
Assert.True(closed);
232+
}
233+
234+
private static List<DropdownWidgetItem> GetItems()
185235
{
186236
var ret = new List<DropdownWidgetItem>();
187-
var wiget = new DropdownWidgetItem();
237+
var widget = new DropdownWidgetItem();
188238
var parameters = new Dictionary<string, object?>()
189239
{
190240
["Icon"] = "fa-regular fa-bell",
@@ -207,8 +257,8 @@ private static IEnumerable<DropdownWidgetItem> GetItems()
207257
}),
208258

209259
};
210-
wiget.SetParametersAsync(ParameterView.FromDictionary(parameters!));
211-
ret.Add(wiget);
260+
widget.SetParametersAsync(ParameterView.FromDictionary(parameters!));
261+
ret.Add(widget);
212262
return ret;
213263
}
214264
}

0 commit comments

Comments
 (0)