Skip to content

Commit

Permalink
refactor(Checkbox): use server render instead of client render (#4723)
Browse files Browse the repository at this point in the history
  • Loading branch information
ArgoZhang authored Nov 23, 2024
1 parent 57cb436 commit 3f3bb1a
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 196 deletions.
3 changes: 2 additions & 1 deletion src/BootstrapBlazor/Components/Checkbox/Checkbox.razor
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ else
@code {
RenderFragment RenderCheckbox =>
@<div @attributes="AdditionalAttributes" class="@ClassString">
<input class="@InputClassString" type="checkbox" id="@Id" disabled="@Disabled" checked="@CheckedString" data-bb-stop-propagation="@StopPropagationString" />
<input type="checkbox" id="@Id" class="@InputClassString" disabled="@Disabled" checked="@CheckedString"
@onclick="OnToggleClick" @onclick:stopPropagation="StopPropagation" @onclick:preventDefault="false" />
@if (IsShowAfterLabel)
{
@RenderLabel
Expand Down
75 changes: 26 additions & 49 deletions src/BootstrapBlazor/Components/Checkbox/Checkbox.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ public partial class Checkbox<TValue> : ValidateBase<TValue>
/// </summary>
private string? ClassString => CssBuilder.Default("form-check")
.AddClass("is-label", IsShowAfterLabel)
.AddClass("is-checked", State == CheckboxState.Checked && !IsBoolean)
.AddClass("is-indeterminate", State == CheckboxState.Indeterminate)
.AddClass($"form-check-{Color.ToDescriptionString()}", Color != Color.None)
.AddClass($"form-check-{Size.ToDescriptionString()}", Size != Size.None)
.AddClass("disabled", IsDisabled)
Expand Down Expand Up @@ -100,8 +98,6 @@ public partial class Checkbox<TValue> : ValidateBase<TValue>
[Parameter]
public bool StopPropagation { get; set; }

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

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down Expand Up @@ -150,56 +146,52 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);

await InvokeVoidAsync("setIndeterminate", Id, State == CheckboxState.Indeterminate);
await InvokeVoidAsync("update", Id, State == CheckboxState.Indeterminate, State == CheckboxState.Checked);
}

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(OnTriggerClickAsync));

/// <summary>
/// 触发 Click 方法
/// </summary>
/// <returns></returns>
public async Task TriggerClick() => await OnTriggerClickAsync();
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(OnStateChangedAsync));

/// <summary>
/// 触发 Click 方法 由 JavaScript 调用
/// 点击组件触发方法 内部调用 <see cref="OnBeforeStateChanged"/> 回调方法
/// </summary>
/// <returns></returns>
[JSInvokable]
public async ValueTask<bool> OnTriggerClickAsync(CheckboxState? state = null)
public async Task OnToggleClick()
{
// 本组件由于支持 OnBeforeStateChanged 回调方法,所以设计上移除了 onclick 事件,改为通过 JS 调用 TriggerClick 方法
// state 有值时表示同步状态功能
if (state.HasValue)
var valid = true;
CheckboxState state;
if (State == CheckboxState.Indeterminate)
{
State = state.Value;
return true;
state = CheckboxState.Checked;
}
else
{
state = State == CheckboxState.Checked ? CheckboxState.UnChecked : CheckboxState.Checked;
}

// 调用 OnBeforeStateChanged 回调方法查看是否阻止状态改变
// 返回 true 时改变状态,返回 false 时不改变状态阻止状态改变 preventDefault
var val = State == CheckboxState.Checked ? CheckboxState.UnChecked : CheckboxState.Checked;
if (OnBeforeStateChanged != null)
{
var ret = await OnBeforeStateChanged(val);
if (ret == false)
{
// 阻止状态改变
return false;
}
valid = await OnBeforeStateChanged(state);
}

// 改变状态 由点击事件触发
var render = await InternalStateChanged(val);
if (render)
if (valid)
{
await InternalStateChanged(state);
StateHasChanged();
}
return true;
}

/// <summary>
/// 触发 Click 方法 由 JavaScript 调用
/// </summary>
/// <returns></returns>
[JSInvokable]
public ValueTask OnStateChangedAsync(CheckboxState state)
{
State = state;
return ValueTask.CompletedTask;
}

/// <summary>
Expand Down Expand Up @@ -251,19 +243,4 @@ public async Task SetState(CheckboxState state)
}
}
}

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="disposing"></param>
/// <returns></returns>
protected override async ValueTask DisposeAsync(bool disposing)
{
if (disposing && Module != null)
{
await Module.DisposeAsync();
Module = null;
}
await base.DisposeAsync(disposing);
}
}
39 changes: 14 additions & 25 deletions src/BootstrapBlazor/Components/Checkbox/Checkbox.razor.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,21 @@ export function init(id, invoke, method) {
return;
}

EventHandler.on(el, 'click', async e => {
e.preventDefault();
const stopPropagation = el.getAttribute("data-bb-stop-propagation");
if (stopPropagation === "true") {
e.stopPropagation();
}

const state = el.getAttribute("data-bb-state");
let val = null;
if (state) {
val = state == "1" ? 0 : 1;
el.removeAttribute('data-bb-state');
EventHandler.on(el, 'statechange.bb.checkbox', e => {
invoke.invokeMethodAsync(method, e.state);
});
}

if (state === "1") {
el.parentElement.classList.remove('is-checked');
}
else {
el.parentElement.classList.add('is-checked');
}
}
export function update(id, state, val) {
const el = document.getElementById(id);
if (el === null) {
return;
}

const result = await invoke.invokeMethodAsync(method, val);
return result;
});
setIndeterminate(id, state);
if (state === false && el.checked !== val) {
el.checked = val;
}
}

export function dispose(id) {
Expand All @@ -39,7 +30,5 @@ export function dispose(id) {
return;
}

EventHandler.off(el, 'click');
EventHandler.off(el, 'statechange.bb.checkbox');
}

export { setIndeterminate }
16 changes: 1 addition & 15 deletions src/BootstrapBlazor/Components/TreeView/TreeView.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,7 @@ public partial class TreeView<TItem> : IModelEqualityComparer<TItem>
.AddClass("disabled", GetItemDisabledState(item))
.Build();

private bool CanTriggerClickNode(TreeViewItem<TItem> item)
{
// 返回 false 时禁止触发 OnClick
if (IsDisabled)
{
return false;
}

if (CanExpandWhenDisabled)
{
return true;
}

return !item.IsDisabled;
}
private bool CanTriggerClickNode(TreeViewItem<TItem> item) => !IsDisabled && (CanExpandWhenDisabled || !item.IsDisabled);

private bool TriggerNodeLabel(TreeViewItem<TItem> item) => !GetItemDisabledState(item);

Expand Down
5 changes: 5 additions & 0 deletions src/BootstrapBlazor/Extensions/ExpandableNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ private static List<TItem> GetAllItems<TItem>(this IEnumerable<IExpandableNode<T
/// </summary>
public static void SetChildrenCheck<TNode, TItem>(this TNode node, TreeNodeCache<TNode, TItem> cache) where TNode : ICheckableNode<TItem>
{
if (node.CheckedState == CheckboxState.Indeterminate)
{
return;
}

foreach (var item in node.Items.OfType<TNode>())
{
item.CheckedState = node.CheckedState;
Expand Down
50 changes: 13 additions & 37 deletions test/UnitTest/Components/CheckboxListTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void StopPropagation_Ok()
{
builder.Add(a => a.StopPropagation, true);
});
Assert.Contains("data-bb-stop-propagation=\"true\"", cut.Markup);
Assert.Contains("blazor:onclick:stopPropagation", cut.Markup);
}

[Fact]
Expand Down Expand Up @@ -69,11 +69,11 @@ public async Task Checkbox_OnBeforeStateChanged()
});
Assert.False(cut.Instance.Value);

await cut.InvokeAsync(cut.Instance.TriggerClick);
await cut.InvokeAsync(cut.Instance.OnToggleClick);
Assert.True(cut.Instance.Value);

confirm = false;
await cut.InvokeAsync(cut.Instance.TriggerClick);
await cut.InvokeAsync(cut.Instance.OnToggleClick);
Assert.True(cut.Instance.Value);
}

Expand All @@ -83,36 +83,12 @@ public async Task Checkbox_OnTriggerClickAsync()
var cut = Context.RenderComponent<Checkbox<bool>>();
Assert.False(cut.Instance.Value);

// JavaScript 调用 OnTriggerClickAsync 方法
var val = await cut.Instance.OnTriggerClickAsync(CheckboxState.UnChecked);

Assert.True(val);
// JavaScript 调用 OnStateChangedAsync 方法
await cut.Instance.OnStateChangedAsync(CheckboxState.UnChecked);
Assert.Equal(CheckboxState.UnChecked, cut.Instance.State);

val = await cut.Instance.OnTriggerClickAsync(CheckboxState.Checked);
Assert.True(val);
Assert.Equal(CheckboxState.Checked, cut.Instance.State);
}

[Fact]
public async Task Bool_TriggerStateChanged_Ok()
{
bool value = false;
// 测试 bool 值改变值时触发 StateChanged 回调方法
var cut = Context.RenderComponent<Checkbox<bool>>(pb =>
{
pb.Add(a => a.Value, false);
pb.Add(a => a.OnStateChanged, (state, v) =>
{
value = v;
return Task.CompletedTask;
});
});

// JavaScript 调用 OnTriggerClickAsync 方法
await cut.InvokeAsync(() => cut.Instance.OnTriggerClickAsync());
await cut.Instance.OnStateChangedAsync(CheckboxState.Checked);
Assert.Equal(CheckboxState.Checked, cut.Instance.State);
Assert.True(value);
}

[Fact]
Expand Down Expand Up @@ -285,7 +261,7 @@ public async Task StringValue_Ok()
});
// 字符串值选中事件
var item = cut.FindComponent<Checkbox<bool>>();
await cut.InvokeAsync(item.Instance.TriggerClick);
await cut.InvokeAsync(item.Instance.OnToggleClick);
Assert.True(selected);
}

Expand All @@ -306,7 +282,7 @@ public async Task OnSelectedChanged_Ok()
});

var item = cut.FindComponent<Checkbox<bool>>();
await cut.InvokeAsync(item.Instance.TriggerClick);
await cut.InvokeAsync(item.Instance.OnToggleClick);
Assert.True(selected);
}

Expand Down Expand Up @@ -341,7 +317,7 @@ public async Task IntValue_Ok()
});
});
var item = cut.FindComponent<Checkbox<bool>>();
await cut.InvokeAsync(item.Instance.TriggerClick);
await cut.InvokeAsync(item.Instance.OnToggleClick);

// 选中 2
Assert.Equal(2, ret.First());
Expand Down Expand Up @@ -435,20 +411,20 @@ public async Task OnMaxSelectedCountExceed_Ok()

await cut.InvokeAsync(async () =>
{
await checkboxes[0].Instance.TriggerClick();
await checkboxes[0].Instance.OnToggleClick();
});
Assert.Equal(CheckboxState.Checked, checkboxes[0].Instance.State);

await cut.InvokeAsync(async () =>
{
await checkboxes[1].Instance.TriggerClick();
await checkboxes[1].Instance.OnToggleClick();
});
Assert.Equal(CheckboxState.Checked, checkboxes[1].Instance.State);

// 选中第三个由于限制无法选中
await cut.InvokeAsync(async () =>
{
await checkboxes[2].Instance.TriggerClick();
await checkboxes[2].Instance.OnToggleClick();
});
Assert.Equal(CheckboxState.Checked, checkboxes[0].Instance.State);
Assert.Equal(CheckboxState.Checked, checkboxes[1].Instance.State);
Expand All @@ -459,7 +435,7 @@ await cut.InvokeAsync(async () =>
max = false;
await cut.InvokeAsync(async () =>
{
await checkboxes[0].Instance.TriggerClick();
await checkboxes[0].Instance.OnToggleClick();
});
Assert.Equal(CheckboxState.UnChecked, checkboxes[0].Instance.State);
Assert.Equal(CheckboxState.Checked, checkboxes[1].Instance.State);
Expand Down
2 changes: 1 addition & 1 deletion test/UnitTest/Components/ConsoleTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public async Task ClickAutoScroll_OK()
});

var item = cut.FindComponent<Checkbox<bool>>();
await cut.InvokeAsync(item.Instance.TriggerClick);
await cut.InvokeAsync(item.Instance.OnToggleClick);
var res = cut.Instance.IsAutoScroll;
Assert.False(res);
}
Expand Down
4 changes: 2 additions & 2 deletions test/UnitTest/Components/TableDialogTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public async Task EditAsync_Ok()
var table = cut.FindComponent<Table<Foo>>();
// 选一个
var checkbox = cut.FindComponents<Checkbox<Foo>>()[1];
await cut.InvokeAsync(checkbox.Instance.TriggerClick);
await cut.InvokeAsync(checkbox.Instance.OnToggleClick);
await cut.InvokeAsync(() => table.Instance.EditAsync());

cut.Contains("test-save");
Expand Down Expand Up @@ -363,7 +363,7 @@ public async Task Required_Ok()

// 选一个
var item = cut.FindComponent<Checkbox<Foo>>();
await cut.InvokeAsync(item.Instance.TriggerClick);
await cut.InvokeAsync(item.Instance.OnToggleClick);
await cut.InvokeAsync(() => table.Instance.AddAsync());

var form = cut.Find(".modal-body form");
Expand Down
4 changes: 2 additions & 2 deletions test/UnitTest/Components/TableDrawerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public async Task EditAsync_Ok()
var table = cut.FindComponent<Table<Foo>>();
// 选一个
var checkbox = cut.FindComponents<Checkbox<Foo>>()[1];
await cut.InvokeAsync(checkbox.Instance.TriggerClick);
await cut.InvokeAsync(checkbox.Instance.OnToggleClick);
await cut.InvokeAsync(() => table.Instance.EditAsync());

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

0 comments on commit 3f3bb1a

Please sign in to comment.