diff --git a/src/BootstrapBlazor/Components/Filters/MultiFilter.razor b/src/BootstrapBlazor/Components/Filters/MultiFilter.razor index df3a9cd937a..6c0fdcfda36 100644 --- a/src/BootstrapBlazor/Components/Filters/MultiFilter.razor +++ b/src/BootstrapBlazor/Components/Filters/MultiFilter.razor @@ -12,7 +12,7 @@ else @if (ShowSearch) { } diff --git a/src/BootstrapBlazor/Components/Filters/MultiSelectFilter.razor b/src/BootstrapBlazor/Components/Filters/MultiSelectFilter.razor new file mode 100644 index 00000000000..951bee5182d --- /dev/null +++ b/src/BootstrapBlazor/Components/Filters/MultiSelectFilter.razor @@ -0,0 +1,15 @@ +@namespace BootstrapBlazor.Components +@inherits FilterBase +@typeparam TType + +@if (IsHeaderRow) +{ +
+ +
+} +else +{ + +} diff --git a/src/BootstrapBlazor/Components/Filters/MultiSelectFilter.razor.cs b/src/BootstrapBlazor/Components/Filters/MultiSelectFilter.razor.cs new file mode 100644 index 00000000000..6f96bb7e325 --- /dev/null +++ b/src/BootstrapBlazor/Components/Filters/MultiSelectFilter.razor.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// 多项选择下拉框过滤组件 +/// +public partial class MultiSelectFilter +{ + private string? FilterRowClassString => CssBuilder.Default("filter-row") + .AddClass("active", TableColumnFilter.HasFilter()) + .Build(); + + private TType? _value1; + private FilterAction _action1 = FilterAction.Equal; + + /// + /// Gets or sets the filter items. + /// + [Parameter] + public List? Items { get; set; } + + /// + /// + /// + public override void Reset() + { + _value1 = default; + _action1 = FilterAction.Equal; + Count = 0; + StateHasChanged(); + } + + /// + /// + /// + /// + public override FilterKeyValueAction GetFilterConditions() + { + var filter = new FilterKeyValueAction() { FilterLogic = FilterLogic.Or }; + if (_value1 is string v) + { + var items = v.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + foreach (var item in items) + { + filter.Filters.Add(new FilterKeyValueAction + { + FieldKey = FieldKey, + FieldValue = item, + FilterAction = _action1 + }); + } + } + else if (_value1 is IEnumerable values) + { + foreach (var item in values) + { + filter.Filters.Add(new FilterKeyValueAction + { + FieldKey = FieldKey, + FieldValue = item, + FilterAction = _action1 + }); + } + } + return filter; + } + + /// + /// + /// + public override async Task SetFilterConditionsAsync(FilterKeyValueAction filter) + { + var first = filter.Filters.FirstOrDefault() ?? filter; + if (first.FieldValue is TType value) + { + _value1 = value; + } + else + { + _value1 = default; + } + _action1 = first.FilterAction; + + await base.SetFilterConditionsAsync(filter); + } +} diff --git a/test/UnitTest/Components/TableMultiSelectFilterTest.cs b/test/UnitTest/Components/TableMultiSelectFilterTest.cs new file mode 100644 index 00000000000..003b40f13aa --- /dev/null +++ b/test/UnitTest/Components/TableMultiSelectFilterTest.cs @@ -0,0 +1,149 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace UnitTest.Components; + +public class TableMultiSelectFilterTest : BootstrapBlazorTestBase +{ + [Fact] + public async Task IsHeaderRow_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Table, new MockTable()); + pb.Add(a => a.Column, new MockColumn()); + pb.Add(a => a.IsHeaderRow, true); + }); + cut.Contains("filter-row"); + + var actions = cut.FindAll(".dropdown-item"); + await cut.InvokeAsync(() => actions[1].Click()); + + // check filter + var filter = cut.Instance; + var conditions = filter.FilterAction.GetFilterConditions(); + Assert.Single(conditions.Filters); + } + + [Fact] + public async Task OnFilterAsync_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Table, new MockTable()); + pb.Add(a => a.Column, new MockColumn()); + pb.Add(a => a.IsHeaderRow, false); + }); + var item = cut.Find(".dropdown-menu .dropdown-item"); + await cut.InvokeAsync(() => item.Click()); + + var filter = cut.FindComponent>(); + var conditions = filter.Instance.GetFilterConditions(); + + Assert.Single(conditions.Filters); + + await cut.InvokeAsync(() => filter.Instance.Reset()); + conditions = filter.Instance.GetFilterConditions(); + Assert.Empty(conditions.Filters); + + await cut.InvokeAsync(() => filter.Instance.SetFilterConditionsAsync(new FilterKeyValueAction() + { + FieldValue = "v1,v2", + })); + conditions = filter.Instance.GetFilterConditions(); + Assert.Equal(2, conditions.Filters.Count); + + await cut.InvokeAsync(() => filter.Instance.SetFilterConditionsAsync(new FilterKeyValueAction() + { + FieldValue = true, + })); + conditions = filter.Instance.GetFilterConditions(); + Assert.Empty(conditions.Filters); + } + + [Fact] + public async Task OnFilterAsync_List() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Table, new MockTable()); + pb.Add(a => a.Column, new MockColumnList()); + pb.Add(a => a.IsHeaderRow, false); + }); + var item = cut.Find(".dropdown-menu .dropdown-item"); + await cut.InvokeAsync(() => item.Click()); + + var filter = cut.FindComponent>>(); + var conditions = filter.Instance.GetFilterConditions(); + + Assert.Single(conditions.Filters); + + await cut.InvokeAsync(() => filter.Instance.Reset()); + conditions = filter.Instance.GetFilterConditions(); + Assert.Empty(conditions.Filters); + } + + class MockTable : ITable + { + public Dictionary Filters { get; set; } = []; + + public Func? OnFilterAsync { get; set; } + + public List Columns => []; + + public IEnumerable GetVisibleColumns() => Columns; + } + + class MockColumn : TableColumn + { + public MockColumn() + { + PropertyType = typeof(string); + FieldName = "MultiSelect"; + + FilterTemplate = new RenderFragment(builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(FilterProvider.ChildContent), new RenderFragment(builder => + { + builder.OpenComponent>(2); + builder.AddAttribute(3, nameof(MultiSelectFilter.Items), new List + { + new("v1", "Test-1"), + new("v2", "Test-2") + }); + builder.CloseComponent(); + })); + builder.CloseComponent(); + }); + } + } + + class MockColumnList : TableColumn> + { + public MockColumnList() + { + PropertyType = typeof(List); + FieldName = "MultiSelect"; + + FilterTemplate = new RenderFragment(builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(FilterProvider.ChildContent), new RenderFragment(builder => + { + builder.OpenComponent>>(2); + builder.AddAttribute(3, nameof(MultiSelectFilter>.Items), new List + { + new("v1", "Test-1"), + new("v2", "Test-2") + }); + builder.CloseComponent(); + })); + builder.CloseComponent(); + }); + } + } + +}