Skip to content

Commit

Permalink
feat(MultiFilter): add MultiFilter component (#3703)
Browse files Browse the repository at this point in the history
* add TableContextualMenu Component

* add CustomFilter

* add OnSearchValueChanged

* 实现单选过滤

* refactor: 增加关系运算符

* 添加隔离css

* 重构代码

* 添加本地化

* 添加demo,重构代码

* 优化UI布局

* refactor: 重命名组件

* refactor: 更新 scss 源

* refactor: 移除 CustomFilter 参数

* refactor: 精简代码

* doc: 更新资源文件

* doc: 更新示例

* refactor: 更新样式

* feat: 增加 ShowSearch 参数

* refactor: 更新全选框状态

* refactor: 重置更新搜索框值

* style: 更新样式

* 固定宽高

* feat: 弃用属性不参与序列化

* doc: 更新说明文档

* test: 增加单元测试

* chore: bump version 8.6.3

---------

Co-authored-by: Argo-AscioTech <argo@live.ca>
  • Loading branch information
h2ls and ArgoZhang authored Jun 22, 2024
1 parent 931f817 commit 6819c03
Show file tree
Hide file tree
Showing 13 changed files with 370 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
</DemoBlock>

<DemoBlock Title="@Localizer["FilterTemplateTitle"]"
Introduction="@Localizer["FilterTemplateIntro"]" Name="CustomerFilter">
Introduction="@Localizer["FilterTemplateIntro"]" Name="CustomerFilter">
<section ignore>@((MarkupString)Localizer["TablesFilterTemplateDescription", ComponentSourceCodeUrl].Value)</section>

<Table TItem="Foo"
Expand Down Expand Up @@ -192,3 +192,38 @@
</TableColumns>
</Table>
</DemoBlock>

<DemoBlock Title="@Localizer["MultiFilterTitle"]"
Introduction="@Localizer["MultiFilterIntro"]"
Name="MultiFilter">
<Table TItem="Foo"
IsPagination="true" PageItemsSource="@PageItemsSource"
IsStriped="true" IsBordered="true" IsMultipleSelect="true"
ShowSkeleton="true"
OnQueryAsync="@OnQueryAsync">
<TableColumns>
<TableColumn @bind-Field="@context.DateTime" Width="180" Sortable="true" />
<TableColumn @bind-Field="@context.Name" Width="100" Sortable="true" Filterable="true">
<FilterTemplate>
<MultiFilter Items="Items.Select(i => new SelectedItem(i.Name!, i.Name!)).DistinctBy(i => i.Value).ToList()"></MultiFilter>
</FilterTemplate>
</TableColumn>
<TableColumn @bind-Field="@context.Address" Sortable="true" Filterable="true">
<FilterTemplate>
<MultiFilter Items="Items.Select(i => new SelectedItem(i.Address!, i.Address!)).DistinctBy(i => i.Value).ToList()"></MultiFilter>
</FilterTemplate>
</TableColumn>
<TableColumn @bind-Field="@context.Complete" Width="100" Sortable="true" Filterable="true">
<FilterTemplate>
<MultiFilter ShowSearch="false" Items="Items.Select(i => new SelectedItem(i.Complete.ToString()!, i.Complete.ToString()!)).DistinctBy(i => i.Value).ToList()"></MultiFilter>
</FilterTemplate>
</TableColumn>
<TableColumn @bind-Field="@context.Education" Width="100" Sortable="true" Filterable="true">
<FilterTemplate>
<MultiFilter ShowSearch="false" Items="Items.Select(i => new SelectedItem(i.Education.ToString()!, i.Education.ToString()!)).DistinctBy(i => i.Value).ToList()"></MultiFilter>
</FilterTemplate>
</TableColumn>
<TableColumn @bind-Field="@context.Count" Width="150" Sortable="true" />
</TableColumns>
</Table>
</DemoBlock>
2 changes: 2 additions & 0 deletions src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -5448,6 +5448,8 @@
"AutoHeightIntro": "<p><b>Highly Adaptive</b></p><p>In this example, when the parent container height is set to <code>600px</code> expand/collapse the search bar, the table automatically fills the parent container</p>"
},
"BootstrapBlazor.Server.Components.Samples.Table.TablesFilter": {
"MultiFilterTitle": "Multiple selection list filtering",
"MultiFilterIntro": "Use the built-in <code>MultiFilter</code> component to provide multi-select filtering via <code>FilterTemplate</code>",
"TablesFilterTitle": "Filter and sort function",
"TablesFilterDesc": "Filter to quickly find the data you want to see; sort to quickly find or compare data.",
"TablesFilterDescLi1": "Filters a column of data to specify the column to be filtered by specifying the <code>filterable</code> property of the column",
Expand Down
2 changes: 2 additions & 0 deletions src/BootstrapBlazor.Server/Locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -5448,6 +5448,8 @@
"AutoHeightIntro": "<p><b>高度自适应</b></p><p>本例中设置父容器高度为 <code>600px</code> 展开/收起搜索栏时,表格自动充满父容器</p>"
},
"BootstrapBlazor.Server.Components.Samples.Table.TablesFilter": {
"MultiFilterTitle": "多选列表筛选",
"MultiFilterIntro": "通过 <code>FilterTemplate</code> 使用内置 <code>MultiFilter</code> 组件提供多选筛选功能",
"TablesFilterTitle": "筛选和排序功能",
"TablesFilterDesc": "筛选可快速查找到自己想看的数据;排序可快速查找或对比数据。",
"TablesFilterDescLi1": "对某一列数据进行筛选,通过指定列的 <code>Filterable</code> 属性来指定需要筛选的列",
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>8.6.3-beta06</Version>
<Version>8.6.3</Version>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
Expand Down
6 changes: 3 additions & 3 deletions src/BootstrapBlazor/Components/Filters/BoolFilter.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ protected override void OnParametersSet()

Items ??= new SelectedItem[]
{
new SelectedItem("", Localizer["BoolFilter.AllText"].Value),
new SelectedItem("true", Localizer["BoolFilter.TrueText"].Value),
new SelectedItem("false", Localizer["BoolFilter.FalseText"].Value)
new("", Localizer["BoolFilter.AllText"].Value),
new("true", Localizer["BoolFilter.TrueText"].Value),
new("false", Localizer["BoolFilter.FalseText"].Value)
};
}

Expand Down
27 changes: 27 additions & 0 deletions src/BootstrapBlazor/Components/Filters/MultiFilter.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@using Microsoft.Extensions.Localization
@namespace BootstrapBlazor.Components
@inherits FilterBase
@inject IStringLocalizer<MultiFilter> Localizer

<div class="bb-multi-filter">
@if (ShowSearch)
{
<BootstrapInput UseInputEvent="true" class="bb-multi-filter-search"
Value="@_searchText" bind-value:event="oninput" IsAutoFocus="true"
PlaceHolder="@SearchPlaceHolderText" IsSelectAllTextOnFocus="true"
OnValueChanged="OnSearchValueChanged" />
}
<div class="bb-multi-filter-list">
<div class="bb-multi-filter-header">
<Checkbox Value="checkAll" ShowAfterLabel="true" ShowLabel="false" DisplayText="@SelectAllText" OnStateChanged="@OnStateChanged" State="GetState()" />
</div>
<div class="bb-multi-filter-body scroll">
@foreach (var item in GetItems())
{
<div class="bb-multi-filter-body-item">
<Checkbox @bind-Value="@item.Checked" ShowAfterLabel="true" ShowLabel="false" DisplayText="@item.Text" />
</div>
}
</div>
</div>
</div>
157 changes: 157 additions & 0 deletions src/BootstrapBlazor/Components/Filters/MultiFilter.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

namespace BootstrapBlazor.Components;

/// <summary>
/// 表格过滤菜单组件
/// </summary>
public partial class MultiFilter
{
/// <summary>
/// 获得/设置 搜索栏占位符 默认 nul 使用资源文件中值
/// </summary>
[Parameter]
public string? SearchPlaceHolderText { get; set; }

/// <summary>
/// 获得/设置 全选按钮文本 默认 nul 使用资源文件中值
/// </summary>
[Parameter]
public string? SelectAllText { get; set; }

/// <summary>
/// 获得/设置 是否显示搜索栏 默认 true
/// </summary>
[Parameter]
public bool ShowSearch { get; set; } = true;

private string? _searchText;

private bool checkAll = false;

private readonly List<MultiFilterItem> _source = [];

private IEnumerable<MultiFilterItem>? _items;

/// <summary>
/// OnInitialized 方法
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();

if (Items != null)
{
_source.AddRange(Items.Select(item => new MultiFilterItem() { Value = item.Value, Text = item.Text }));
}
if (TableFilter != null)
{
TableFilter.ShowMoreButton = false;
}
}

/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnParametersSet()
{
base.OnParametersSet();

SearchPlaceHolderText ??= Localizer["MultiFilterSearchPlaceHolderText"];
SelectAllText ??= Localizer["MultiFilterSelectAllText"];
}

/// <summary>
/// 重置过滤条件方法
/// </summary>
public override void Reset()
{
checkAll = false;
_searchText = string.Empty;
foreach (var item in _source)
{
item.Checked = false;
}
StateHasChanged();
}

/// <summary>
/// 生成过滤条件方法
/// </summary>
/// <returns></returns>
public override FilterKeyValueAction GetFilterConditions()
{
var filter = new FilterKeyValueAction() { Filters = [], FilterLogic = FilterLogic.Or };

foreach (var item in GetItems().Where(i => i.Checked))
{
filter.Filters.Add(new FilterKeyValueAction()
{
FieldKey = FieldKey,
FieldValue = item.Value,
FilterAction = FilterAction.Equal
});
}
return filter;
}

private CheckboxState GetState() => GetItems().All(i => i.Checked)
? CheckboxState.Checked
: GetItems().All(i => !i.Checked) ? CheckboxState.UnChecked : CheckboxState.Indeterminate;

private Task OnStateChanged(CheckboxState state, bool val)
{
checkAll = val;
if (state == CheckboxState.Checked)
{
foreach (var item in _source)
{
item.Checked = true;
}
}
else
{
foreach (var item in _source)
{
item.Checked = false;
}
}
StateHasChanged();
return Task.CompletedTask;
}

/// <summary>
/// 过滤内容搜索
/// </summary>
/// <param name="val"></param>
/// <returns></returns>
private Task OnSearchValueChanged(string? val)
{
_searchText = val;
if (!string.IsNullOrEmpty(_searchText))
{
_items = _source.Where(i => i.Text.Contains(_searchText));
}
else
{
_items = null;
}
StateHasChanged();
return Task.CompletedTask;
}

private IEnumerable<MultiFilterItem> GetItems() => _items ?? _source;

class MultiFilterItem
{
public bool Checked { get; set; }

[NotNull]
public string? Value { get; init; }

[NotNull]
public string? Text { get; init; }
}
}
51 changes: 51 additions & 0 deletions src/BootstrapBlazor/Components/Filters/MultiFilter.razor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
.bb-multi-filter {
--bb-multi-filter-height: 180px;
--bb-multi-filter-width: 224px;
--bb-multi-filter-search-margin-bottom: 1rem;
--bb-multi-filter-body-item-bg: #fff;
--bb-multi-filter-body-item-hover-bg: #fff;
--bb-multi-filter-body-item-margin: .5rem;

.bb-multi-filter-search {
margin-bottom: var(--bb-multi-filter-search-margin-bottom);
}

.bb-multi-filter-list {
border: var(--bs-border-width) solid var(--bs-border-color);
border-radius: var(--bs-border-radius);
padding: var(--bb-multi-filter-body-item-margin);

.bb-multi-filter-header {
margin-bottom: var(--bb-multi-filter-body-item-margin);
padding-bottom: var(--bb-multi-filter-body-item-margin);
border-bottom: var(--bs-border-width) solid var(--bs-border-color);
}

.bb-multi-filter-body {
height: var(--bb-multi-filter-height);
width: var(--bb-multi-filter-width);
margin-top: var(--bb-multi-filter-body-item-margin);

.bb-multi-filter-body-item {
background-color: var(--bb-multi-filter-body-item-bg);

.form-check {
width: 100%;

.form-check-input + .form-check-label {
text-overflow: unset;
overflow: unset;
}
}

&:not(:last-child) {
margin-bottom: var(--bb-multi-filter-body-item-margin);
}

&:hover {
background-color: var(--bb-multi-filter-body-item-hover-bg);
}
}
}
}
}
4 changes: 4 additions & 0 deletions src/BootstrapBlazor/Locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@
"CloseAllTabsText": "Close All",
"NotFoundTabText": "NotFound"
},
"BootstrapBlazor.Components.MultiFilter": {
"MultiFilterSearchPlaceHolderText": "Please enter ...",
"MultiFilterSelectAllText": "Select All"
},
"BootstrapBlazor.Components.Table": {
"AddButtonText": "Add",
"EditButtonText": "Edit",
Expand Down
4 changes: 4 additions & 0 deletions src/BootstrapBlazor/Locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@
"CloseAllTabsText": "关闭所有标签",
"NotFoundTabText": "未找到"
},
"BootstrapBlazor.Components.MultiFilter": {
"MultiFilterSearchPlaceHolderText": "请输入 ...",
"MultiFilterSelectAllText": "全选"
},
"BootstrapBlazor.Components.Table": {
"AddButtonText": "新建",
"EditButtonText": "编辑",
Expand Down
4 changes: 4 additions & 0 deletions src/BootstrapBlazor/Options/QueryPageOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using System.Text.Json.Serialization;

namespace BootstrapBlazor.Components;

/// <summary>
Expand Down Expand Up @@ -82,6 +84,7 @@ public class QueryPageOptions
/// </summary>
[Obsolete("This property is obsolete. Use CustomerSearches instead. 已过期,请使用 CustomerSearches 参数")]
[ExcludeFromCodeCoverage]
[JsonIgnore]
public List<IFilterAction> CustomerSearchs => CustomerSearches;

/// <summary>
Expand Down Expand Up @@ -112,6 +115,7 @@ public class QueryPageOptions
/// <remarks><see cref="Table{TItem}"/> 组件首次查询数据时为 true</remarks>
[Obsolete("This property is obsolete. Use IsFirstQuery. 已弃用单词拼写错误,请使用 IsFirstQuery")]
[ExcludeFromCodeCoverage]
[JsonIgnore]
public bool IsFristQuery { get => IsFirstQuery; set => IsFirstQuery = value; }

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions src/BootstrapBlazor/wwwroot/scss/components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
@import "../../Components/ErrorLogger/ErrorLogger.razor.scss";
@import "../../Components/FlipClock/FlipClock.razor.scss";
@import "../../Components/FileIcon/FileIcon.razor.scss";
@import "../../Components/Filters/MultiFilter.razor.scss";
@import "../../Components/Filters/TableFilter.razor.scss";
@import "../../Components/Footer/Footer.razor.scss";
@import "../../Components/FullScreen/FullScreenButton.razor.scss";
Expand Down
Loading

0 comments on commit 6819c03

Please sign in to comment.