Skip to content

Commit 3e217f1

Browse files
authored
feat(CheckboxList): add ItemTemplate parameter (#5226)
* feat: 增加 ItemTemplate 参数 * doc: 更新示例文档 * doc: 移除 div 元素 * test: 增加单元测试 * chore: bump version 9.3.1-beta02
1 parent 175dac6 commit 3e217f1

15 files changed

+109
-28
lines changed

src/BootstrapBlazor.Server/Components/Samples/CheckboxLists.razor

+14
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,20 @@
5959
</div>
6060
</DemoBlock>
6161

62+
<DemoBlock Title="@Localizer["ItemTemplateTitle"]"
63+
Introduction="@Localizer["ItemTemplateIntro"]"
64+
Name="ItemTemplate">
65+
<CheckboxList TValue="string" Items="@IconDemoValues">
66+
<ItemTemplate>
67+
@if (context is IconSelectedItem item)
68+
{
69+
<i class="@item.Icon"></i>
70+
<span>@item.Text</span>
71+
}
72+
</ItemTemplate>
73+
</CheckboxList>
74+
</DemoBlock>
75+
6276
<DemoBlock Title="@Localizer["EnumTitle"]" Introduction="@Localizer["EnumIntro"]" Name="Enum">
6377
<section ignore>@((MarkupString)Localizer["EnumTip"].Value)</section>
6478
<div class="row g-3">

src/BootstrapBlazor.Server/Components/Samples/CheckboxLists.razor.cs

+14
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ protected override void OnInitialized()
8989
new() { Text = Localizer["item4"], Value = Localizer["item4"] },
9090
};
9191

92+
IconDemoValues = new List<IconSelectedItem>()
93+
{
94+
new() { Text = "Item1", Value = "1", Icon = "fa-solid fa-users" },
95+
new() { Text = "Item2", Value = "2", Icon = "fa-solid fa-users-gear" }
96+
};
97+
9298
Dummy = new Foo() { Name = Localizer["Foo"] };
9399
Model = Foo.Generate(LocalizerFoo);
94100
FooItems = Foo.GenerateHobbies(LocalizerFoo);
@@ -135,6 +141,9 @@ protected override async Task OnInitializedAsync()
135141
new() { Text = "Item 4", Value = "4" },
136142
};
137143

144+
[NotNull]
145+
private IEnumerable<IconSelectedItem>? IconDemoValues { get; set; }
146+
138147
private Task OnSelectedChanged(IEnumerable<SelectedItem> items, string value)
139148
{
140149
NormalLogger.Log($"{Localizer["Header"]} {items.Count(i => i.Active)} {Localizer["Counter"]}{value}");
@@ -157,6 +166,11 @@ private Task OnMaxSelectedCountExceed()
157166
return ToastService.Information(Localizer["OnMaxSelectedCountExceedTitle"], Localizer["OnMaxSelectedCountExceedContent", 2]);
158167
}
159168

169+
class IconSelectedItem : SelectedItem
170+
{
171+
public string? Icon { get; init; }
172+
}
173+
160174
private AttributeItem[] GetAttributes() =>
161175
[
162176
new()

src/BootstrapBlazor.Server/Components/Samples/Checkboxs.razor

+9
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@
5454
</div>
5555
</DemoBlock>
5656

57+
<DemoBlock Title="@Localizer["ItemTemplateTitle"]"
58+
Introduction="@Localizer["ItemTemplateIntro"]"
59+
Name="ItemTemplate">
60+
<Checkbox TValue="string" State="CheckboxState.Checked">
61+
<i class="fa-solid fa-flag"></i>
62+
<span>@Localizer["StatusText1"]</span>
63+
</Checkbox>
64+
</DemoBlock>
65+
5766
<DemoBlock Title="@Localizer["DisplayTextTitle"]" Introduction="@Localizer["DisplayTextIntro"]" Name="DisplayText">
5867
<div class="row g-3 form-inline">
5968
<div class="col-12 col-md-3">

src/BootstrapBlazor.Server/Components/Samples/Radios.razor

+2-4
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,8 @@
6161
<ItemTemplate>
6262
@if (context is IconSelectedItem item)
6363
{
64-
<div>
65-
<i class="@item.Icon"></i>
66-
<span>@item.Text</span>
67-
</div>
64+
<i class="@item.Icon"></i>
65+
<span>@item.Text</span>
6866
}
6967
</ItemTemplate>
7068
</RadioList>

src/BootstrapBlazor.Server/Locales/en-US.json

+4
Original file line numberDiff line numberDiff line change
@@ -2369,6 +2369,8 @@
23692369
"ValidateFormTitle": "Used in forms",
23702370
"ValidateFormIntro": "When you use <code>Checkbox</code> in a form, the display label text is placed in front of the component",
23712371
"ValidateFormDescription": "The pre-label explicit rules are consistent with the <code>BootstrapInput</code> component <a href='input'>[portal]</a>",
2372+
"RadiosItemTemplateTitle": "ItemTemplate",
2373+
"RadiosItemTemplateIntro": "Set <code>ItemTemplate</code> for customer the item UI",
23722374
"OnBeforeStateChangedTitle": "OnBeforeStateChanged",
23732375
"OnBeforeStateChangedIntro": "By setting the <code>OnBeforeStateChanged</code> callback method, you can cancel the state change logic",
23742376
"OnBeforeStateChangedText": "Confirm",
@@ -2405,6 +2407,8 @@
24052407
"ShowLabelTitle": "Bidirectional binding collection",
24062408
"ShowLabelIntro": "Binding values are collections",
24072409
"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",
2410+
"ItemTemplateTitle": "ItemTemplate",
2411+
"ItemTemplateIntro": "Set <code>ItemTemplate</code> for customer the item UI",
24082412
"EnumTitle": "Bidirectional binding enumeration",
24092413
"EnumIntro": "The binding value is enumeration",
24102414
"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>",

src/BootstrapBlazor.Server/Locales/zh-CN.json

+8-4
Original file line numberDiff line numberDiff line change
@@ -2369,6 +2369,8 @@
23692369
"ValidateFormTitle": "表单中使用",
23702370
"ValidateFormIntro": "在表单中使用 <code>Checkbox</code> 时,显示标签文字会放置到组件前面",
23712371
"ValidateFormDescription": "前置标签显式规则与 <code>BootstrapInput</code> 组件一致 <a href='input'>[传送门]</a>",
2372+
"ItemTemplateTitle": "项目模板",
2373+
"ItemTemplateIntro": "通过设置 <code>ItemTemplate</code> 自定义显示 UI",
23722374
"OnBeforeStateChangedTitle": "选中前回调方法",
23732375
"OnBeforeStateChangedIntro": "通过设置 <code>OnBeforeStateChanged</code> 回调方法,可取消选中逻辑",
23742376
"OnBeforeStateChangedText": "弹窗确认",
@@ -2405,6 +2407,8 @@
24052407
"ShowLabelTitle": "双向绑定集合",
24062408
"ShowLabelIntro": "绑定值为集合",
24072409
"ShowLabelTip": "<code>TValue</code> 设置为 <code>IEnumerable&lt;int&gt;</code> 泛型集合,绑定集合的 <code>ValueField</code> 指定字段必须与泛型类型一致",
2410+
"ItemTemplateTitle": "项目模板",
2411+
"ItemTemplateIntro": "通过设置 <code>ItemTemplate</code> 自定义显示 UI",
24082412
"EnumTitle": "双向绑定枚举",
24092413
"EnumIntro": "绑定值为枚举",
24102414
"EnumTip": "当 <code>CheckboxList</code> 绑定一个枚举集合时,<code>Items</code> 不需要指定,<code>Items</code> 会被自动设置成枚举里面所有的值,如果需要绑定部分值时,请自行提供枚举集合 <code>Items</code>",
@@ -3018,14 +3022,14 @@
30183022
"RadiosVerticalIntro": "通过设置 <code>IsVertical</code> 使组件内部竖向排列",
30193023
"RadiosEnumTitle": "绑定枚举类型",
30203024
"RadiosEnumIntro": "通过双向绑定 <code>Value</code> 无需设置 <code>Items</code>",
3021-
"RadiosIsButtonTitle": "按钮样式",
3022-
"RadiosIsButtonIntro": "通过设定 <code>IsButton</code> 值为 <code>True</code> 将候选项更改为按钮样式",
3023-
"RadiosItemTemplateTitle": "项目模板",
3024-
"RadiosItemTemplateIntro": "通过设置 <code>ItemTemplate</code> 自定义显示 UI",
30253025
"RadiosEnumDescription": "通过设置 <code>IsAutoAddNullItem</code> 自动添加 <b>空值</b> 选项,通过设置 <code>NullItemText</code> 自定义 <b>空值</b> 选项",
30263026
"RadiosEnumText": "空值",
30273027
"RadiosColorTitle": "颜色",
30283028
"RadiosColorIntro": "通过设置 <code>Color</code> 属性改变组件背景色",
3029+
"RadiosIsButtonTitle": "按钮样式",
3030+
"RadiosIsButtonIntro": "通过设定 <code>IsButton</code> 值为 <code>True</code> 将候选项更改为按钮样式",
3031+
"RadiosItemTemplateTitle": "项目模板",
3032+
"RadiosItemTemplateIntro": "通过设置 <code>ItemTemplate</code> 自定义显示 UI",
30293033
"RadiosDisplayText": "显示文字",
30303034
"RadiosNullItemText": "空值显示文字",
30313035
"RadiosIsDisabled": "是否禁用",

src/BootstrapBlazor/BootstrapBlazor.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk.Razor">
22

33
<PropertyGroup>
4-
<Version>9.3.1-beta01</Version>
4+
<Version>9.3.1-beta02</Version>
55
</PropertyGroup>
66

77
<ItemGroup>

src/BootstrapBlazor/Components/Checkbox/Checkbox.razor

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
<div @attributes="AdditionalAttributes" class="@ClassString">
1010
<input type="checkbox" id="@Id" class="@InputClassString" disabled="@Disabled" checked="@CheckedString"
1111
@onclick="OnToggleClick" @onclick:stopPropagation="StopPropagation" @onclick:preventDefault="false" />
12-
@if (IsShowAfterLabel)
12+
@if(ChildContent != null)
13+
{
14+
<label class="form-check-label" for="@Id">
15+
@ChildContent
16+
</label>
17+
}
18+
else if (IsShowAfterLabel)
1319
{
1420
<label class="form-check-label" for="@Id">
1521
@if (ShowLabelTooltip is true)

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

+6
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ public partial class Checkbox<TValue> : ValidateBase<TValue>
9898
[Parameter]
9999
public bool StopPropagation { get; set; }
100100

101+
/// <summary>
102+
/// 获得/设置 子组件 RenderFragment 实例
103+
/// </summary>
104+
[Parameter]
105+
public RenderFragment? ChildContent { get; set; }
106+
101107
/// <summary>
102108
/// <inheritdoc/>
103109
/// </summary>

src/BootstrapBlazor/Components/Checkbox/CheckboxList.razor

+3-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ else
2828
<div @key="item" class="@CheckboxItemClassString">
2929
<Checkbox TValue="bool" IsDisabled="GetDisabledState(item)"
3030
ShowAfterLabel="true" ShowLabel="false" ShowLabelTooltip="ShowLabelTooltip"
31-
DisplayText="@item.Text" OnBeforeStateChanged="_onBeforeStateChangedCallback!"
32-
Value="@item.Active" OnStateChanged="@((state, v) => OnStateChanged(item, v))"></Checkbox>
31+
DisplayText="@item.Text" OnBeforeStateChanged="_onBeforeStateChangedCallback"
32+
Value="@item.Active" OnStateChanged="@((state, v) => OnStateChanged(item, v))"
33+
ChildContent="GetChildContent(item)"></Checkbox>
3334
</div>
3435
}
3536
</div>

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

+10
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ public partial class CheckboxList<TValue> : ValidateBase<TValue>
9898
[Parameter]
9999
public Func<Task>? OnMaxSelectedCountExceed { get; set; }
100100

101+
/// <summary>
102+
/// 获得/设置 项模板
103+
/// </summary>
104+
[Parameter]
105+
public RenderFragment<SelectedItem>? ItemTemplate { get; set; }
106+
101107
/// <summary>
102108
/// 获得 当前选项是否被禁用
103109
/// </summary>
@@ -295,4 +301,8 @@ protected virtual void EnsureParameterValid()
295301
throw new NotSupportedException();
296302
}
297303
}
304+
305+
private RenderFragment? GetChildContent(SelectedItem item) => ItemTemplate == null
306+
? null
307+
: ItemTemplate(item);
298308
}

src/BootstrapBlazor/Components/Radio/Radio.razor.cs

-8
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,11 @@ public partial class Radio<TValue> : Checkbox<TValue>
1717
[Parameter]
1818
public Func<TValue, Task>? OnClick { get; set; }
1919

20-
/// <summary>
21-
/// 获得/设置 子组件 RenderFragment 实例
22-
/// </summary>
23-
[Parameter]
24-
public RenderFragment? ChildContent { get; set; }
25-
2620
/// <summary>
2721
/// 获得/设置 Radio 组名称一般来讲需要设置 默认为 null 未设置
2822
/// </summary>
2923
[Parameter]
30-
#if NET6_0_OR_GREATER
3124
[EditorRequired]
32-
#endif
3325
public string? GroupName { get; set; }
3426

3527
private string? ClassString => CssBuilder.Default("form-check")

src/BootstrapBlazor/Components/Radio/RadioList.razor

+1-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ else
2424
<div @attributes="@AdditionalAttributes" class="@ClassString" role="group">
2525
@foreach (var item in Items)
2626
{
27-
var content = GetChildContent(item);
28-
<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>
27+
<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>
2928
}
3029
</div>
3130
}

src/BootstrapBlazor/Components/Radio/RadioList.razor.cs

-6
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,6 @@ public partial class RadioList<TValue>
2525
[NotNull]
2626
public string? NullItemText { get; set; }
2727

28-
/// <summary>
29-
/// 获得/设置 项模板
30-
/// </summary>
31-
[Parameter]
32-
public RenderFragment<SelectedItem>? ItemTemplate { get; set; }
33-
3428
/// <summary>
3529
/// 获得/设置 未设置选中项时是否自动选择第一项 默认 true
3630
/// </summary>

test/UnitTest/Components/CheckboxListTest.cs

+30
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ public void Checkbox_Ok()
2727
Assert.DoesNotContain("is-label", cut.Markup);
2828
}
2929

30+
[Fact]
31+
public void ChildContent_Ok()
32+
{
33+
var cut = Context.RenderComponent<Checkbox<string>>(builder =>
34+
{
35+
builder.Add(a => a.ChildContent, pb => pb.AddContent(0, "test-childcontent"));
36+
});
37+
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>");
38+
}
39+
3040
[Fact]
3141
public void StopPropagation_Ok()
3242
{
@@ -443,6 +453,26 @@ await cut.InvokeAsync(async () =>
443453
Assert.False(max);
444454
}
445455

456+
[Fact]
457+
public void ItemTemplate_Ok()
458+
{
459+
var items = new List<SelectedItem>()
460+
{
461+
new("1", "Test 1"),
462+
new("2", "Test 2"),
463+
new("3", "Test 3")
464+
};
465+
var cut = Context.RenderComponent<CheckboxList<string>>(pb =>
466+
{
467+
pb.Add(a => a.Items, items);
468+
pb.Add(a => a.ItemTemplate, item => b =>
469+
{
470+
b.AddContent(0, item.Text);
471+
});
472+
});
473+
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>");
474+
}
475+
446476
private class CheckboxListGenericMock<T>
447477
{
448478

0 commit comments

Comments
 (0)