Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Checkbox): use client click instead of server event #4620

Merged
merged 13 commits into from
Nov 7, 2024
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/Components/Checkbox/Checkbox.razor
Original file line number Diff line number Diff line change
Expand Up @@ -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" data-bb-trigger-before="@TriggerBeforeValueString" TriggerClick="TriggerClick" OnClick="OnToggleClick" StopPropagation="StopPropagation" />
<input class="@InputClassString" type="checkbox" id="@Id" disabled="@Disabled" checked="@CheckedString" data-bb-trigger-before="@TriggerBeforeValueString" data-bb-stop-propagation="@StopPropagationString" />
@if (IsShowAfterLabel)
{
@RenderLabel
Expand Down
51 changes: 32 additions & 19 deletions src/BootstrapBlazor/Components/Checkbox/Checkbox.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ public partial class Checkbox<TValue> : ValidateBase<TValue>
[Parameter]
public bool StopPropagation { get; set; }

private string? StopPropagationString => StopPropagation ? "true" : null;

private string? TriggerBeforeValueString => OnBeforeStateChanged == null ? null : "true";

/// <summary>
Expand Down Expand Up @@ -157,46 +159,57 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new { Callback = nameof(TriggerOnBeforeStateChanged) });
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new
ArgoZhang marked this conversation as resolved.
Show resolved Hide resolved
{
TriggerOnBeforeStateChanged = nameof(TriggerOnBeforeStateChanged),
TriggerClick = nameof(TriggerClick),
SyncStateCallback = nameof(SyncStateCallback)
});

private CheckboxState NextState => State == CheckboxState.Checked ? CheckboxState.UnChecked : CheckboxState.Checked;

/// <summary>
/// 触发 OnBeforeStateChanged 回调方法 由 JavaScript 调用
/// </summary>
[JSInvokable]
public async Task TriggerOnBeforeStateChanged()
public async ValueTask TriggerOnBeforeStateChanged()
ArgoZhang marked this conversation as resolved.
Show resolved Hide resolved
{
if (OnBeforeStateChanged != null)
{
var state = State == CheckboxState.Checked ? CheckboxState.UnChecked : CheckboxState.Checked;
var ret = await OnBeforeStateChanged(state);
var ret = await OnBeforeStateChanged(NextState);
if (ret)
{
var render = await InternalStateChanged(state);
if (render)
{
StateHasChanged();
}
await TriggerClick();
}
}
}

/// <summary>
/// 点击选择框方法
/// 同步 <see cref="State"/> 值方法 由 JavaScript 调用
/// </summary>
/// <param name="state"></param>
/// <returns></returns>
[JSInvokable]
public ValueTask SyncStateCallback(CheckboxState state)
{
State = state;
return ValueTask.CompletedTask;
}

/// <summary>
/// 触发 Click 方法 由 JavaScript 调用
/// </summary>
private async Task OnToggleClick()
/// <returns></returns>
[JSInvokable]
public async ValueTask TriggerClick()
{
if (!IsDisabled)
var render = await InternalStateChanged(NextState);
if (render)
{
var render = await InternalStateChanged(State == CheckboxState.Checked ? CheckboxState.UnChecked : CheckboxState.Checked);
if (render)
{
StateHasChanged();
}
StateHasChanged();
}
}

private bool TriggerClick => !IsDisabled && OnBeforeStateChanged == null;

/// <summary>
/// 此变量为了提高性能,避免循环更新
/// </summary>
Expand Down
29 changes: 27 additions & 2 deletions src/BootstrapBlazor/Components/Checkbox/Checkbox.razor.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,37 @@ export function init(id, invoke, options) {
}

EventHandler.on(el, 'click', async e => {
ArgoZhang marked this conversation as resolved.
Show resolved Hide resolved
e.preventDefault();
const stopPropagation = el.getAttribute("data-bb-stop-propagation");
if (stopPropagation === "true") {
e.stopPropagation();
}

const state = el.getAttribute("data-bb-state");
const trigger = el.getAttribute("data-bb-trigger-before");

if (state) {
el.removeAttribute('data-bb-state');
await invoke.invokeMethodAsync(options.syncStateCallback, parseInt(state));
ArgoZhang marked this conversation as resolved.
Show resolved Hide resolved

if (state === "1") {
ArgoZhang marked this conversation as resolved.
Show resolved Hide resolved
el.parentElement.classList.remove('is-checked');
}
else {
el.parentElement.classList.add('is-checked');
}

if (trigger !== "true") {
return;
}
}

if (trigger === 'true') {
await invoke.invokeMethodAsync(options.callback);
e.preventDefault();
await invoke.invokeMethodAsync(options.triggerOnBeforeStateChanged);
return;
}

await invoke.invokeMethodAsync(options.triggerClick);
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/Components/Table/Table.razor
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@
<label>@CheckboxDisplayText</label>
@if (GetShowRowCheckbox(item))
{
<Checkbox TValue="TItem" Value="@item" State="@RowCheckState(item)" OnStateChanged="OnCheck" @onclick:stopPropagation></Checkbox>
<Checkbox TValue="TItem" Value="@item" State="@RowCheckState(item)" OnStateChanged="OnCheck" StopPropagation="true"></Checkbox>
}
</div>
}
Expand Down
41 changes: 32 additions & 9 deletions test/UnitTest/Components/CheckboxListTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,29 @@ public void Checkbox_Ok()
Assert.DoesNotContain("is-label", cut.Markup);
}

[Fact]
public void StopPropagation_Ok()
{
var cut = Context.RenderComponent<Checkbox<string>>(builder =>
{
builder.Add(a => a.StopPropagation, true);
});
Assert.Contains("data-bb-stop-propagation=\"true\"", cut.Markup);
}

[Fact]
public async Task SyncStateCallback_Ok()
{
var cut = Context.RenderComponent<Checkbox<bool>>(builder =>
{
builder.Add(a => a.State, CheckboxState.UnChecked);
});
Assert.Equal(CheckboxState.UnChecked, cut.Instance.State);

await cut.InvokeAsync(() => cut.Instance.SyncStateCallback(CheckboxState.Checked));
Assert.Equal(CheckboxState.Checked, cut.Instance.State);
}

[Fact]
public void ShowAfterLabel_Ok()
{
Expand Down Expand Up @@ -208,7 +231,7 @@ public void CheckboxItemClass_Ok()
}

[Fact]
public void StringValue_Ok()
public async Task StringValue_Ok()
{
var cut = Context.RenderComponent<CheckboxList<string>>(builder =>
{
Expand Down Expand Up @@ -236,13 +259,13 @@ public void StringValue_Ok()
});
});
// 字符串值选中事件
var item = cut.Find(".form-check-input");
item.Click();
var item = cut.FindComponent<Checkbox<bool>>();
await cut.InvokeAsync(item.Instance.TriggerClick);
Assert.True(selected);
}

[Fact]
public void OnSelectedChanged_Ok()
public async Task OnSelectedChanged_Ok()
{
var selected = false;
var foo = Foo.Generate(Localizer);
Expand All @@ -257,8 +280,8 @@ public void OnSelectedChanged_Ok()
});
});

var item = cut.Find(".form-check-input");
item.Click();
var item = cut.FindComponent<Checkbox<bool>>();
await cut.InvokeAsync(item.Instance.TriggerClick);
Assert.True(selected);
}

Expand All @@ -274,7 +297,7 @@ public void EnumValue_Ok()
}

[Fact]
public void IntValue_Ok()
public async Task IntValue_Ok()
{
var ret = new List<int>();
var selectedIntValues = new List<int> { 1, 2 };
Expand All @@ -292,8 +315,8 @@ public void IntValue_Ok()
return Task.CompletedTask;
});
});
var item = cut.Find(".form-check-input");
item.Click();
var item = cut.FindComponent<Checkbox<bool>>();
await cut.InvokeAsync(item.Instance.TriggerClick);

// 选中 2
Assert.Equal(2, ret.First());
Expand Down
5 changes: 3 additions & 2 deletions test/UnitTest/Components/ConsoleTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ public void LightTitle_OK()
}

[Fact]
public void ClickAutoScroll_OK()
public async Task ClickAutoScroll_OK()
{
var cut = Context.RenderComponent<Console>(builder =>
{
Expand All @@ -206,7 +206,8 @@ public void ClickAutoScroll_OK()
builder.Add(a => a.ShowAutoScroll, true);
});

cut.Find(".card-footer input").Click();
var item = cut.FindComponent<Checkbox<bool>>();
await cut.InvokeAsync(item.Instance.TriggerClick);
var res = cut.Instance.IsAutoScroll;
Assert.False(res);
}
Expand Down
10 changes: 5 additions & 5 deletions test/UnitTest/Components/TableDialogTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ public async Task EditAsync_Ok()

var table = cut.FindComponent<Table<Foo>>();
// 选一个
var input = cut.Find("tbody tr input");
await cut.InvokeAsync(() => input.Click());
var checkbox = cut.FindComponents<Checkbox<Foo>>()[1];
await cut.InvokeAsync(checkbox.Instance.TriggerClick);
await cut.InvokeAsync(() => table.Instance.EditAsync());

cut.Contains("test-save");
Expand Down Expand Up @@ -128,7 +128,7 @@ public async Task EditAsync_Ok()
await cut.InvokeAsync(() => table.Instance.AddAsync());

// 编辑弹窗逻辑
input = cut.Find(".modal-body form input.form-control");
var input = cut.Find(".modal-body form input.form-control");
await cut.InvokeAsync(() => input.Change("Test_Name"));

form = cut.Find(".modal-body form");
Expand Down Expand Up @@ -362,8 +362,8 @@ public async Task Required_Ok()
var modal = cut.FindComponent<Modal>();

// 选一个
var input = cut.Find("tbody tr input");
await cut.InvokeAsync(() => input.Click());
var item = cut.FindComponent<Checkbox<Foo>>();
await cut.InvokeAsync(item.Instance.TriggerClick);
await cut.InvokeAsync(() => table.Instance.AddAsync());

var form = cut.Find(".modal-body form");
Expand Down
10 changes: 5 additions & 5 deletions test/UnitTest/Components/TableDrawerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ public async Task EditAsync_Ok()

var table = cut.FindComponent<Table<Foo>>();
// 选一个
var input = cut.Find("tbody tr input");
await cut.InvokeAsync(() => input.Click());
var checkbox = cut.FindComponents<Checkbox<Foo>>()[1];
await cut.InvokeAsync(checkbox.Instance.TriggerClick);
await cut.InvokeAsync(() => table.Instance.EditAsync());

// 编辑弹窗逻辑
Expand Down Expand Up @@ -102,8 +102,8 @@ public async Task EditAsync_Ok()
{
pb.Add(a => a.OnSaveAsync, (foo, itemType) => Task.FromResult(false));
});
input = cut.Find("tbody tr input");
await cut.InvokeAsync(() => input.Click());
checkbox = cut.FindComponents<Checkbox<Foo>>()[1];
await cut.InvokeAsync(checkbox.Instance.TriggerClick);
await cut.InvokeAsync(() => table.Instance.EditAsync());
form = cut.Find("form");
await cut.InvokeAsync(() => form.Submit());
Expand All @@ -119,7 +119,7 @@ public async Task EditAsync_Ok()
await cut.InvokeAsync(() => table.Instance.AddAsync());

// 编辑弹窗逻辑
input = cut.Find("form input.form-control");
var input = cut.Find("form input.form-control");
await cut.InvokeAsync(() => input.Change("Test_Name"));

form = cut.Find("form");
Expand Down
Loading