diff --git a/src/BootstrapBlazor.Server/Components/Samples/DropdownWidgets.razor b/src/BootstrapBlazor.Server/Components/Samples/DropdownWidgets.razor index c6e7ad895fd..dd3f4609dd2 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/DropdownWidgets.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/DropdownWidgets.razor @@ -7,7 +7,7 @@
- + @Localizer["BasicUsageMessage"] @@ -81,7 +81,9 @@
- +
+ +
diff --git a/src/BootstrapBlazor.Server/Components/Samples/DropdownWidgets.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/DropdownWidgets.razor.cs index 055c6484392..f01f520f938 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/DropdownWidgets.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/DropdownWidgets.razor.cs @@ -9,6 +9,14 @@ namespace BootstrapBlazor.Server.Components.Samples; /// public partial class DropdownWidgets { + private ConsoleLogger _logger = default!; + + private Task OnItemCloseAsync(DropdownWidgetItem item) + { + _logger.Log($"Item {item.BadgeNumber} closed"); + return Task.CompletedTask; + } + private AttributeItem[] GetAttributes() => [ new() @@ -82,6 +90,14 @@ private AttributeItem[] GetAttributes() => Type = "RenderFragment", ValueList = " — ", DefaultValue = " — " + }, + new() + { + Name = "OnItemCloseAsync", + Description = Localizer["OnItemCloseAsync"], + Type = "Func", + ValueList = " — ", + DefaultValue = " — " } ]; } diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index 118f16ed38e..7742592c80e 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -3980,7 +3980,8 @@ "BasicUsageNotify": "You have 10 unread notifications", "BasicUsageViewNotify": "View all notifications", "BasicUsageTasks": "You have 3 tasks", - "BasicUsageViewTasks": "View all tasks" + "BasicUsageViewTasks": "View all tasks", + "OnItemCloseAsync": "Close dropdown widget item callback method" }, "BootstrapBlazor.Server.Components.Samples.Empties": { "Title": "Empty", diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index af0183f5542..470910d311d 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -3980,7 +3980,8 @@ "BasicUsageNotify": "您有 10 个未读通知", "BasicUsageViewNotify": "查看所有通知", "BasicUsageTasks": "您有 3 个任务", - "BasicUsageViewTasks": "查看所有任务" + "BasicUsageViewTasks": "查看所有任务", + "OnItemCloseAsync": "关闭菜单项回调方法" }, "BootstrapBlazor.Server.Components.Samples.Empties": { "Title": "Empty 空状态", diff --git a/src/BootstrapBlazor.Server/Properties/launchSettings.json b/src/BootstrapBlazor.Server/Properties/launchSettings.json index abe15615528..7152745fa40 100644 --- a/src/BootstrapBlazor.Server/Properties/launchSettings.json +++ b/src/BootstrapBlazor.Server/Properties/launchSettings.json @@ -8,7 +8,7 @@ } }, "profiles": { - "WebConsole": { + "console": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, @@ -17,15 +17,6 @@ "ASPNETCORE_ENVIRONMENT": "Development" } }, - "Watch": { - "commandName": "Executable", - "executablePath": "dotnet.exe", - "workingDirectory": "$(ProjectDir)", - "commandLineArgs": "watch run", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "https": { "commandName": "Project", "dotnetRunMessages": true, @@ -35,26 +26,12 @@ "ASPNETCORE_ENVIRONMENT": "Development" } }, - "LanConsole": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://0.0.0.0/", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } - }, - "Docker": { - "commandName": "Docker", - "launchBrowser": true, - "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}" } } } diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 30a2b24ecd5..366d487b631 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@ - 8.10.2-beta04 + 8.10.2-beta05 diff --git a/src/BootstrapBlazor/Components/DropdownWidget/DropdownWidget.razor b/src/BootstrapBlazor/Components/DropdownWidget/DropdownWidget.razor index a95a82e4ebb..ce68d0d98a1 100644 --- a/src/BootstrapBlazor/Components/DropdownWidget/DropdownWidget.razor +++ b/src/BootstrapBlazor/Components/DropdownWidget/DropdownWidget.razor @@ -1,7 +1,8 @@ @namespace BootstrapBlazor.Components -@inherits BootstrapComponentBase +@inherits BootstrapModuleComponentBase +@attribute [BootstrapModuleAutoLoader(JSObjectReference = true)] -
+
@ChildContent diff --git a/src/BootstrapBlazor/Components/DropdownWidget/DropdownWidget.razor.cs b/src/BootstrapBlazor/Components/DropdownWidget/DropdownWidget.razor.cs index 8f7874ef3b7..ae84d07ae3a 100644 --- a/src/BootstrapBlazor/Components/DropdownWidget/DropdownWidget.razor.cs +++ b/src/BootstrapBlazor/Components/DropdownWidget/DropdownWidget.razor.cs @@ -25,8 +25,26 @@ public sealed partial class DropdownWidget [Parameter] public IEnumerable? Items { get; set; } + /// + /// 获得/设置 下拉项关闭回调方法 + /// + [Parameter] + public Func? OnItemCloseAsync { get; set; } + + /// + /// 获得/设置 下拉项关闭回调方法 + /// + [Parameter] + public Func? OnItemShownAsync { get; set; } + private List Childs { get; } = new List(20); + /// + /// + /// + /// + protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new { Method = nameof(TriggerStateChanged) }); + /// /// 添加 DropdownWidgetItem 方法 /// @@ -37,4 +55,28 @@ internal void Add(DropdownWidgetItem item) } private IEnumerable GetItems() => Items == null ? Childs : Childs.Concat(Items); + + /// + /// Widget 下拉项关闭回调方法 由 JavaScript 调用 + /// + /// + /// + /// + [JSInvokable] + public async Task TriggerStateChanged(int index, bool shown) + { + var items = GetItems().ToList(); + var item = index < items.Count ? items[index] : null; + if (item != null) + { + if (OnItemCloseAsync != null && !shown) + { + await OnItemCloseAsync(item); + } + else if (OnItemShownAsync != null && shown) + { + await OnItemShownAsync(item); + } + } + } } diff --git a/src/BootstrapBlazor/Components/DropdownWidget/DropdownWidget.razor.js b/src/BootstrapBlazor/Components/DropdownWidget/DropdownWidget.razor.js new file mode 100644 index 00000000000..f23422284bc --- /dev/null +++ b/src/BootstrapBlazor/Components/DropdownWidget/DropdownWidget.razor.js @@ -0,0 +1,23 @@ +import EventHandler from "../../modules/event-handler.js" + +export function init(id, invoke, options) { + const el = document.getElementById(id); + if (el === null) { + return; + } + + const invokeMethod = e => { + const item = e.target; + const items = [...el.querySelectorAll("[data-bs-toggle=\"dropdown\"]")]; + const index = items.indexOf(item); + invoke.invokeMethodAsync(method, index, e.type === 'shown.bs.dropdown'); + } + + const { method } = options; + EventHandler.on(el, 'shown.bs.dropdown', invokeMethod); + EventHandler.on(el, 'hidden.bs.dropdown', invokeMethod); +} + +export function dispose(id) { + EventHandler.off(el, 'hidden.bs.dropdown'); +} diff --git a/test/UnitTest/Components/DropdownWigetTest.cs b/test/UnitTest/Components/DropdownWidgetTest.cs similarity index 75% rename from test/UnitTest/Components/DropdownWigetTest.cs rename to test/UnitTest/Components/DropdownWidgetTest.cs index 8dfe7ff9e62..9bc26dccbf2 100644 --- a/test/UnitTest/Components/DropdownWigetTest.cs +++ b/test/UnitTest/Components/DropdownWidgetTest.cs @@ -4,7 +4,7 @@ namespace UnitTest.Components; -public class DropdownWigetTest : BootstrapBlazorTestBase +public class DropdownWidgetTest : BootstrapBlazorTestBase { [Fact] public void Items_OK() @@ -54,12 +54,12 @@ public void Title_OK() builder.Add(s => s.ChildContent, new RenderFragment(builder => { builder.OpenComponent(0); - builder.AddAttribute(1, nameof(DropdownWidgetItem.Title), "Wiget Title"); + builder.AddAttribute(1, nameof(DropdownWidgetItem.Title), "Widget Title"); builder.CloseComponent(); })); }); - Assert.Contains("Wiget Title", cut.Markup); + Assert.Contains("Widget Title", cut.Markup); } [Fact] @@ -181,10 +181,60 @@ public void HeaderColor_OK() Assert.NotNull(ele); } - private static IEnumerable GetItems() + [Fact] + public async Task OnItemAsync_OK() + { + var shown = false; + var closed = false; + var cut = Context.RenderComponent(builder => + { + builder.Add(a => a.OnItemShownAsync, item => + { + shown = true; + return Task.CompletedTask; + }); + builder.Add(s => s.ChildContent, new RenderFragment(builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(DropdownWidgetItem.HeaderColor), Color.Success); + builder.AddAttribute(2, nameof(DropdownWidgetItem.Title), "Test1"); + builder.CloseComponent(); + + builder.OpenComponent(0); + builder.AddAttribute(10, nameof(DropdownWidgetItem.HeaderColor), Color.Success); + builder.AddAttribute(11, nameof(DropdownWidgetItem.Title), "Test2"); + builder.CloseComponent(); + })); + }); + + // 索引越界 + await cut.InvokeAsync(() => cut.Instance.TriggerStateChanged(2, false)); + Assert.False(closed); + + // 未注册 OnItemCloseAsync 回调 + await cut.InvokeAsync(() => cut.Instance.TriggerStateChanged(1, false)); + Assert.False(closed); + + // 触发 OnItemShownAsync 回调 + await cut.InvokeAsync(() => cut.Instance.TriggerStateChanged(0, true)); + Assert.True(shown); + + cut.SetParametersAndRender(pb => + { + pb.Add(a => a.OnItemCloseAsync, item => + { + closed = true; + return Task.CompletedTask; + }); + }); + await cut.InvokeAsync(() => cut.Instance.TriggerStateChanged(1, false)); + Assert.True(closed); + } + + private static List GetItems() { var ret = new List(); - var wiget = new DropdownWidgetItem(); + var widget = new DropdownWidgetItem(); var parameters = new Dictionary() { ["Icon"] = "fa-regular fa-bell", @@ -207,8 +257,8 @@ private static IEnumerable GetItems() }), }; - wiget.SetParametersAsync(ParameterView.FromDictionary(parameters!)); - ret.Add(wiget); + widget.SetParametersAsync(ParameterView.FromDictionary(parameters!)); + ret.Add(widget); return ret; } }