Skip to content
Merged
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
{
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()
{
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 => {
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));

if (state === "1") {
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