Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/Components/Filters/MultiFilter.razor
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ else
@if (ShowSearch)
{
<BootstrapInput UseInputEvent="true" class="bb-multi-filter-search"
Value="@_searchText" bind-value:event="oninput" IsAutoFocus="true"
Value="@_searchText" IsAutoFocus="true"
PlaceHolder="@SearchPlaceHolderText" IsSelectAllTextOnFocus="true"
OnValueChanged="OnSearchValueChanged" ShowLabel="false" SkipValidate="true"></BootstrapInput>
}
Expand Down
15 changes: 15 additions & 0 deletions src/BootstrapBlazor/Components/Filters/MultiSelectFilter.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@namespace BootstrapBlazor.Components
@inherits FilterBase
@typeparam TType

@if (IsHeaderRow)
{
<div class="@FilterRowClassString">
<MultiSelect class="is-filter" Items="@Items" @bind-Value="_value1" OnValueChanged="_ => OnFilterAsync()"
ShowLabel="false" SkipValidate="true" IsPopover="true"></MultiSelect>
</div>
}
else
{
<MultiSelect Items="@Items" @bind-Value="@_value1" IsPopover="true" ShowLabel="false" SkipValidate="true"></MultiSelect>
}
90 changes: 90 additions & 0 deletions src/BootstrapBlazor/Components/Filters/MultiSelectFilter.razor.cs
Original file line number Diff line number Diff line change
@@ -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([email protected]) Website: https://www.blazor.zone

namespace BootstrapBlazor.Components;

/// <summary>
/// 多项选择下拉框过滤组件
/// </summary>
public partial class MultiSelectFilter<TType>
{
private string? FilterRowClassString => CssBuilder.Default("filter-row")
.AddClass("active", TableColumnFilter.HasFilter())
.Build();

private TType? _value1;
private FilterAction _action1 = FilterAction.Equal;

/// <summary>
/// Gets or sets the filter items.
/// </summary>
[Parameter]
public List<SelectedItem>? Items { get; set; }

/// <summary>
/// <inheritdoc/>
/// </summary>
public override void Reset()
{
_value1 = default;
_action1 = FilterAction.Equal;
Count = 0;
StateHasChanged();
}

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
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<string> values)
{
foreach (var item in values)
{
filter.Filters.Add(new FilterKeyValueAction
{
FieldKey = FieldKey,
FieldValue = item,
FilterAction = _action1
});
}
}
return filter;
}

/// <summary>
/// <inheritdoc/>
/// </summary>
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);
}
}
149 changes: 149 additions & 0 deletions test/UnitTest/Components/TableMultiSelectFilterTest.cs
Original file line number Diff line number Diff line change
@@ -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([email protected]) Website: https://www.blazor.zone

namespace UnitTest.Components;

public class TableMultiSelectFilterTest : BootstrapBlazorTestBase
{
[Fact]
public async Task IsHeaderRow_Ok()
{
var cut = Context.RenderComponent<TableColumnFilter>(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<TableColumnFilter>(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<MultiSelectFilter<string>>();
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<TableColumnFilter>(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<MultiSelectFilter<List<string>>>();
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<string, IFilterAction> Filters { get; set; } = [];

public Func<Task>? OnFilterAsync { get; set; }

public List<ITableColumn> Columns => [];

public IEnumerable<ITableColumn> GetVisibleColumns() => Columns;
}

class MockColumn : TableColumn<Foo, string>
{
public MockColumn()
{
PropertyType = typeof(string);
FieldName = "MultiSelect";

FilterTemplate = new RenderFragment(builder =>
{
builder.OpenComponent<FilterProvider>(0);
builder.AddAttribute(1, nameof(FilterProvider.ChildContent), new RenderFragment(builder =>
{
builder.OpenComponent<MultiSelectFilter<string>>(2);
builder.AddAttribute(3, nameof(MultiSelectFilter<string>.Items), new List<SelectedItem>
{
new("v1", "Test-1"),
new("v2", "Test-2")
});
builder.CloseComponent();
}));
builder.CloseComponent();
});
}
}

class MockColumnList : TableColumn<Foo, List<string>>
{
public MockColumnList()
{
PropertyType = typeof(List<string>);
FieldName = "MultiSelect";

FilterTemplate = new RenderFragment(builder =>
{
builder.OpenComponent<FilterProvider>(0);
builder.AddAttribute(1, nameof(FilterProvider.ChildContent), new RenderFragment(builder =>
{
builder.OpenComponent<MultiSelectFilter<List<string>>>(2);
builder.AddAttribute(3, nameof(MultiSelectFilter<List<string>>.Items), new List<SelectedItem>
{
new("v1", "Test-1"),
new("v2", "Test-2")
});
builder.CloseComponent();
}));
builder.CloseComponent();
});
}
}

}