Skip to content

Commit 3f3bb1a

Browse files
authored
refactor(Checkbox): use server render instead of client render (#4723)
1 parent 57cb436 commit 3f3bb1a

File tree

12 files changed

+139
-196
lines changed

12 files changed

+139
-196
lines changed

src/BootstrapBlazor/Components/Checkbox/Checkbox.razor

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ else
2020
@code {
2121
RenderFragment RenderCheckbox =>
2222
@<div @attributes="AdditionalAttributes" class="@ClassString">
23-
<input class="@InputClassString" type="checkbox" id="@Id" disabled="@Disabled" checked="@CheckedString" data-bb-stop-propagation="@StopPropagationString" />
23+
<input type="checkbox" id="@Id" class="@InputClassString" disabled="@Disabled" checked="@CheckedString"
24+
@onclick="OnToggleClick" @onclick:stopPropagation="StopPropagation" @onclick:preventDefault="false" />
2425
@if (IsShowAfterLabel)
2526
{
2627
@RenderLabel

src/BootstrapBlazor/Components/Checkbox/Checkbox.razor.cs

Lines changed: 26 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ public partial class Checkbox<TValue> : ValidateBase<TValue>
1818
/// </summary>
1919
private string? ClassString => CssBuilder.Default("form-check")
2020
.AddClass("is-label", IsShowAfterLabel)
21-
.AddClass("is-checked", State == CheckboxState.Checked && !IsBoolean)
22-
.AddClass("is-indeterminate", State == CheckboxState.Indeterminate)
2321
.AddClass($"form-check-{Color.ToDescriptionString()}", Color != Color.None)
2422
.AddClass($"form-check-{Size.ToDescriptionString()}", Size != Size.None)
2523
.AddClass("disabled", IsDisabled)
@@ -100,8 +98,6 @@ public partial class Checkbox<TValue> : ValidateBase<TValue>
10098
[Parameter]
10199
public bool StopPropagation { get; set; }
102100

103-
private string? StopPropagationString => StopPropagation ? "true" : null;
104-
105101
/// <summary>
106102
/// <inheritdoc/>
107103
/// </summary>
@@ -150,56 +146,52 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
150146
{
151147
await base.OnAfterRenderAsync(firstRender);
152148

153-
await InvokeVoidAsync("setIndeterminate", Id, State == CheckboxState.Indeterminate);
149+
await InvokeVoidAsync("update", Id, State == CheckboxState.Indeterminate, State == CheckboxState.Checked);
154150
}
155151

156152
/// <summary>
157153
/// <inheritdoc/>
158154
/// </summary>
159155
/// <returns></returns>
160-
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(OnTriggerClickAsync));
161-
162-
/// <summary>
163-
/// 触发 Click 方法
164-
/// </summary>
165-
/// <returns></returns>
166-
public async Task TriggerClick() => await OnTriggerClickAsync();
156+
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(OnStateChangedAsync));
167157

168158
/// <summary>
169-
/// 触发 Click 方法 由 JavaScript 调用
159+
/// 点击组件触发方法 内部调用 <see cref="OnBeforeStateChanged"/> 回调方法
170160
/// </summary>
171161
/// <returns></returns>
172-
[JSInvokable]
173-
public async ValueTask<bool> OnTriggerClickAsync(CheckboxState? state = null)
162+
public async Task OnToggleClick()
174163
{
175-
// 本组件由于支持 OnBeforeStateChanged 回调方法,所以设计上移除了 onclick 事件,改为通过 JS 调用 TriggerClick 方法
176-
// state 有值时表示同步状态功能
177-
if (state.HasValue)
164+
var valid = true;
165+
CheckboxState state;
166+
if (State == CheckboxState.Indeterminate)
178167
{
179-
State = state.Value;
180-
return true;
168+
state = CheckboxState.Checked;
169+
}
170+
else
171+
{
172+
state = State == CheckboxState.Checked ? CheckboxState.UnChecked : CheckboxState.Checked;
181173
}
182-
183-
// 调用 OnBeforeStateChanged 回调方法查看是否阻止状态改变
184-
// 返回 true 时改变状态,返回 false 时不改变状态阻止状态改变 preventDefault
185-
var val = State == CheckboxState.Checked ? CheckboxState.UnChecked : CheckboxState.Checked;
186174
if (OnBeforeStateChanged != null)
187175
{
188-
var ret = await OnBeforeStateChanged(val);
189-
if (ret == false)
190-
{
191-
// 阻止状态改变
192-
return false;
193-
}
176+
valid = await OnBeforeStateChanged(state);
194177
}
195178

196-
// 改变状态 由点击事件触发
197-
var render = await InternalStateChanged(val);
198-
if (render)
179+
if (valid)
199180
{
181+
await InternalStateChanged(state);
200182
StateHasChanged();
201183
}
202-
return true;
184+
}
185+
186+
/// <summary>
187+
/// 触发 Click 方法 由 JavaScript 调用
188+
/// </summary>
189+
/// <returns></returns>
190+
[JSInvokable]
191+
public ValueTask OnStateChangedAsync(CheckboxState state)
192+
{
193+
State = state;
194+
return ValueTask.CompletedTask;
203195
}
204196

205197
/// <summary>
@@ -251,19 +243,4 @@ public async Task SetState(CheckboxState state)
251243
}
252244
}
253245
}
254-
255-
/// <summary>
256-
/// <inheritdoc/>
257-
/// </summary>
258-
/// <param name="disposing"></param>
259-
/// <returns></returns>
260-
protected override async ValueTask DisposeAsync(bool disposing)
261-
{
262-
if (disposing && Module != null)
263-
{
264-
await Module.DisposeAsync();
265-
Module = null;
266-
}
267-
await base.DisposeAsync(disposing);
268-
}
269246
}

src/BootstrapBlazor/Components/Checkbox/Checkbox.razor.js

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,21 @@ export function init(id, invoke, method) {
77
return;
88
}
99

10-
EventHandler.on(el, 'click', async e => {
11-
e.preventDefault();
12-
const stopPropagation = el.getAttribute("data-bb-stop-propagation");
13-
if (stopPropagation === "true") {
14-
e.stopPropagation();
15-
}
16-
17-
const state = el.getAttribute("data-bb-state");
18-
let val = null;
19-
if (state) {
20-
val = state == "1" ? 0 : 1;
21-
el.removeAttribute('data-bb-state');
10+
EventHandler.on(el, 'statechange.bb.checkbox', e => {
11+
invoke.invokeMethodAsync(method, e.state);
12+
});
13+
}
2214

23-
if (state === "1") {
24-
el.parentElement.classList.remove('is-checked');
25-
}
26-
else {
27-
el.parentElement.classList.add('is-checked');
28-
}
29-
}
15+
export function update(id, state, val) {
16+
const el = document.getElementById(id);
17+
if (el === null) {
18+
return;
19+
}
3020

31-
const result = await invoke.invokeMethodAsync(method, val);
32-
return result;
33-
});
21+
setIndeterminate(id, state);
22+
if (state === false && el.checked !== val) {
23+
el.checked = val;
24+
}
3425
}
3526

3627
export function dispose(id) {
@@ -39,7 +30,5 @@ export function dispose(id) {
3930
return;
4031
}
4132

42-
EventHandler.off(el, 'click');
33+
EventHandler.off(el, 'statechange.bb.checkbox');
4334
}
44-
45-
export { setIndeterminate }

src/BootstrapBlazor/Components/TreeView/TreeView.razor.cs

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,7 @@ public partial class TreeView<TItem> : IModelEqualityComparer<TItem>
6565
.AddClass("disabled", GetItemDisabledState(item))
6666
.Build();
6767

68-
private bool CanTriggerClickNode(TreeViewItem<TItem> item)
69-
{
70-
// 返回 false 时禁止触发 OnClick
71-
if (IsDisabled)
72-
{
73-
return false;
74-
}
75-
76-
if (CanExpandWhenDisabled)
77-
{
78-
return true;
79-
}
80-
81-
return !item.IsDisabled;
82-
}
68+
private bool CanTriggerClickNode(TreeViewItem<TItem> item) => !IsDisabled && (CanExpandWhenDisabled || !item.IsDisabled);
8369

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

src/BootstrapBlazor/Extensions/ExpandableNodeExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ private static List<TItem> GetAllItems<TItem>(this IEnumerable<IExpandableNode<T
6565
/// </summary>
6666
public static void SetChildrenCheck<TNode, TItem>(this TNode node, TreeNodeCache<TNode, TItem> cache) where TNode : ICheckableNode<TItem>
6767
{
68+
if (node.CheckedState == CheckboxState.Indeterminate)
69+
{
70+
return;
71+
}
72+
6873
foreach (var item in node.Items.OfType<TNode>())
6974
{
7075
item.CheckedState = node.CheckedState;

test/UnitTest/Components/CheckboxListTest.cs

Lines changed: 13 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public void StopPropagation_Ok()
3434
{
3535
builder.Add(a => a.StopPropagation, true);
3636
});
37-
Assert.Contains("data-bb-stop-propagation=\"true\"", cut.Markup);
37+
Assert.Contains("blazor:onclick:stopPropagation", cut.Markup);
3838
}
3939

4040
[Fact]
@@ -69,11 +69,11 @@ public async Task Checkbox_OnBeforeStateChanged()
6969
});
7070
Assert.False(cut.Instance.Value);
7171

72-
await cut.InvokeAsync(cut.Instance.TriggerClick);
72+
await cut.InvokeAsync(cut.Instance.OnToggleClick);
7373
Assert.True(cut.Instance.Value);
7474

7575
confirm = false;
76-
await cut.InvokeAsync(cut.Instance.TriggerClick);
76+
await cut.InvokeAsync(cut.Instance.OnToggleClick);
7777
Assert.True(cut.Instance.Value);
7878
}
7979

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

86-
// JavaScript 调用 OnTriggerClickAsync 方法
87-
var val = await cut.Instance.OnTriggerClickAsync(CheckboxState.UnChecked);
88-
89-
Assert.True(val);
86+
// JavaScript 调用 OnStateChangedAsync 方法
87+
await cut.Instance.OnStateChangedAsync(CheckboxState.UnChecked);
9088
Assert.Equal(CheckboxState.UnChecked, cut.Instance.State);
9189

92-
val = await cut.Instance.OnTriggerClickAsync(CheckboxState.Checked);
93-
Assert.True(val);
94-
Assert.Equal(CheckboxState.Checked, cut.Instance.State);
95-
}
96-
97-
[Fact]
98-
public async Task Bool_TriggerStateChanged_Ok()
99-
{
100-
bool value = false;
101-
// 测试 bool 值改变值时触发 StateChanged 回调方法
102-
var cut = Context.RenderComponent<Checkbox<bool>>(pb =>
103-
{
104-
pb.Add(a => a.Value, false);
105-
pb.Add(a => a.OnStateChanged, (state, v) =>
106-
{
107-
value = v;
108-
return Task.CompletedTask;
109-
});
110-
});
111-
112-
// JavaScript 调用 OnTriggerClickAsync 方法
113-
await cut.InvokeAsync(() => cut.Instance.OnTriggerClickAsync());
90+
await cut.Instance.OnStateChangedAsync(CheckboxState.Checked);
11491
Assert.Equal(CheckboxState.Checked, cut.Instance.State);
115-
Assert.True(value);
11692
}
11793

11894
[Fact]
@@ -285,7 +261,7 @@ public async Task StringValue_Ok()
285261
});
286262
// 字符串值选中事件
287263
var item = cut.FindComponent<Checkbox<bool>>();
288-
await cut.InvokeAsync(item.Instance.TriggerClick);
264+
await cut.InvokeAsync(item.Instance.OnToggleClick);
289265
Assert.True(selected);
290266
}
291267

@@ -306,7 +282,7 @@ public async Task OnSelectedChanged_Ok()
306282
});
307283

308284
var item = cut.FindComponent<Checkbox<bool>>();
309-
await cut.InvokeAsync(item.Instance.TriggerClick);
285+
await cut.InvokeAsync(item.Instance.OnToggleClick);
310286
Assert.True(selected);
311287
}
312288

@@ -341,7 +317,7 @@ public async Task IntValue_Ok()
341317
});
342318
});
343319
var item = cut.FindComponent<Checkbox<bool>>();
344-
await cut.InvokeAsync(item.Instance.TriggerClick);
320+
await cut.InvokeAsync(item.Instance.OnToggleClick);
345321

346322
// 选中 2
347323
Assert.Equal(2, ret.First());
@@ -435,20 +411,20 @@ public async Task OnMaxSelectedCountExceed_Ok()
435411

436412
await cut.InvokeAsync(async () =>
437413
{
438-
await checkboxes[0].Instance.TriggerClick();
414+
await checkboxes[0].Instance.OnToggleClick();
439415
});
440416
Assert.Equal(CheckboxState.Checked, checkboxes[0].Instance.State);
441417

442418
await cut.InvokeAsync(async () =>
443419
{
444-
await checkboxes[1].Instance.TriggerClick();
420+
await checkboxes[1].Instance.OnToggleClick();
445421
});
446422
Assert.Equal(CheckboxState.Checked, checkboxes[1].Instance.State);
447423

448424
// 选中第三个由于限制无法选中
449425
await cut.InvokeAsync(async () =>
450426
{
451-
await checkboxes[2].Instance.TriggerClick();
427+
await checkboxes[2].Instance.OnToggleClick();
452428
});
453429
Assert.Equal(CheckboxState.Checked, checkboxes[0].Instance.State);
454430
Assert.Equal(CheckboxState.Checked, checkboxes[1].Instance.State);
@@ -459,7 +435,7 @@ await cut.InvokeAsync(async () =>
459435
max = false;
460436
await cut.InvokeAsync(async () =>
461437
{
462-
await checkboxes[0].Instance.TriggerClick();
438+
await checkboxes[0].Instance.OnToggleClick();
463439
});
464440
Assert.Equal(CheckboxState.UnChecked, checkboxes[0].Instance.State);
465441
Assert.Equal(CheckboxState.Checked, checkboxes[1].Instance.State);

test/UnitTest/Components/ConsoleTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ public async Task ClickAutoScroll_OK()
207207
});
208208

209209
var item = cut.FindComponent<Checkbox<bool>>();
210-
await cut.InvokeAsync(item.Instance.TriggerClick);
210+
await cut.InvokeAsync(item.Instance.OnToggleClick);
211211
var res = cut.Instance.IsAutoScroll;
212212
Assert.False(res);
213213
}

test/UnitTest/Components/TableDialogTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public async Task EditAsync_Ok()
6363
var table = cut.FindComponent<Table<Foo>>();
6464
// 选一个
6565
var checkbox = cut.FindComponents<Checkbox<Foo>>()[1];
66-
await cut.InvokeAsync(checkbox.Instance.TriggerClick);
66+
await cut.InvokeAsync(checkbox.Instance.OnToggleClick);
6767
await cut.InvokeAsync(() => table.Instance.EditAsync());
6868

6969
cut.Contains("test-save");
@@ -363,7 +363,7 @@ public async Task Required_Ok()
363363

364364
// 选一个
365365
var item = cut.FindComponent<Checkbox<Foo>>();
366-
await cut.InvokeAsync(item.Instance.TriggerClick);
366+
await cut.InvokeAsync(item.Instance.OnToggleClick);
367367
await cut.InvokeAsync(() => table.Instance.AddAsync());
368368

369369
var form = cut.Find(".modal-body form");

test/UnitTest/Components/TableDrawerTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public async Task EditAsync_Ok()
4444
var table = cut.FindComponent<Table<Foo>>();
4545
// 选一个
4646
var checkbox = cut.FindComponents<Checkbox<Foo>>()[1];
47-
await cut.InvokeAsync(checkbox.Instance.TriggerClick);
47+
await cut.InvokeAsync(checkbox.Instance.OnToggleClick);
4848
await cut.InvokeAsync(() => table.Instance.EditAsync());
4949

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

0 commit comments

Comments
 (0)