Skip to content

Commit b331fc3

Browse files
authored
feat(MultiSelectFilter): add MultiSelectFilter component (#6097)
* refactor: 移除 bind-value event 语句 * refactor: 增加 MultiSelectFilter 组件 * test: 增加单元测试 * test: 更新单元测试 * test: 更新单元测试 * test: 更新单元测试 * test: 更新单元测试
1 parent ac20831 commit b331fc3

File tree

4 files changed

+255
-1
lines changed

4 files changed

+255
-1
lines changed

src/BootstrapBlazor/Components/Filters/MultiFilter.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ else
1212
@if (ShowSearch)
1313
{
1414
<BootstrapInput UseInputEvent="true" class="bb-multi-filter-search"
15-
Value="@_searchText" bind-value:event="oninput" IsAutoFocus="true"
15+
Value="@_searchText" IsAutoFocus="true"
1616
PlaceHolder="@SearchPlaceHolderText" IsSelectAllTextOnFocus="true"
1717
OnValueChanged="OnSearchValueChanged" ShowLabel="false" SkipValidate="true"></BootstrapInput>
1818
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@namespace BootstrapBlazor.Components
2+
@inherits FilterBase
3+
@typeparam TType
4+
5+
@if (IsHeaderRow)
6+
{
7+
<div class="@FilterRowClassString">
8+
<MultiSelect class="is-filter" Items="@Items" @bind-Value="_value1" OnValueChanged="_ => OnFilterAsync()"
9+
ShowLabel="false" SkipValidate="true" IsPopover="true"></MultiSelect>
10+
</div>
11+
}
12+
else
13+
{
14+
<MultiSelect Items="@Items" @bind-Value="@_value1" IsPopover="true" ShowLabel="false" SkipValidate="true"></MultiSelect>
15+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
namespace BootstrapBlazor.Components;
7+
8+
/// <summary>
9+
/// 多项选择下拉框过滤组件
10+
/// </summary>
11+
public partial class MultiSelectFilter<TType>
12+
{
13+
private string? FilterRowClassString => CssBuilder.Default("filter-row")
14+
.AddClass("active", TableColumnFilter.HasFilter())
15+
.Build();
16+
17+
private TType? _value1;
18+
private FilterAction _action1 = FilterAction.Equal;
19+
20+
/// <summary>
21+
/// Gets or sets the filter items.
22+
/// </summary>
23+
[Parameter]
24+
public List<SelectedItem>? Items { get; set; }
25+
26+
/// <summary>
27+
/// <inheritdoc/>
28+
/// </summary>
29+
public override void Reset()
30+
{
31+
_value1 = default;
32+
_action1 = FilterAction.Equal;
33+
Count = 0;
34+
StateHasChanged();
35+
}
36+
37+
/// <summary>
38+
/// <inheritdoc/>
39+
/// </summary>
40+
/// <returns></returns>
41+
public override FilterKeyValueAction GetFilterConditions()
42+
{
43+
var filter = new FilterKeyValueAction() { FilterLogic = FilterLogic.Or };
44+
if (_value1 is string v)
45+
{
46+
var items = v.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
47+
foreach (var item in items)
48+
{
49+
filter.Filters.Add(new FilterKeyValueAction
50+
{
51+
FieldKey = FieldKey,
52+
FieldValue = item,
53+
FilterAction = _action1
54+
});
55+
}
56+
}
57+
else if (_value1 is IEnumerable<string> values)
58+
{
59+
foreach (var item in values)
60+
{
61+
filter.Filters.Add(new FilterKeyValueAction
62+
{
63+
FieldKey = FieldKey,
64+
FieldValue = item,
65+
FilterAction = _action1
66+
});
67+
}
68+
}
69+
return filter;
70+
}
71+
72+
/// <summary>
73+
/// <inheritdoc/>
74+
/// </summary>
75+
public override async Task SetFilterConditionsAsync(FilterKeyValueAction filter)
76+
{
77+
var first = filter.Filters.FirstOrDefault() ?? filter;
78+
if (first.FieldValue is TType value)
79+
{
80+
_value1 = value;
81+
}
82+
else
83+
{
84+
_value1 = default;
85+
}
86+
_action1 = first.FilterAction;
87+
88+
await base.SetFilterConditionsAsync(filter);
89+
}
90+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
namespace UnitTest.Components;
7+
8+
public class TableMultiSelectFilterTest : BootstrapBlazorTestBase
9+
{
10+
[Fact]
11+
public async Task IsHeaderRow_Ok()
12+
{
13+
var cut = Context.RenderComponent<TableColumnFilter>(pb =>
14+
{
15+
pb.Add(a => a.Table, new MockTable());
16+
pb.Add(a => a.Column, new MockColumn());
17+
pb.Add(a => a.IsHeaderRow, true);
18+
});
19+
cut.Contains("filter-row");
20+
21+
var actions = cut.FindAll(".dropdown-item");
22+
await cut.InvokeAsync(() => actions[1].Click());
23+
24+
// check filter
25+
var filter = cut.Instance;
26+
var conditions = filter.FilterAction.GetFilterConditions();
27+
Assert.Single(conditions.Filters);
28+
}
29+
30+
[Fact]
31+
public async Task OnFilterAsync_Ok()
32+
{
33+
var cut = Context.RenderComponent<TableColumnFilter>(pb =>
34+
{
35+
pb.Add(a => a.Table, new MockTable());
36+
pb.Add(a => a.Column, new MockColumn());
37+
pb.Add(a => a.IsHeaderRow, false);
38+
});
39+
var item = cut.Find(".dropdown-menu .dropdown-item");
40+
await cut.InvokeAsync(() => item.Click());
41+
42+
var filter = cut.FindComponent<MultiSelectFilter<string>>();
43+
var conditions = filter.Instance.GetFilterConditions();
44+
45+
Assert.Single(conditions.Filters);
46+
47+
await cut.InvokeAsync(() => filter.Instance.Reset());
48+
conditions = filter.Instance.GetFilterConditions();
49+
Assert.Empty(conditions.Filters);
50+
51+
await cut.InvokeAsync(() => filter.Instance.SetFilterConditionsAsync(new FilterKeyValueAction()
52+
{
53+
FieldValue = "v1,v2",
54+
}));
55+
conditions = filter.Instance.GetFilterConditions();
56+
Assert.Equal(2, conditions.Filters.Count);
57+
58+
await cut.InvokeAsync(() => filter.Instance.SetFilterConditionsAsync(new FilterKeyValueAction()
59+
{
60+
FieldValue = true,
61+
}));
62+
conditions = filter.Instance.GetFilterConditions();
63+
Assert.Empty(conditions.Filters);
64+
}
65+
66+
[Fact]
67+
public async Task OnFilterAsync_List()
68+
{
69+
var cut = Context.RenderComponent<TableColumnFilter>(pb =>
70+
{
71+
pb.Add(a => a.Table, new MockTable());
72+
pb.Add(a => a.Column, new MockColumnList());
73+
pb.Add(a => a.IsHeaderRow, false);
74+
});
75+
var item = cut.Find(".dropdown-menu .dropdown-item");
76+
await cut.InvokeAsync(() => item.Click());
77+
78+
var filter = cut.FindComponent<MultiSelectFilter<List<string>>>();
79+
var conditions = filter.Instance.GetFilterConditions();
80+
81+
Assert.Single(conditions.Filters);
82+
83+
await cut.InvokeAsync(() => filter.Instance.Reset());
84+
conditions = filter.Instance.GetFilterConditions();
85+
Assert.Empty(conditions.Filters);
86+
}
87+
88+
class MockTable : ITable
89+
{
90+
public Dictionary<string, IFilterAction> Filters { get; set; } = [];
91+
92+
public Func<Task>? OnFilterAsync { get; set; }
93+
94+
public List<ITableColumn> Columns => [];
95+
96+
public IEnumerable<ITableColumn> GetVisibleColumns() => Columns;
97+
}
98+
99+
class MockColumn : TableColumn<Foo, string>
100+
{
101+
public MockColumn()
102+
{
103+
PropertyType = typeof(string);
104+
FieldName = "MultiSelect";
105+
106+
FilterTemplate = new RenderFragment(builder =>
107+
{
108+
builder.OpenComponent<FilterProvider>(0);
109+
builder.AddAttribute(1, nameof(FilterProvider.ChildContent), new RenderFragment(builder =>
110+
{
111+
builder.OpenComponent<MultiSelectFilter<string>>(2);
112+
builder.AddAttribute(3, nameof(MultiSelectFilter<string>.Items), new List<SelectedItem>
113+
{
114+
new("v1", "Test-1"),
115+
new("v2", "Test-2")
116+
});
117+
builder.CloseComponent();
118+
}));
119+
builder.CloseComponent();
120+
});
121+
}
122+
}
123+
124+
class MockColumnList : TableColumn<Foo, List<string>>
125+
{
126+
public MockColumnList()
127+
{
128+
PropertyType = typeof(List<string>);
129+
FieldName = "MultiSelect";
130+
131+
FilterTemplate = new RenderFragment(builder =>
132+
{
133+
builder.OpenComponent<FilterProvider>(0);
134+
builder.AddAttribute(1, nameof(FilterProvider.ChildContent), new RenderFragment(builder =>
135+
{
136+
builder.OpenComponent<MultiSelectFilter<List<string>>>(2);
137+
builder.AddAttribute(3, nameof(MultiSelectFilter<List<string>>.Items), new List<SelectedItem>
138+
{
139+
new("v1", "Test-1"),
140+
new("v2", "Test-2")
141+
});
142+
builder.CloseComponent();
143+
}));
144+
builder.CloseComponent();
145+
});
146+
}
147+
}
148+
149+
}

0 commit comments

Comments
 (0)