mirror of
https://gitee.com/LongbowEnterprise/BootstrapBlazor.git
synced 2024-11-30 02:58:37 +08:00
feat(Checkbox): add OnBeforeStateChanged parameter (#4005)
* feat: 增加 OnBeforeStateChanged 回调方法 * refactor: 增加客户端阻止逻辑 * refactor: 增加阻止继承标签 * feat: 支持 OnBeforeStateChanged 回调方法 * doc: 格式化代码 * doc: 增加示例文档 * refactor: 重构代码 * test: 更新单元测试
This commit is contained in:
parent
e9e23e66fe
commit
fbfa97c528
@ -99,7 +99,6 @@
|
||||
<Checkbox TValue="string" State="CheckboxState.Checked" ShowAfterLabel="true" DisplayText="ExtraExtraLarge" Size="Size.ExtraExtraLarge" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</DemoBlock>
|
||||
|
||||
<DemoBlock Title="@Localizer["OnStateChangedTitle"]" Introduction="@Localizer["OnStateChangedIntro"]" Name="OnStateChanged">
|
||||
@ -114,6 +113,17 @@
|
||||
<ConsoleLogger @ref="OnStateChangedLogger" />
|
||||
</DemoBlock>
|
||||
|
||||
<DemoBlock Title="@Localizer["OnBeforeStateChangedTitle"]" Introduction="@Localizer["OnBeforeStateChangedIntro"]" Name="OnBeforeStateChanged">
|
||||
<div class="row g-3 form-inline">
|
||||
<div class="col-12 col-sm-6">
|
||||
<Checkbox TValue="bool" DisplayText="@Localizer["OnBeforeStateChangedText"]" ShowLabel="true" @bind-Value="SelectedValue" OnBeforeStateChanged="@OnBeforeStateChanged" />
|
||||
</div>
|
||||
<div class="col-12 col-sm-6">
|
||||
<Display @bind-Value="@SelectedValue" />
|
||||
</div>
|
||||
</div>
|
||||
</DemoBlock>
|
||||
|
||||
<DemoBlock Title="@Localizer["BindStringTitle"]" Introduction="@Localizer["BindStringIntro"]" Name="BindString">
|
||||
<div class="row g-3 form-inline">
|
||||
<div class="col-12">
|
||||
|
@ -11,6 +11,9 @@ namespace BootstrapBlazor.Server.Components.Samples;
|
||||
/// </summary>
|
||||
public sealed partial class Checkboxs
|
||||
{
|
||||
[Inject, NotNull]
|
||||
private SwalService? SwalService { get; set; }
|
||||
|
||||
private Foo Model { get; set; } = new Foo();
|
||||
|
||||
private class Foo
|
||||
@ -53,6 +56,14 @@ public sealed partial class Checkboxs
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private bool SelectedValue { get; set; }
|
||||
|
||||
private Task<bool> OnBeforeStateChanged(CheckboxState state) => SwalService.ShowModal(new SwalOption()
|
||||
{
|
||||
Title = Localizer["OnBeforeStateChangedSwalTitle"],
|
||||
Content = Localizer["OnBeforeStateChangedSwalContent"]
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// GetAttributes
|
||||
/// </summary>
|
||||
@ -107,16 +118,22 @@ public sealed partial class Checkboxs
|
||||
/// <returns></returns>
|
||||
private EventItem[] GetEvents() =>
|
||||
[
|
||||
new()
|
||||
{
|
||||
Name = "OnBeforeStateChanged",
|
||||
Description = Localizer["OnBeforeStateChanged"],
|
||||
Type ="Action<CheckboxState, TItem>"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "OnStateChanged",
|
||||
Description = Localizer["Event1"],
|
||||
Description = Localizer["OnStateChanged"],
|
||||
Type ="Action<CheckboxState, TItem>"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "StateChanged",
|
||||
Description = Localizer["Event2"],
|
||||
Description = Localizer["StateChanged"],
|
||||
Type ="EventCallback<CheckboxState>"
|
||||
}
|
||||
];
|
||||
|
@ -2366,13 +2366,19 @@
|
||||
"ValidateFormTitle": "Used in forms",
|
||||
"ValidateFormIntro": "When you use <code>Checkbox</code> in a form, the display label text is placed in front of the component",
|
||||
"ValidateFormDescription": "The pre-label explicit rules are consistent with the <code>BootstrapInput</code> component <a href='input'>[portal]</a>",
|
||||
"OnBeforeStateChangedTitle": "OnBeforeStateChanged",
|
||||
"OnBeforeStateChangedIntro": "By setting the <code>OnBeforeStateChanged</code> callback method, you can cancel the state change logic",
|
||||
"OnBeforeStateChangedText": "Confirm",
|
||||
"OnBeforeStateChangedSwalTitle": "Confirm Select",
|
||||
"OnBeforeStateChangedSwalContent": "Whether the current checkbox is selected",
|
||||
"Att1": "Whether to display the front label",
|
||||
"Att2": "Whether to display the rear label",
|
||||
"Att3": "The front label displays text",
|
||||
"Att4": "Whether to disable it",
|
||||
"Att5": "The type of control",
|
||||
"Event1": "This method is called back when the selection box state changes",
|
||||
"Event2": "The state changes the callback method",
|
||||
"OnBeforeStateChanged": "This method is called back before the selection box status changes",
|
||||
"OnStateChanged": "This method is called back when the selection box state changes",
|
||||
"StateChanged": "The state changes the callback method",
|
||||
"StatusText1": "Selected",
|
||||
"StatusText2": "Not selected",
|
||||
"StatusText3": "Indeterminate",
|
||||
|
@ -2366,13 +2366,19 @@
|
||||
"ValidateFormTitle": "表单中使用",
|
||||
"ValidateFormIntro": "在表单中使用 <code>Checkbox</code> 时,显示标签文字会放置到组件前面",
|
||||
"ValidateFormDescription": "前置标签显式规则与 <code>BootstrapInput</code> 组件一致 <a href='input'>[传送门]</a>",
|
||||
"OnBeforeStateChangedTitle": "选中前回调方法",
|
||||
"OnBeforeStateChangedIntro": "通过设置 <code>OnBeforeStateChanged</code> 回调方法,可取消选中逻辑",
|
||||
"OnBeforeStateChangedText": "弹窗确认",
|
||||
"OnBeforeStateChangedSwalTitle": "弹窗确认",
|
||||
"OnBeforeStateChangedSwalContent": "是否更改选中状态",
|
||||
"Att1": "是否显示前置标签",
|
||||
"Att2": "是否显示后置标签",
|
||||
"Att3": "前置标签显示文本",
|
||||
"Att4": "是否禁用",
|
||||
"Att5": "控件类型",
|
||||
"Event1": "选择框状态改变时回调此方法",
|
||||
"Event2": "State 状态改变回调方法",
|
||||
"OnBeforeStateChanged": "选择框状态改变前回调此方法",
|
||||
"OnStateChanged": "选择框状态改变时回调此方法",
|
||||
"StateChanged": "State 状态改变回调方法",
|
||||
"StatusText1": "选中",
|
||||
"StatusText2": "未选",
|
||||
"StatusText3": "不确定",
|
||||
|
@ -20,7 +20,7 @@ else
|
||||
@code {
|
||||
RenderFragment RenderCheckbox =>
|
||||
@<div @attributes="AdditionalAttributes" class="@ClassString">
|
||||
<DynamicElement TagName="input" class="@InputClassString" type="checkbox" id="@Id" disabled="@Disabled" checked="@CheckedString" TriggerClick="!IsDisabled" OnClick="OnToggleClick" StopPropagation="StopPropagation" />
|
||||
<DynamicElement TagName="input" class="@InputClassString" type="checkbox" id="@Id" disabled="@Disabled" checked="@CheckedString" TriggerClick="TriggerClick" OnClick="OnToggleClick" StopPropagation="StopPropagation" />
|
||||
@if (IsShowAfterLabel)
|
||||
{
|
||||
@RenderLabel
|
||||
|
@ -9,7 +9,7 @@ namespace BootstrapBlazor.Components;
|
||||
/// <summary>
|
||||
/// Checkbox 组件
|
||||
/// </summary>
|
||||
[BootstrapModuleAutoLoader(ModuleName = "utility", AutoInvokeInit = false, AutoInvokeDispose = false)]
|
||||
[BootstrapModuleAutoLoader(JSObjectReference = true)]
|
||||
public partial class Checkbox<TValue> : ValidateBase<TValue>
|
||||
{
|
||||
/// <summary>
|
||||
@ -81,6 +81,12 @@ public partial class Checkbox<TValue> : ValidateBase<TValue>
|
||||
[Parameter]
|
||||
public EventCallback<CheckboxState> StateChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 选中状态改变前回调此方法 返回 false 可以阻止状态改变
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<CheckboxState, Task<bool>>? OnBeforeStateChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 选择框状态改变时回调此方法
|
||||
/// </summary>
|
||||
@ -143,6 +149,41 @@ public partial class Checkbox<TValue> : ValidateBase<TValue>
|
||||
await InvokeVoidAsync("setIndeterminate", Id, State == CheckboxState.Indeterminate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override async Task InvokeInitAsync()
|
||||
{
|
||||
if (OnBeforeStateChanged != null)
|
||||
{
|
||||
await InvokeVoidAsync("init", Id, Interop, new { Callback = nameof(TriggerOnBeforeStateChanged) });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发 OnBeforeStateChanged 回调方法 由 JavaScript 调用
|
||||
/// </summary>
|
||||
/// <param name="v"></param>
|
||||
/// <returns></returns>
|
||||
[JSInvokable]
|
||||
public async Task TriggerOnBeforeStateChanged()
|
||||
{
|
||||
if (OnBeforeStateChanged != null)
|
||||
{
|
||||
var state = State == CheckboxState.Checked ? CheckboxState.UnChecked : CheckboxState.Checked;
|
||||
var ret = await OnBeforeStateChanged(state);
|
||||
if (ret)
|
||||
{
|
||||
var render = await InternalStateChanged(state);
|
||||
if (render)
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 点击选择框方法
|
||||
/// </summary>
|
||||
@ -150,20 +191,23 @@ public partial class Checkbox<TValue> : ValidateBase<TValue>
|
||||
{
|
||||
if (!IsDisabled)
|
||||
{
|
||||
_paddingStateChanged = true;
|
||||
await InternalStateChanged(State == CheckboxState.Checked ? CheckboxState.UnChecked : CheckboxState.Checked);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TriggerClick => !IsDisabled && OnBeforeStateChanged == null;
|
||||
|
||||
/// <summary>
|
||||
/// 此变量为了提高性能,避免循环更新
|
||||
/// </summary>
|
||||
private bool _paddingStateChanged;
|
||||
|
||||
private async Task InternalStateChanged(CheckboxState state)
|
||||
{
|
||||
if (_paddingStateChanged)
|
||||
private async Task<bool> InternalStateChanged(CheckboxState state)
|
||||
{
|
||||
var ret = true;
|
||||
|
||||
_paddingStateChanged = true;
|
||||
|
||||
if (IsBoolean)
|
||||
{
|
||||
CurrentValue = (TValue)(object)(state == CheckboxState.Checked);
|
||||
@ -175,14 +219,16 @@ public partial class Checkbox<TValue> : ValidateBase<TValue>
|
||||
if (StateChanged.HasDelegate)
|
||||
{
|
||||
await StateChanged.InvokeAsync(State);
|
||||
ret = false;
|
||||
}
|
||||
|
||||
if (OnStateChanged != null)
|
||||
{
|
||||
await OnStateChanged.Invoke(State, Value);
|
||||
}
|
||||
await OnStateChanged(State, Value);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -193,12 +239,13 @@ public partial class Checkbox<TValue> : ValidateBase<TValue>
|
||||
{
|
||||
if (!_paddingStateChanged)
|
||||
{
|
||||
_paddingStateChanged = true;
|
||||
|
||||
await InternalStateChanged(state);
|
||||
var render = await InternalStateChanged(state);
|
||||
if (render)
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
|
25
src/BootstrapBlazor/Components/Checkbox/Checkbox.razor.js
Normal file
25
src/BootstrapBlazor/Components/Checkbox/Checkbox.razor.js
Normal file
@ -0,0 +1,25 @@
|
||||
import { setIndeterminate } from "../../modules/utility.js"
|
||||
import EventHandler from "../../modules/event-handler.js"
|
||||
|
||||
export function init(id, invoke, options) {
|
||||
const el = document.getElementById(id);
|
||||
if (el === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
EventHandler.on(el, 'click', async e => {
|
||||
e.preventDefault();
|
||||
await invoke.invokeMethodAsync(options.callback);
|
||||
})
|
||||
}
|
||||
|
||||
export function dispose(id) {
|
||||
const el = document.getElementById(id);
|
||||
if (el === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
EventHandler.off(el, 'click');
|
||||
}
|
||||
|
||||
export { setIndeterminate }
|
@ -7,6 +7,7 @@ namespace BootstrapBlazor.Components;
|
||||
/// <summary>
|
||||
/// Radio 单选框组件
|
||||
/// </summary>
|
||||
[JSModuleNotInherited]
|
||||
public partial class Radio<TValue> : Checkbox<TValue>
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -47,6 +47,27 @@ public class CheckboxListTest : BootstrapBlazorTestBase
|
||||
span.MarkupMatches("<span tabindex=\"0\" diff:ignore data-bs-original-title=\"Test\" data-bs-toggle=\"tooltip\" data-bs-placement=\"top\" data-bs-trigger=\"focus hover\">Test</span>");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Checkbox_OnBeforeStateChanged()
|
||||
{
|
||||
var confirm = true;
|
||||
var cut = Context.RenderComponent<Checkbox<bool>>(builder =>
|
||||
{
|
||||
builder.Add(a => a.OnBeforeStateChanged, state =>
|
||||
{
|
||||
return Task.FromResult(confirm);
|
||||
});
|
||||
});
|
||||
Assert.False(cut.Instance.Value);
|
||||
|
||||
await cut.InvokeAsync(cut.Instance.TriggerOnBeforeStateChanged);
|
||||
Assert.True(cut.Instance.Value);
|
||||
|
||||
confirm = false;
|
||||
await cut.InvokeAsync(cut.Instance.TriggerOnBeforeStateChanged);
|
||||
Assert.True(cut.Instance.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Checkbox_Dispose()
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user