Skip to content

Commit

Permalink
feat(CheckboxList): add ItemTemplate parameter (#5226)
Browse files Browse the repository at this point in the history
* feat: 增加 ItemTemplate 参数

* doc: 更新示例文档

* doc: 移除 div 元素

* test: 增加单元测试

* chore: bump version 9.3.1-beta02
  • Loading branch information
ArgoZhang authored Jan 27, 2025
1 parent 175dac6 commit 3e217f1
Show file tree
Hide file tree
Showing 15 changed files with 109 additions and 28 deletions.
14 changes: 14 additions & 0 deletions src/BootstrapBlazor.Server/Components/Samples/CheckboxLists.razor
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@
</div>
</DemoBlock>

<DemoBlock Title="@Localizer["ItemTemplateTitle"]"
Introduction="@Localizer["ItemTemplateIntro"]"
Name="ItemTemplate">
<CheckboxList TValue="string" Items="@IconDemoValues">
<ItemTemplate>
@if (context is IconSelectedItem item)
{
<i class="@item.Icon"></i>
<span>@item.Text</span>
}
</ItemTemplate>
</CheckboxList>
</DemoBlock>

<DemoBlock Title="@Localizer["EnumTitle"]" Introduction="@Localizer["EnumIntro"]" Name="Enum">
<section ignore>@((MarkupString)Localizer["EnumTip"].Value)</section>
<div class="row g-3">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ protected override void OnInitialized()
new() { Text = Localizer["item4"], Value = Localizer["item4"] },
};

IconDemoValues = new List<IconSelectedItem>()
{
new() { Text = "Item1", Value = "1", Icon = "fa-solid fa-users" },
new() { Text = "Item2", Value = "2", Icon = "fa-solid fa-users-gear" }
};

Dummy = new Foo() { Name = Localizer["Foo"] };
Model = Foo.Generate(LocalizerFoo);
FooItems = Foo.GenerateHobbies(LocalizerFoo);
Expand Down Expand Up @@ -135,6 +141,9 @@ protected override async Task OnInitializedAsync()
new() { Text = "Item 4", Value = "4" },
};

[NotNull]
private IEnumerable<IconSelectedItem>? IconDemoValues { get; set; }

private Task OnSelectedChanged(IEnumerable<SelectedItem> items, string value)
{
NormalLogger.Log($"{Localizer["Header"]} {items.Count(i => i.Active)} {Localizer["Counter"]}{value}");
Expand All @@ -157,6 +166,11 @@ private Task OnMaxSelectedCountExceed()
return ToastService.Information(Localizer["OnMaxSelectedCountExceedTitle"], Localizer["OnMaxSelectedCountExceedContent", 2]);
}

class IconSelectedItem : SelectedItem
{
public string? Icon { get; init; }
}

private AttributeItem[] GetAttributes() =>
[
new()
Expand Down
9 changes: 9 additions & 0 deletions src/BootstrapBlazor.Server/Components/Samples/Checkboxs.razor
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@
</div>
</DemoBlock>

<DemoBlock Title="@Localizer["ItemTemplateTitle"]"
Introduction="@Localizer["ItemTemplateIntro"]"
Name="ItemTemplate">
<Checkbox TValue="string" State="CheckboxState.Checked">
<i class="fa-solid fa-flag"></i>
<span>@Localizer["StatusText1"]</span>
</Checkbox>
</DemoBlock>

<DemoBlock Title="@Localizer["DisplayTextTitle"]" Introduction="@Localizer["DisplayTextIntro"]" Name="DisplayText">
<div class="row g-3 form-inline">
<div class="col-12 col-md-3">
Expand Down
6 changes: 2 additions & 4 deletions src/BootstrapBlazor.Server/Components/Samples/Radios.razor
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,8 @@
<ItemTemplate>
@if (context is IconSelectedItem item)
{
<div>
<i class="@item.Icon"></i>
<span>@item.Text</span>
</div>
<i class="@item.Icon"></i>
<span>@item.Text</span>
}
</ItemTemplate>
</RadioList>
Expand Down
4 changes: 4 additions & 0 deletions src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -2369,6 +2369,8 @@
"ValidateFormTitle": "Used in forms",
"ValidateFormIntro": "When you use <code>Checkbox</code> in a form, the display label text is placed in front of the component",
"ValidateFormDescription": "The pre-label explicit rules are consistent with the <code>BootstrapInput</code> component <a href='input'>[portal]</a>",
"RadiosItemTemplateTitle": "ItemTemplate",
"RadiosItemTemplateIntro": "Set <code>ItemTemplate</code> for customer the item UI",
"OnBeforeStateChangedTitle": "OnBeforeStateChanged",
"OnBeforeStateChangedIntro": "By setting the <code>OnBeforeStateChanged</code> callback method, you can cancel the state change logic",
"OnBeforeStateChangedText": "Confirm",
Expand Down Expand Up @@ -2405,6 +2407,8 @@
"ShowLabelTitle": "Bidirectional binding collection",
"ShowLabelIntro": "Binding values are collections",
"ShowLabelTip": "<code>TValue</code> is set to <code>IEnumerable&lt;int&gt;</code> generic collection, the <Code>ValueField</Code> specified field of the bound collection must be consistent with the generic type",
"ItemTemplateTitle": "ItemTemplate",
"ItemTemplateIntro": "Set <code>ItemTemplate</code> for customer the item UI",
"EnumTitle": "Bidirectional binding enumeration",
"EnumIntro": "The binding value is enumeration",
"EnumTip": "When <code>CheckboxList</code> binds an enumeration set, <code>Items</code> does not need to be specified, <code>Items</code> will be automatically set to all values in the enumeration. If you need to bind some values, please provide the enumeration set <code>Items</code>",
Expand Down
12 changes: 8 additions & 4 deletions src/BootstrapBlazor.Server/Locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2369,6 +2369,8 @@
"ValidateFormTitle": "表单中使用",
"ValidateFormIntro": "在表单中使用 <code>Checkbox</code> 时,显示标签文字会放置到组件前面",
"ValidateFormDescription": "前置标签显式规则与 <code>BootstrapInput</code> 组件一致 <a href='input'>[传送门]</a>",
"ItemTemplateTitle": "项目模板",
"ItemTemplateIntro": "通过设置 <code>ItemTemplate</code> 自定义显示 UI",
"OnBeforeStateChangedTitle": "选中前回调方法",
"OnBeforeStateChangedIntro": "通过设置 <code>OnBeforeStateChanged</code> 回调方法,可取消选中逻辑",
"OnBeforeStateChangedText": "弹窗确认",
Expand Down Expand Up @@ -2405,6 +2407,8 @@
"ShowLabelTitle": "双向绑定集合",
"ShowLabelIntro": "绑定值为集合",
"ShowLabelTip": "<code>TValue</code> 设置为 <code>IEnumerable&lt;int&gt;</code> 泛型集合,绑定集合的 <code>ValueField</code> 指定字段必须与泛型类型一致",
"ItemTemplateTitle": "项目模板",
"ItemTemplateIntro": "通过设置 <code>ItemTemplate</code> 自定义显示 UI",
"EnumTitle": "双向绑定枚举",
"EnumIntro": "绑定值为枚举",
"EnumTip": "当 <code>CheckboxList</code> 绑定一个枚举集合时,<code>Items</code> 不需要指定,<code>Items</code> 会被自动设置成枚举里面所有的值,如果需要绑定部分值时,请自行提供枚举集合 <code>Items</code>",
Expand Down Expand Up @@ -3018,14 +3022,14 @@
"RadiosVerticalIntro": "通过设置 <code>IsVertical</code> 使组件内部竖向排列",
"RadiosEnumTitle": "绑定枚举类型",
"RadiosEnumIntro": "通过双向绑定 <code>Value</code> 无需设置 <code>Items</code>",
"RadiosIsButtonTitle": "按钮样式",
"RadiosIsButtonIntro": "通过设定 <code>IsButton</code> 值为 <code>True</code> 将候选项更改为按钮样式",
"RadiosItemTemplateTitle": "项目模板",
"RadiosItemTemplateIntro": "通过设置 <code>ItemTemplate</code> 自定义显示 UI",
"RadiosEnumDescription": "通过设置 <code>IsAutoAddNullItem</code> 自动添加 <b>空值</b> 选项,通过设置 <code>NullItemText</code> 自定义 <b>空值</b> 选项",
"RadiosEnumText": "空值",
"RadiosColorTitle": "颜色",
"RadiosColorIntro": "通过设置 <code>Color</code> 属性改变组件背景色",
"RadiosIsButtonTitle": "按钮样式",
"RadiosIsButtonIntro": "通过设定 <code>IsButton</code> 值为 <code>True</code> 将候选项更改为按钮样式",
"RadiosItemTemplateTitle": "项目模板",
"RadiosItemTemplateIntro": "通过设置 <code>ItemTemplate</code> 自定义显示 UI",
"RadiosDisplayText": "显示文字",
"RadiosNullItemText": "空值显示文字",
"RadiosIsDisabled": "是否禁用",
Expand Down
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/BootstrapBlazor.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>9.3.1-beta01</Version>
<Version>9.3.1-beta02</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
8 changes: 7 additions & 1 deletion src/BootstrapBlazor/Components/Checkbox/Checkbox.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
<div @attributes="AdditionalAttributes" class="@ClassString">
<input type="checkbox" id="@Id" class="@InputClassString" disabled="@Disabled" checked="@CheckedString"
@onclick="OnToggleClick" @onclick:stopPropagation="StopPropagation" @onclick:preventDefault="false" />
@if (IsShowAfterLabel)
@if(ChildContent != null)
{
<label class="form-check-label" for="@Id">
@ChildContent
</label>
}
else if (IsShowAfterLabel)
{
<label class="form-check-label" for="@Id">
@if (ShowLabelTooltip is true)
Expand Down
6 changes: 6 additions & 0 deletions src/BootstrapBlazor/Components/Checkbox/Checkbox.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ public partial class Checkbox<TValue> : ValidateBase<TValue>
[Parameter]
public bool StopPropagation { get; set; }

/// <summary>
/// 获得/设置 子组件 RenderFragment 实例
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down
5 changes: 3 additions & 2 deletions src/BootstrapBlazor/Components/Checkbox/CheckboxList.razor
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ else
<div @key="item" class="@CheckboxItemClassString">
<Checkbox TValue="bool" IsDisabled="GetDisabledState(item)"
ShowAfterLabel="true" ShowLabel="false" ShowLabelTooltip="ShowLabelTooltip"
DisplayText="@item.Text" OnBeforeStateChanged="_onBeforeStateChangedCallback!"
Value="@item.Active" OnStateChanged="@((state, v) => OnStateChanged(item, v))"></Checkbox>
DisplayText="@item.Text" OnBeforeStateChanged="_onBeforeStateChangedCallback"
Value="@item.Active" OnStateChanged="@((state, v) => OnStateChanged(item, v))"
ChildContent="GetChildContent(item)"></Checkbox>
</div>
}
</div>
Expand Down
10 changes: 10 additions & 0 deletions src/BootstrapBlazor/Components/Checkbox/CheckboxList.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ public partial class CheckboxList<TValue> : ValidateBase<TValue>
[Parameter]
public Func<Task>? OnMaxSelectedCountExceed { get; set; }

/// <summary>
/// 获得/设置 项模板
/// </summary>
[Parameter]
public RenderFragment<SelectedItem>? ItemTemplate { get; set; }

/// <summary>
/// 获得 当前选项是否被禁用
/// </summary>
Expand Down Expand Up @@ -295,4 +301,8 @@ protected virtual void EnsureParameterValid()
throw new NotSupportedException();
}
}

private RenderFragment? GetChildContent(SelectedItem item) => ItemTemplate == null
? null
: ItemTemplate(item);
}
8 changes: 0 additions & 8 deletions src/BootstrapBlazor/Components/Radio/Radio.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,11 @@ public partial class Radio<TValue> : Checkbox<TValue>
[Parameter]
public Func<TValue, Task>? OnClick { get; set; }

/// <summary>
/// 获得/设置 子组件 RenderFragment 实例
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }

/// <summary>
/// 获得/设置 Radio 组名称一般来讲需要设置 默认为 null 未设置
/// </summary>
[Parameter]
#if NET6_0_OR_GREATER
[EditorRequired]
#endif
public string? GroupName { get; set; }

private string? ClassString => CssBuilder.Default("form-check")
Expand Down
3 changes: 1 addition & 2 deletions src/BootstrapBlazor/Components/Radio/RadioList.razor
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ else
<div @attributes="@AdditionalAttributes" class="@ClassString" role="group">
@foreach (var item in Items)
{
var content = GetChildContent(item);
<Radio TValue="SelectedItem" Value="@item" Color="@Color" GroupName="@GroupName" IsDisabled="@GetDisabledState(item)" ShowAfterLabel="true" ShowLabel="false" DisplayText="@item.Text" State="@CheckState(item)" OnClick="OnClick" ChildContent="content!"></Radio>
<Radio TValue="SelectedItem" Value="@item" Color="@Color" GroupName="@GroupName" IsDisabled="@GetDisabledState(item)" ShowAfterLabel="true" ShowLabel="false" DisplayText="@item.Text" State="@CheckState(item)" OnClick="OnClick" ChildContent="GetChildContent(item)"></Radio>
}
</div>
}
6 changes: 0 additions & 6 deletions src/BootstrapBlazor/Components/Radio/RadioList.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@ public partial class RadioList<TValue>
[NotNull]
public string? NullItemText { get; set; }

/// <summary>
/// 获得/设置 项模板
/// </summary>
[Parameter]
public RenderFragment<SelectedItem>? ItemTemplate { get; set; }

/// <summary>
/// 获得/设置 未设置选中项时是否自动选择第一项 默认 true
/// </summary>
Expand Down
30 changes: 30 additions & 0 deletions test/UnitTest/Components/CheckboxListTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ public void Checkbox_Ok()
Assert.DoesNotContain("is-label", cut.Markup);
}

[Fact]
public void ChildContent_Ok()
{
var cut = Context.RenderComponent<Checkbox<string>>(builder =>
{
builder.Add(a => a.ChildContent, pb => pb.AddContent(0, "test-childcontent"));
});
cut.MarkupMatches("<div class=\"form-check\"><input type=\"checkbox\" diff:ignore class=\"form-check-input\" blazor:onclick=\"1\" /><label class=\"form-check-label\" diff:ignore>test-childcontent</label></div>");
}

[Fact]
public void StopPropagation_Ok()
{
Expand Down Expand Up @@ -443,6 +453,26 @@ await cut.InvokeAsync(async () =>
Assert.False(max);
}

[Fact]
public void ItemTemplate_Ok()
{
var items = new List<SelectedItem>()
{
new("1", "Test 1"),
new("2", "Test 2"),
new("3", "Test 3")
};
var cut = Context.RenderComponent<CheckboxList<string>>(pb =>
{
pb.Add(a => a.Items, items);
pb.Add(a => a.ItemTemplate, item => b =>
{
b.AddContent(0, item.Text);
});
});
cut.MarkupMatches("<div diff:ignore class=\"checkbox-list form-control\" tabindex=\"0\" hidefocus=\"true\"><div class=\"checkbox-item\"><div class=\"form-check is-label\"><input type=\"checkbox\" diff:ignore class=\"form-check-input\" blazor:onclick=\"1\" /><label class=\"form-check-label\" diff:ignore>Test 1</label></div></div><div class=\"checkbox-item\"><div class=\"form-check is-label\"><input type=\"checkbox\" diff:ignore class=\"form-check-input\" blazor:onclick=\"2\" /><label class=\"form-check-label\" diff:ignore>Test 2</label></div></div><div class=\"checkbox-item\"><div class=\"form-check is-label\"><input type=\"checkbox\" diff:ignore class=\"form-check-input\" blazor:onclick=\"3\" /><label class=\"form-check-label\" diff:ignore>Test 3</label></div></div></div>");
}

private class CheckboxListGenericMock<T>
{

Expand Down

0 comments on commit 3e217f1

Please sign in to comment.