diff --git a/src/BootstrapBlazor.Server/Components/Samples/Dropdowns.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Dropdowns.razor.cs
index 5e51ff48194..15b016ab120 100644
--- a/src/BootstrapBlazor.Server/Components/Samples/Dropdowns.razor.cs
+++ b/src/BootstrapBlazor.Server/Components/Samples/Dropdowns.razor.cs
@@ -10,6 +10,9 @@ namespace BootstrapBlazor.Server.Components.Samples;
///
public sealed partial class Dropdowns
{
+ [Inject, NotNull]
+ private ToastService? ToastService { get; set; }
+
[NotNull]
private ConsoleLogger? Logger { get; set; }
@@ -142,6 +145,15 @@ private async Task OnCascadeBindSelectClick(SelectedItem item)
StateHasChanged();
}
+ private async Task OnIsAsyncClick()
+ {
+ // 模拟异步延时
+ await Task.Delay(1000);
+
+ // 提示任务完成
+ await ToastService.Success("Dropdown IsAsync", "Job done!");
+ }
+
///
/// GetAttributes
///
@@ -220,6 +232,14 @@ private AttributeItem[] GetAttributes() =>
DefaultValue = " false "
},
new()
+ {
+ Name = "IsAsync",
+ Description = Localizer["AttributeIsAsync"],
+ Type = "boolean",
+ ValueList = " — ",
+ DefaultValue = "false"
+ },
+ new()
{
Name = "Size",
Description = Localizer["AttributeSize"],
diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json
index 43afeeb8947..39c19942614 100644
--- a/src/BootstrapBlazor.Server/Locales/en-US.json
+++ b/src/BootstrapBlazor.Server/Locales/en-US.json
@@ -1763,6 +1763,8 @@
"SplitIntro": "You can create a split drop-down menu with tags similar to a single button drop-down menu, and add ShowSplit='true' when you use a split component. Insert this symbol as a drop-down The options are handled at appropriate intervals (distance).",
"SizeTitle": "Size definition",
"SizeIntro": "The drop-down menu has a variety of size specifications to choose from
Size attributes, including preset and split button drop-down menus.",
+ "IsAsyncTitle": "Asynchronous request button",
+ "IsAsyncIntro": "By setting whether the
isAsync property button is
asynchronous request button by setting whether the is false by default",
"DirectionTitle": "Expanding direction",
"DirectionIntro": "Add the style of
Direction='Direction.Dropup' to make the drop-down menu expand upward.",
"AlignmentTitle": "Menu alignment",
@@ -1788,6 +1790,7 @@
"AttributeMenuItem": "Menu item rendering label",
"AttributeResponsive": "Menu alignment",
"AttributeShowSplit": "Split button drop-down menu",
+ "AttributeIsAsync": "whether it is an asynchronous button",
"AttributeSize": "Size",
"AttributeTagName": "Label",
"AttributeButtonTemplate": "The template of button",
@@ -2226,7 +2229,7 @@
"Block8Title": "Secondary encapsulation button",
"Block8Intro": "The button display text is set by setting the
Text property of the
winButton component, and the click button is the text on the right that shows the clicked button",
"Block9Title": "Asynchronous request button",
- "Block9Intro": "By setting whether the
isAsync property button is
asynchronous request button by setting whether the is false by default",
+ "Block9Intro": "By setting whether the
isAsync property button is
asynchronous request button by setting whether the is false by default.
Note This will only take effect when
ShowSplit=\"true\" is set",
"ButtonAsyncDescription": "When the button is an asynchronous request button, the button is changed to disabled, and the
loading small icon is displayed, returning to normal after the asynchronous request ends, in this case, after clicking the
asynchronous button, the request load animation is displayed and returns to normal after 5 seconds",
"EventDesc1": "This event is triggered when the button is clicked",
"EventDesc2": "This event is triggered when the button is clicked and the current component is not refreshed for performance improvement",
diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json
index a4e851896d0..c0011c53a9a 100644
--- a/src/BootstrapBlazor.Server/Locales/zh-CN.json
+++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json
@@ -1761,6 +1761,8 @@
"ColorIntro": "提供各种颜色的警告信息框 引用
Color='Color.Primary' 等颜色及样式类来定义下拉菜单的外在表现",
"SplitTitle": "分裂式按钮下拉菜单",
"SplitIntro": "可用与单个按钮下拉菜单近似的标记创建分裂式下拉菜单,添加
ShowSplit='true' 插入此符号为下拉选项作适当的间隔(距)处理。",
+ "IsAsyncTitle": "异步按钮",
+ "IsAsyncIntro": "通过设置
IsAsync 属性按钮是否为
异步请求按钮,默认为
false,
注意 需要设置
ShowSplit=\"true\" 时才生效",
"SizeTitle": "尺寸大小定义",
"SizeIntro": "下拉菜单有各种大小规格可以选用
Size 属性,包括预设及分裂式按钮下拉菜单。",
"DirectionTitle": "展开方向",
@@ -1788,6 +1790,7 @@
"AttributeMenuItem": "菜单项渲染标签",
"AttributeResponsive": "菜单对齐",
"AttributeShowSplit": "分裂式按钮下拉菜单",
+ "AttributeIsAsync": "是否为异步按钮",
"AttributeSize": "尺寸",
"AttributeTagName": "标签",
"AttributeButtonTemplate": "按钮模板",
diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj
index 08ca21995af..569ead45b1f 100644
--- a/src/BootstrapBlazor/BootstrapBlazor.csproj
+++ b/src/BootstrapBlazor/BootstrapBlazor.csproj
@@ -1,7 +1,7 @@
- 9.3.1-beta09
+ 9.3.1-beta10
diff --git a/src/BootstrapBlazor/Components/Button/Button.razor.cs b/src/BootstrapBlazor/Components/Button/Button.razor.cs
index c501bca497f..4c123da58f0 100644
--- a/src/BootstrapBlazor/Components/Button/Button.razor.cs
+++ b/src/BootstrapBlazor/Components/Button/Button.razor.cs
@@ -18,26 +18,11 @@ public partial class Button : ButtonBase
[Parameter]
public bool IsAutoFocus { get; set; }
- ///
- /// 按钮点击回调方法,内置支持 IsAsync 开关
- ///
- protected EventCallback OnClickButton { get; set; }
-
///
/// 获得/设置 html button 实例
///
protected ElementReference ButtonElement { get; set; }
- ///
- /// OnInitialized 方法
- ///
- protected override void OnInitialized()
- {
- base.OnInitialized();
-
- SetClickHandler();
- }
-
///
/// OnAfterRenderAsync 方法
///
@@ -57,36 +42,33 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
}
///
- /// 设置 OnClickButton 方法
+ /// OnClickButton 方法
///
- protected virtual void SetClickHandler()
+ protected virtual async Task OnClickButton()
{
- OnClickButton = EventCallback.Factory.Create(this, async () =>
+ if (IsAsync && ButtonType == ButtonType.Button)
{
- if (IsAsync && ButtonType == ButtonType.Button)
- {
- IsAsyncLoading = true;
- ButtonIcon = LoadingIcon;
- IsDisabled = true;
- }
+ IsAsyncLoading = true;
+ ButtonIcon = LoadingIcon;
+ IsDisabled = true;
+ }
- if (IsAsync)
- {
- await Task.Run(() => InvokeAsync(HandlerClick));
- }
- else
- {
- await HandlerClick();
- }
+ if (IsAsync)
+ {
+ await Task.Run(() => InvokeAsync(HandlerClick));
+ }
+ else
+ {
+ await HandlerClick();
+ }
- // 恢复按钮
- if (IsAsync && ButtonType == ButtonType.Button)
- {
- ButtonIcon = Icon;
- IsDisabled = IsKeepDisabled;
- IsAsyncLoading = false;
- }
- });
+ // 恢复按钮
+ if (IsAsync && ButtonType == ButtonType.Button)
+ {
+ ButtonIcon = Icon;
+ IsDisabled = IsKeepDisabled;
+ IsAsyncLoading = false;
+ }
}
///
@@ -107,7 +89,7 @@ protected virtual async Task HandlerClick()
{
IsNotRender = true;
}
- await OnClickWithoutRender.Invoke();
+ await OnClickWithoutRender();
}
if (OnClick.HasDelegate)
{
diff --git a/src/BootstrapBlazor/Components/Button/CountButton.cs b/src/BootstrapBlazor/Components/Button/CountButton.cs
index b585069610f..13252a6a38e 100644
--- a/src/BootstrapBlazor/Components/Button/CountButton.cs
+++ b/src/BootstrapBlazor/Components/Button/CountButton.cs
@@ -3,8 +3,6 @@
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
-using Microsoft.AspNetCore.Components.Web;
-
namespace BootstrapBlazor.Components;
///
@@ -33,21 +31,18 @@ public class CountButton : Button
///
///
///
- protected override void SetClickHandler()
+ protected override async Task OnClickButton()
{
- OnClickButton = EventCallback.Factory.Create(this, async () =>
- {
- IsAsyncLoading = true;
- ButtonIcon = LoadingIcon;
- IsDisabled = true;
+ IsAsyncLoading = true;
+ ButtonIcon = LoadingIcon;
+ IsDisabled = true;
- await Task.Run(() => InvokeAsync(HandlerClick));
- await UpdateCount();
+ await Task.Run(() => InvokeAsync(HandlerClick));
+ await UpdateCount();
- IsDisabled = false;
- ButtonIcon = Icon;
- IsAsyncLoading = false;
- });
+ IsDisabled = false;
+ ButtonIcon = Icon;
+ IsAsyncLoading = false;
}
///
diff --git a/src/BootstrapBlazor/Components/Dropdown/Dropdown.razor b/src/BootstrapBlazor/Components/Dropdown/Dropdown.razor
index c735da5155b..354740d440d 100644
--- a/src/BootstrapBlazor/Components/Dropdown/Dropdown.razor
+++ b/src/BootstrapBlazor/Components/Dropdown/Dropdown.razor
@@ -5,14 +5,18 @@
@if (IsShowLabel)
{
-
+
}
@if (ButtonTemplate == null)
{
- @ButtonText
+ @if (!string.IsNullOrEmpty(_buttonIcon))
+ {
+
+ }
+ @ButtonText
}
else
{
diff --git a/src/BootstrapBlazor/Components/Dropdown/Dropdown.razor.cs b/src/BootstrapBlazor/Components/Dropdown/Dropdown.razor.cs
index bf9e226662d..c64c8a3a384 100644
--- a/src/BootstrapBlazor/Components/Dropdown/Dropdown.razor.cs
+++ b/src/BootstrapBlazor/Components/Dropdown/Dropdown.razor.cs
@@ -38,10 +38,11 @@ public partial class Dropdown
///
///
private string? ClassName => CssBuilder.Default("btn dropdown-toggle")
- .AddClass("dropdown-toggle-split")
- .AddClass($"btn-{Color.ToDescriptionString()}", Color != Color.None)
- .AddClass($"btn-{Size.ToDescriptionString()}", Size != Size.None)
- .Build();
+ .AddClass("dropdown-toggle-split")
+ .AddClass($"btn-primary", Color == Color.None)
+ .AddClass($"btn-{Color.ToDescriptionString()}", Color != Color.None)
+ .AddClass($"btn-{Size.ToDescriptionString()}", Size != Size.None)
+ .Build();
///
/// 获得 是否分裂式按钮
@@ -106,16 +107,45 @@ public partial class Dropdown
///
/// 获得/设置 OnClick 事件
+ /// 为 true 时生效
///
[Parameter]
public EventCallback OnClick { get; set; }
///
/// 获得/设置 OnClick 事件不刷新父组件
+ /// 为 true 时生效
///
[Parameter]
public Func? OnClickWithoutRender { get; set; }
+ ///
+ /// 获得/设置 是否为异步按钮,默认为 false 如果为 true 表示是异步按钮,点击按钮后禁用自身并且等待异步完成,过程中显示 loading 动画
+ /// 为 true 时生效
+ ///
+ [Parameter]
+ public bool IsAsync { get; set; }
+
+ ///
+ /// 获得/设置 是否异步结束后是否保持禁用状态,默认为 false
+ ///
+ /// 开启时有效
+ [Parameter]
+ public bool IsKeepDisabled { get; set; }
+
+ ///
+ /// 获得/设置 显示图标
+ ///
+ [Parameter]
+ public string? Icon { get; set; }
+
+ ///
+ /// 获得/设置 正在加载动画图标 默认为 fa-solid fa-spin fa-spinner
+ ///
+ [Parameter]
+ [NotNull]
+ public string? LoadingIcon { get; set; }
+
///
/// 获得/设置 获取菜单对齐方式 默认 none 未设置
///
@@ -164,6 +194,13 @@ public partial class Dropdown
[Parameter]
public Func? OnSelectedItemChanged { get; set; }
+ ///
+ /// 获得 IconTheme 实例
+ ///
+ [Inject]
+ [NotNull]
+ protected IIconTheme? IconTheme { get; set; }
+
[NotNull]
private List? DataSource { get; set; }
@@ -172,6 +209,16 @@ public partial class Dropdown
///
private SelectedItem? SelectedItem { get; set; }
+ ///
+ /// 获得/设置 是否当前正在异步执行操作
+ ///
+ private bool _isAsyncLoading;
+
+ ///
+ /// 获得/设置 实际按钮渲染图标
+ ///
+ protected string? _buttonIcon { get; set; }
+
///
/// OnParametersSet 方法
///
@@ -194,6 +241,12 @@ protected override void OnParametersSet()
?? DataSource.FirstOrDefault();
FixedButtonText ??= SelectedItem?.Text;
+ LoadingIcon ??= IconTheme.GetIconByKey(ComponentIcons.ButtonLoadingIcon);
+
+ if (_isAsyncLoading == false)
+ {
+ _buttonIcon = Icon;
+ }
}
private IEnumerable GetItems() => (IsFixedButtonText && !ShowFixedButtonTextInDropdown)
@@ -220,6 +273,35 @@ protected async Task OnItemClick(SelectedItem item)
private async Task OnClickButton()
{
+ if (IsAsync)
+ {
+ _isAsyncLoading = true;
+ _buttonIcon = LoadingIcon;
+ IsDisabled = true;
+ StateHasChanged();
+ await Task.Yield();
+ }
+
+ await HandlerClick();
+
+ // 恢复按钮
+ if (IsAsync)
+ {
+ _buttonIcon = Icon;
+ IsDisabled = IsKeepDisabled;
+ _isAsyncLoading = false;
+ }
+ StateHasChanged();
+ }
+
+
+ ///
+ /// 处理点击方法
+ ///
+ ///
+ private async Task HandlerClick()
+ {
+ IsNotRender = true;
if (OnClickWithoutRender != null)
{
await OnClickWithoutRender();
diff --git a/src/BootstrapBlazor/Components/Filters/FilterButton.razor.cs b/src/BootstrapBlazor/Components/Filters/FilterButton.razor.cs
index bea392b73b4..396d0249353 100644
--- a/src/BootstrapBlazor/Components/Filters/FilterButton.razor.cs
+++ b/src/BootstrapBlazor/Components/Filters/FilterButton.razor.cs
@@ -28,10 +28,6 @@ public partial class FilterButton : Dropdown
[Parameter]
public string? ClearIcon { get; set; }
- [Inject]
- [NotNull]
- private IIconTheme? IconTheme { get; set; }
-
///
///
///
diff --git a/test/UnitTest/Components/DropdownTest.cs b/test/UnitTest/Components/DropdownTest.cs
index 970c997f7be..aad1347ccca 100644
--- a/test/UnitTest/Components/DropdownTest.cs
+++ b/test/UnitTest/Components/DropdownTest.cs
@@ -33,6 +33,25 @@ public async Task ShowSplit_OK()
Assert.True(clickedWithoutRender);
}
+ [Fact]
+ public async Task IsAsync_Ok()
+ {
+ var cut = Context.RenderComponent>(pb =>
+ {
+ pb.Add(a => a.ShowSplit, true);
+ pb.Add(a => a.IsAsync, true);
+ pb.Add(a => a.IsKeepDisabled, false);
+ pb.Add(a => a.Icon, "fa-solid fa-test-icon");
+ pb.Add(a => a.OnClickWithoutRender, () =>
+ {
+ return Task.CompletedTask;
+ });
+ });
+ cut.Contains("");
+ var button = cut.Find("button");
+ await cut.InvokeAsync(() => button.Click());
+ }
+
[Fact]
public void ShowSize_OK()
{