Skip to content

Commit 9fcd84c

Browse files
authored
feat(Checkbox): use client click instead of server event (#4620)
* refactor: 使用原生 checkbox 元素 * feat: 增加 TriggerClick 逻辑改为客户端触发 * feat: 实现客户端脚本逻辑 * feat: 增加父节点样式 * refactor: 移除父节点样式 * feat: 增加同步样式代码 * test: 更新单元测试 * test: 增加单元测试 * refactor: 精简代码 * feat: 增加 StopPropagation 支持 * refactor: 更新阻止冒泡逻辑 * test: 增加阻止冒泡单元测试 * test: 更新单元测试
1 parent cc22758 commit 9fcd84c

File tree

10 files changed

+164
-101
lines changed

10 files changed

+164
-101
lines changed

src/BootstrapBlazor/Components/Checkbox/Checkbox.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ else
2020
@code {
2121
RenderFragment RenderCheckbox =>
2222
@<div @attributes="AdditionalAttributes" class="@ClassString">
23-
<DynamicElement TagName="input" class="@InputClassString" type="checkbox" id="@Id" disabled="@Disabled" checked="@CheckedString" data-bb-trigger-before="@TriggerBeforeValueString" TriggerClick="TriggerClick" OnClick="OnToggleClick" StopPropagation="StopPropagation" />
23+
<input class="@InputClassString" type="checkbox" id="@Id" disabled="@Disabled" checked="@CheckedString" data-bb-trigger-before="@TriggerBeforeValueString" data-bb-stop-propagation="@StopPropagationString" />
2424
@if (IsShowAfterLabel)
2525
{
2626
@RenderLabel

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

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ public partial class Checkbox<TValue> : ValidateBase<TValue>
100100
[Parameter]
101101
public bool StopPropagation { get; set; }
102102

103+
private string? StopPropagationString => StopPropagation ? "true" : null;
104+
103105
private string? TriggerBeforeValueString => OnBeforeStateChanged == null ? null : "true";
104106

105107
/// <summary>
@@ -157,46 +159,57 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
157159
/// <inheritdoc/>
158160
/// </summary>
159161
/// <returns></returns>
160-
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new { Callback = nameof(TriggerOnBeforeStateChanged) });
162+
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new
163+
{
164+
TriggerOnBeforeStateChanged = nameof(TriggerOnBeforeStateChanged),
165+
TriggerClick = nameof(TriggerClick),
166+
SyncStateCallback = nameof(SyncStateCallback)
167+
});
168+
169+
private CheckboxState NextState => State == CheckboxState.Checked ? CheckboxState.UnChecked : CheckboxState.Checked;
161170

162171
/// <summary>
163172
/// 触发 OnBeforeStateChanged 回调方法 由 JavaScript 调用
164173
/// </summary>
165174
[JSInvokable]
166-
public async Task TriggerOnBeforeStateChanged()
175+
public async ValueTask TriggerOnBeforeStateChanged()
167176
{
168177
if (OnBeforeStateChanged != null)
169178
{
170-
var state = State == CheckboxState.Checked ? CheckboxState.UnChecked : CheckboxState.Checked;
171-
var ret = await OnBeforeStateChanged(state);
179+
var ret = await OnBeforeStateChanged(NextState);
172180
if (ret)
173181
{
174-
var render = await InternalStateChanged(state);
175-
if (render)
176-
{
177-
StateHasChanged();
178-
}
182+
await TriggerClick();
179183
}
180184
}
181185
}
182186

183187
/// <summary>
184-
/// 点击选择框方法
188+
/// 同步 <see cref="State"/> 值方法 由 JavaScript 调用
189+
/// </summary>
190+
/// <param name="state"></param>
191+
/// <returns></returns>
192+
[JSInvokable]
193+
public ValueTask SyncStateCallback(CheckboxState state)
194+
{
195+
State = state;
196+
return ValueTask.CompletedTask;
197+
}
198+
199+
/// <summary>
200+
/// 触发 Click 方法 由 JavaScript 调用
185201
/// </summary>
186-
private async Task OnToggleClick()
202+
/// <returns></returns>
203+
[JSInvokable]
204+
public async ValueTask TriggerClick()
187205
{
188-
if (!IsDisabled)
206+
var render = await InternalStateChanged(NextState);
207+
if (render)
189208
{
190-
var render = await InternalStateChanged(State == CheckboxState.Checked ? CheckboxState.UnChecked : CheckboxState.Checked);
191-
if (render)
192-
{
193-
StateHasChanged();
194-
}
209+
StateHasChanged();
195210
}
196211
}
197212

198-
private bool TriggerClick => !IsDisabled && OnBeforeStateChanged == null;
199-
200213
/// <summary>
201214
/// 此变量为了提高性能,避免循环更新
202215
/// </summary>

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,37 @@ export function init(id, invoke, options) {
88
}
99

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

16+
const state = el.getAttribute("data-bb-state");
1317
const trigger = el.getAttribute("data-bb-trigger-before");
18+
19+
if (state) {
20+
el.removeAttribute('data-bb-state');
21+
await invoke.invokeMethodAsync(options.syncStateCallback, parseInt(state));
22+
23+
if (state === "1") {
24+
el.parentElement.classList.remove('is-checked');
25+
}
26+
else {
27+
el.parentElement.classList.add('is-checked');
28+
}
29+
30+
if (trigger !== "true") {
31+
return;
32+
}
33+
}
34+
1435
if (trigger === 'true') {
15-
await invoke.invokeMethodAsync(options.callback);
36+
e.preventDefault();
37+
await invoke.invokeMethodAsync(options.triggerOnBeforeStateChanged);
38+
return;
1639
}
40+
41+
await invoke.invokeMethodAsync(options.triggerClick);
1742
});
1843
}
1944

src/BootstrapBlazor/Components/Table/Table.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@
241241
<label>@CheckboxDisplayText</label>
242242
@if (GetShowRowCheckbox(item))
243243
{
244-
<Checkbox TValue="TItem" Value="@item" State="@RowCheckState(item)" OnStateChanged="OnCheck" @onclick:stopPropagation></Checkbox>
244+
<Checkbox TValue="TItem" Value="@item" State="@RowCheckState(item)" OnStateChanged="OnCheck" StopPropagation="true"></Checkbox>
245245
}
246246
</div>
247247
}

test/UnitTest/Components/CheckboxListTest.cs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,29 @@ public void Checkbox_Ok()
2727
Assert.DoesNotContain("is-label", cut.Markup);
2828
}
2929

30+
[Fact]
31+
public void StopPropagation_Ok()
32+
{
33+
var cut = Context.RenderComponent<Checkbox<string>>(builder =>
34+
{
35+
builder.Add(a => a.StopPropagation, true);
36+
});
37+
Assert.Contains("data-bb-stop-propagation=\"true\"", cut.Markup);
38+
}
39+
40+
[Fact]
41+
public async Task SyncStateCallback_Ok()
42+
{
43+
var cut = Context.RenderComponent<Checkbox<bool>>(builder =>
44+
{
45+
builder.Add(a => a.State, CheckboxState.UnChecked);
46+
});
47+
Assert.Equal(CheckboxState.UnChecked, cut.Instance.State);
48+
49+
await cut.InvokeAsync(() => cut.Instance.SyncStateCallback(CheckboxState.Checked));
50+
Assert.Equal(CheckboxState.Checked, cut.Instance.State);
51+
}
52+
3053
[Fact]
3154
public void ShowAfterLabel_Ok()
3255
{
@@ -208,7 +231,7 @@ public void CheckboxItemClass_Ok()
208231
}
209232

210233
[Fact]
211-
public void StringValue_Ok()
234+
public async Task StringValue_Ok()
212235
{
213236
var cut = Context.RenderComponent<CheckboxList<string>>(builder =>
214237
{
@@ -236,13 +259,13 @@ public void StringValue_Ok()
236259
});
237260
});
238261
// 字符串值选中事件
239-
var item = cut.Find(".form-check-input");
240-
item.Click();
262+
var item = cut.FindComponent<Checkbox<bool>>();
263+
await cut.InvokeAsync(item.Instance.TriggerClick);
241264
Assert.True(selected);
242265
}
243266

244267
[Fact]
245-
public void OnSelectedChanged_Ok()
268+
public async Task OnSelectedChanged_Ok()
246269
{
247270
var selected = false;
248271
var foo = Foo.Generate(Localizer);
@@ -257,8 +280,8 @@ public void OnSelectedChanged_Ok()
257280
});
258281
});
259282

260-
var item = cut.Find(".form-check-input");
261-
item.Click();
283+
var item = cut.FindComponent<Checkbox<bool>>();
284+
await cut.InvokeAsync(item.Instance.TriggerClick);
262285
Assert.True(selected);
263286
}
264287

@@ -274,7 +297,7 @@ public void EnumValue_Ok()
274297
}
275298

276299
[Fact]
277-
public void IntValue_Ok()
300+
public async Task IntValue_Ok()
278301
{
279302
var ret = new List<int>();
280303
var selectedIntValues = new List<int> { 1, 2 };
@@ -292,8 +315,8 @@ public void IntValue_Ok()
292315
return Task.CompletedTask;
293316
});
294317
});
295-
var item = cut.Find(".form-check-input");
296-
item.Click();
318+
var item = cut.FindComponent<Checkbox<bool>>();
319+
await cut.InvokeAsync(item.Instance.TriggerClick);
297320

298321
// 选中 2
299322
Assert.Equal(2, ret.First());

test/UnitTest/Components/ConsoleTest.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ public void LightTitle_OK()
195195
}
196196

197197
[Fact]
198-
public void ClickAutoScroll_OK()
198+
public async Task ClickAutoScroll_OK()
199199
{
200200
var cut = Context.RenderComponent<Console>(builder =>
201201
{
@@ -206,7 +206,8 @@ public void ClickAutoScroll_OK()
206206
builder.Add(a => a.ShowAutoScroll, true);
207207
});
208208

209-
cut.Find(".card-footer input").Click();
209+
var item = cut.FindComponent<Checkbox<bool>>();
210+
await cut.InvokeAsync(item.Instance.TriggerClick);
210211
var res = cut.Instance.IsAutoScroll;
211212
Assert.False(res);
212213
}

test/UnitTest/Components/TableDialogTest.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ public async Task EditAsync_Ok()
6262

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

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

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

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

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

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

test/UnitTest/Components/TableDrawerTest.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ public async Task EditAsync_Ok()
4343

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

5050
// 编辑弹窗逻辑
@@ -102,8 +102,8 @@ public async Task EditAsync_Ok()
102102
{
103103
pb.Add(a => a.OnSaveAsync, (foo, itemType) => Task.FromResult(false));
104104
});
105-
input = cut.Find("tbody tr input");
106-
await cut.InvokeAsync(() => input.Click());
105+
checkbox = cut.FindComponents<Checkbox<Foo>>()[1];
106+
await cut.InvokeAsync(checkbox.Instance.TriggerClick);
107107
await cut.InvokeAsync(() => table.Instance.EditAsync());
108108
form = cut.Find("form");
109109
await cut.InvokeAsync(() => form.Submit());
@@ -119,7 +119,7 @@ public async Task EditAsync_Ok()
119119
await cut.InvokeAsync(() => table.Instance.AddAsync());
120120

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

125125
form = cut.Find("form");

0 commit comments

Comments
 (0)