feat(module: table): Allow custom Field filters (#3279)

* feat(module: table): Refactor FilterExpression to FieldFilterType, allow custom Field filters

* feat(module: table): Add DefaultFilters property

* fix(module: table): Calculate _hasFilterSelected when DefaultFilters is set

* Remove Resharper settings file

* Fix generate columns example

* Add CustomFieldFilter demo, filtering by a color's brightness

* add Chinese description

* fix docs

* Change IFieldFilterType.SupportsCompareOperator to SupportedCompareOperators, returning an IEnumerable
Small fixes

* fix locale

* fix the docs

* refactor SupportedCompareOperators

* fix build error

---------

Co-authored-by: Rhodon <rhodonvantilburg@gmail.com>
Co-authored-by: James Yeung <shunjiey@hotmail.com>
Co-authored-by: James Yeung <shunjiev5@hotmail.com>
This commit is contained in:
rhodon-jargon 2023-10-14 14:38:10 +02:00 committed by GitHub
parent 050822297d
commit 6eb2f2e558
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 701 additions and 568 deletions

View File

@ -1,6 +1,7 @@
using System.Globalization;
using System.Text.Encodings.Web;
using AntDesign;
using AntDesign.Filters;
using AntDesign.JsInterop;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
@ -36,6 +37,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddScoped<ImageService>();
services.TryAddScoped<ConfigService>();
services.TryAddSingleton<ReuseTabsService>();
services.TryAddSingleton<IFieldFilterTypeResolver, DefaultFieldFilterTypeResolver>();
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.CurrentCulture;

View File

@ -34,7 +34,12 @@ namespace AntDesign
public static bool IsTypeNullable<T>()
{
return Nullable.GetUnderlyingType(typeof(T)) != null;
return IsTypeNullable(typeof(T));
}
public static bool IsTypeNullable(Type type)
{
return Nullable.GetUnderlyingType(type) != null;
}
public static Type GetNullableType<T>()
@ -76,29 +81,21 @@ namespace AntDesign
return targetType;
}
public static bool IsNumericType<T>()
public static bool IsNumericType(this Type type)
{
Type type = GetUnderlyingType<T>();
if (type == null)
{
return false;
}
return Type.GetTypeCode(type) switch
{
TypeCode.Byte
or TypeCode.Decimal
or TypeCode.Double
or TypeCode.Int16
or TypeCode.Int32
or TypeCode.Int64
or TypeCode.SByte
or TypeCode.Single
or TypeCode.UInt16
or TypeCode.UInt32
or TypeCode.UInt64 => true,
_ => false,
};
return type != null
&& Type.GetTypeCode(type)
is TypeCode.Byte
or TypeCode.Decimal
or TypeCode.Double
or TypeCode.Int16
or TypeCode.Int32
or TypeCode.Int64
or TypeCode.SByte
or TypeCode.Single
or TypeCode.UInt16
or TypeCode.UInt32
or TypeCode.UInt64;
}
}
}

View File

@ -1,6 +1,7 @@
@namespace AntDesign
@inherits ColumnBase
@using AntDesign.Core.Helpers
@using AntDesign.Filters
@using AntDesign.TableModels
@using Microsoft.AspNetCore.Components.Rendering;
@typeparam TData
@ -243,10 +244,9 @@ else if (IsBody && RowSpan != 0 && ColSpan != 0)
</Template>
;
private void RenderDefaultFilterDropdown(RenderTreeBuilder __builder)
{
@if (_filters?.Any() == true && _columnFilterType == TableFilterType.List)
@if (_filters.Any() == true && _columnFilterType == TableFilterType.List)
{
<Menu AutoCloseDropdown="false" SelectedKeys="_selectedFilterValues">
@foreach (var filter in _filters)
@ -267,11 +267,11 @@ else if (IsBody && RowSpan != 0 && ColSpan != 0)
else
{
<div id="@("popup-container-for-" + Id)" style="position:relative!important"></div>
int index = 0;
int filterCount = _filters.Count();
@for (int index = 0; index < filterCount; index++)
@foreach (TableFilter filter in _filters)
{
var filter = _filters.ElementAt(index);
var noInput = filter.FilterCompareOperator == TableFilterCompareOperator.IsNull || filter.FilterCompareOperator == TableFilterCompareOperator.IsNotNull;
var noInput = filter.FilterCompareOperator is TableFilterCompareOperator.IsNull or TableFilterCompareOperator.IsNotNull;
<div @key="filter" style="padding:10px;min-width:150px;@(index > 0 ? "border-top:1px solid #f0f0f0" : "")">
@if (index > 0)
{
@ -292,39 +292,13 @@ else if (IsBody && RowSpan != 0 && ColSpan != 0)
<SpaceItem Style="flex:auto">
<Select Value="filter.FilterCompareOperator" TItemValue="TableFilterCompareOperator" TItem="TableFilterCompareOperator" Style="width: 100%; overflow: visible" ValueChanged="value => SetFilterCompareOperator(filter,value)" PopupContainerSelector="@("#popup-container-for-" + Id)" BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None" DropdownMatchSelectWidth="false">
<SelectOptions>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.Equals" Label="@Table?.Locale.FilterOptions.Equals"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.NotEquals" Label="@Table?.Locale.FilterOptions.NotEquals"></SelectOption>
@if (_columnDataType == typeof(string))
{
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.Contains" Label="@Table?.Locale.FilterOptions.Contains"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.StartsWith" Label="@Table?.Locale.FilterOptions.StartsWith"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.EndsWith" Label="@Table?.Locale.FilterOptions.EndsWith"></SelectOption>
}
else if (_columnDataType.IsEnum)
{
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.Contains" Label="@Table?.Locale.FilterOptions.Contains"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.NotContains" Label="@Table?.Locale.FilterOptions.NotContains"></SelectOption>
}
else if (_columnDataType == typeof(DateTime))
{
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.GreaterThan" Label="@Table?.Locale.FilterOptions.GreaterThan"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.LessThan" Label="@Table?.Locale.FilterOptions.LessThan"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.GreaterThanOrEquals" Label="@Table?.Locale.FilterOptions.GreaterThanOrEquals"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.LessThanOrEquals" Label="@Table?.Locale.FilterOptions.LessThanOrEquals"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.TheSameDateWith" Label="@Table?.Locale.FilterOptions.TheSameDateWith"></SelectOption>
}
else if (_columnDataType == typeof(Guid)) { }
else
{
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.GreaterThan" Label="@Table?.Locale.FilterOptions.GreaterThan"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.LessThan" Label="@Table?.Locale.FilterOptions.LessThan"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.GreaterThanOrEquals" Label="@Table?.Locale.FilterOptions.GreaterThanOrEquals"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.LessThanOrEquals" Label="@Table?.Locale.FilterOptions.LessThanOrEquals"></SelectOption>
}
@if (THelper.IsTypeNullable<TData>() || _columnDataType == typeof(string))
{
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.IsNull" Label="@Table?.Locale.FilterOptions.IsNull"></SelectOption>
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator" Value="@TableFilterCompareOperator.IsNotNull" Label="@Table?.Locale.FilterOptions.IsNotNull"></SelectOption>
@foreach (TableFilterCompareOperator compareOperator in _fieldFilterType.SupportedCompareOperators) {
if (compareOperator is TableFilterCompareOperator.IsNull or TableFilterCompareOperator.IsNotNull
&& !(THelper.IsTypeNullable<TData>() || _columnDataType == typeof(string)))
continue;
<SelectOption TItem="TableFilterCompareOperator" TItemValue="TableFilterCompareOperator"
Value="@compareOperator" Label="@Table?.Locale.FilterOptions.Operator(compareOperator)"/>
}
</SelectOptions>
</Select>
@ -332,7 +306,7 @@ else if (IsBody && RowSpan != 0 && ColSpan != 0)
@if (!noInput)
{
<SpaceItem>
@FilterInput(filter)
@_fieldFilterType.FilterInput(new TableFilterInputRenderOptions(filter, "#popup-container-for-" + Id, Format))
</SpaceItem>
}
@if (filterCount > 1)
@ -344,6 +318,7 @@ else if (IsBody && RowSpan != 0 && ColSpan != 0)
}
</Space>
</div>
index++;
}
}
<div class="ant-table-filter-dropdown-btns">
@ -360,86 +335,4 @@ else if (IsBody && RowSpan != 0 && ColSpan != 0)
</Button>
</div>
}
RenderFragment<TableFilter> FilterInput => filter =>
@<Template>
@if (_columnDataType == typeof(DateTime))
{
if (filter.FilterCompareOperator != TableFilterCompareOperator.TheSameDateWith)
{
<DatePicker Value="(DateTime?)filter.Value" ShowTime="@true"
TValue="DateTime?" ValueChanged="value => SetFilterValue(filter, value?.AddMilliseconds(-value.Value.Millisecond))"
PopupContainerSelector="@("#popup-container-for-" + Id)"
BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None"/>
}
else
{
<DatePicker Value="(DateTime?)filter.Value" TValue="DateTime?" ValueChanged="value => SetFilterValue(filter, value)"
PopupContainerSelector="@("#popup-container-for-" + Id)"
BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None"/>
}
}
else if (_columnDataType.IsEnum)
{
<EnumSelect TEnum="TData" Mode="multiple"
@*Values="EnumHelper<TData>.Split(filter.Value)" workaround for https://github.com/ant-design-blazor/ant-design-blazor/issues/3006 *@
PopupContainerSelector="@("#popup-container-for-" + Id)"
Style="width:180px;"
ValuesChanged="value => SetFilterValue(filter, EnumHelper<TData>.Combine(value))"
BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None"/>
}
else if (_columnDataType == typeof(byte))
{
<InputNumber Value="(byte?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="byte?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(decimal))
{
<InputNumber Value="(decimal?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="decimal?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(double))
{
<InputNumber Value="(double?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="double?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(short))
{
<InputNumber Value="(short?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="short?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(int))
{
<InputNumber Value="(int?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="int?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(long))
{
<InputNumber Value="(long?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="long?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(sbyte))
{
<InputNumber Value="(sbyte?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="sbyte?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(float))
{
<InputNumber Value="(float?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="float?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(ushort))
{
<InputNumber Value="(ushort?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="ushort?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(uint))
{
<InputNumber Value="(uint?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="uint?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(ulong))
{
<InputNumber Value="(ulong?)filter.Value" Formatter="number => NumberFormatter(number)" TValue="ulong?" ValueChanged="value => SetFilterValue(filter, value)"></InputNumber>
}
else if (_columnDataType == typeof(Guid))
{
<Input Value="(Guid?)filter.Value" TValue="Guid?" ValueChanged="value => SetFilterValue(filter, value)" />
}
else
{
<Input TValue="TData" Value="(TData)filter.Value" ValueChanged="value => SetFilterValue(filter, value)" />
}
</Template>
;
}

View File

@ -10,6 +10,7 @@ using AntDesign.TableModels;
using Microsoft.AspNetCore.Components;
using System.Text.Json;
using AntDesign.Core.Helpers;
using AntDesign.Filters;
namespace AntDesign
{
@ -110,9 +111,15 @@ namespace AntDesign
}
}
[Parameter]
public IEnumerable<TableFilter> DefaultFilters { get; set; }
[Parameter]
public bool FilterMultiple { get; set; } = true;
[Parameter]
public IFieldFilterType FieldFilterType { get; set; }
/// <summary>
/// Function that determines if the row is displayed when filtered
/// <para>
@ -129,6 +136,7 @@ namespace AntDesign
public virtual RenderFragment<CellData<TData>> CellRender { get; set; }
private TableFilterType _columnFilterType;
private IFieldFilterType _fieldFilterType;
private Type _columnDataType;
@ -235,43 +243,43 @@ namespace AntDesign
else if (_hasFilterableAttribute)
{
_columnDataType = THelper.GetUnderlyingType<TData>();
if (_columnDataType == typeof(bool))
_columnFilterType = TableFilterType.FieldType;
if (FieldFilterType is null)
{
_columnFilterType = TableFilterType.List;
_filters = new List<TableFilter>();
var trueFilterOption = GetNewFilter();
trueFilterOption.Text = Table.Locale.FilterOptions.True;
trueFilterOption.Value = true;
((List<TableFilter>)_filters).Add(trueFilterOption);
var falseFilterOption = GetNewFilter();
falseFilterOption.Text = Table.Locale.FilterOptions.False;
falseFilterOption.Value = false;
((List<TableFilter>)_filters).Add(falseFilterOption);
}
else if (_columnDataType.IsEnum && _columnDataType.GetCustomAttribute<FlagsAttribute>() == null)
{
_columnFilterType = TableFilterType.List;
_filters = EnumHelper<TData>.GetValueLabelList().Select(item =>
if (_columnDataType == typeof(bool))
{
var filterOption = GetNewFilter();
filterOption.Text = item.Label;
filterOption.Value = item.Value;
return filterOption;
}).ToList();
}
else
{
_columnFilterType = TableFilterType.FieldType;
InitFilters();
_columnFilterType = TableFilterType.List;
_filters = new List<TableFilter>();
var trueFilterOption = GetNewFilter();
trueFilterOption.Text = Table.Locale.FilterOptions.True;
trueFilterOption.Value = true;
((List<TableFilter>)_filters).Add(trueFilterOption);
var falseFilterOption = GetNewFilter();
falseFilterOption.Text = Table.Locale.FilterOptions.False;
falseFilterOption.Value = false;
((List<TableFilter>)_filters).Add(falseFilterOption);
}
else if (_columnDataType.IsEnum && _columnDataType.GetCustomAttribute<FlagsAttribute>() == null)
{
_columnFilterType = TableFilterType.List;
_filters = EnumHelper<TData>.GetValueLabelList().Select(item =>
{
var filterOption = GetNewFilter();
filterOption.Text = item.Label;
filterOption.Value = item.Value;
return filterOption;
}).ToList();
}
}
if (_columnFilterType == TableFilterType.List && THelper.IsTypeNullable<TData>())
{
var nullFilterOption = GetNewFilter();
nullFilterOption.Text = Table.Locale.FilterOptions.IsNull;
nullFilterOption.Text = Table.Locale.FilterOptions.Operator(TableFilterCompareOperator.IsNull);
nullFilterOption.Value = null;
((List<TableFilter>)_filters).Add(nullFilterOption);
}
@ -293,8 +301,20 @@ namespace AntDesign
if (IsHeader)
{
if (_columnFilterType == TableFilterType.FieldType && _fieldFilterType == null && Filterable)
{
_fieldFilterType = FieldFilterType ?? Table.FieldFilterTypeResolver.Resolve<TData>();
if (DefaultFilters is null)
ResetFieldFilters();
else
{
_filters = DefaultFilters;
_hasFilterSelected = DefaultFilters.Any(f => f.Selected);
}
}
FilterModel = _filterable && _filters?.Any(x => x.Selected) == true ?
new FilterModel<TData>(this, GetFieldExpression, FieldName, OnFilter, _filters.Where(x => x.Selected).ToList(), _columnFilterType) :
new FilterModel<TData>(this, GetFieldExpression, FieldName, OnFilter, _filters.Where(x => x.Selected).ToList(), _columnFilterType, _fieldFilterType) :
null;
}
}
@ -305,12 +325,6 @@ namespace AntDesign
return true;
}
private string NumberFormatter(object value)
{
if (value == null) return null;
return Convert.ToDouble(value).ToString(Format);
}
private void HandleSort()
{
if (Sortable)
@ -369,11 +383,6 @@ namespace AntDesign
filter.FilterCondition = filterCondition;
}
private void SetFilterValue(TableFilter filter, object value)
{
filter.Value = value;
}
private void FilterSelected(TableFilter filter)
{
if (_columnFilterType == TableFilterType.FieldType) return;
@ -405,7 +414,10 @@ namespace AntDesign
});
}
_hasFilterSelected = _filters?.Any(x => x.Selected) == true;
FilterModel = _hasFilterSelected ? new FilterModel<TData>(this, GetFieldExpression, FieldName, OnFilter, _filters.Where(x => x.Selected).ToList(), _columnFilterType) : null;
FilterModel = _hasFilterSelected
? new FilterModel<TData>(this, GetFieldExpression, FieldName, OnFilter,
_filters.Where(x => x.Selected).ToList(), _columnFilterType, _fieldFilterType)
: null;
Table?.ColumnFilterChange();
}
@ -418,7 +430,7 @@ namespace AntDesign
}
else
{
InitFilters();
ResetFieldFilters();
}
FilterConfirm(true);
}
@ -440,7 +452,7 @@ namespace AntDesign
return new TableFilter()
{
FilterCondition = TableFilterCondition.And,
FilterCompareOperator = _columnDataType == typeof(string) ? TableFilterCompareOperator.Contains : TableFilterCompareOperator.Equals
FilterCompareOperator = _fieldFilterType.DefaultCompareOperator
};
}
else
@ -453,7 +465,7 @@ namespace AntDesign
}
}
private void InitFilters()
private void ResetFieldFilters()
{
_filters = new List<TableFilter>() { GetNewFilter() };
}
@ -482,7 +494,8 @@ namespace AntDesign
_filters = filterModel.Filters;
}
FilterModel = new FilterModel<TData>(this, GetFieldExpression, FieldName, OnFilter, _filters.Where(x => x.Selected).ToList(), _columnFilterType);
FilterModel = new FilterModel<TData>(this, GetFieldExpression, FieldName, OnFilter,
_filters.Where(x => x.Selected).ToList(), _columnFilterType, _fieldFilterType);
_hasFilterSelected = true;
}

View File

@ -1,97 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using System.Linq.Expressions;
namespace AntDesign.FilterExpression
{
public class DateFilterExpression : IFilterExpression
{
public TableFilterCompareOperator GetDefaultCompareOperator()
{
return TableFilterCompareOperator.Equals;
}
public Expression GetFilterExpression(TableFilterCompareOperator compareOperator, Expression leftExpr, Expression rightExpr)
{
switch (compareOperator)
{
case TableFilterCompareOperator.IsNull:
return Expression.Equal(leftExpr, rightExpr);
case TableFilterCompareOperator.IsNotNull:
return Expression.NotEqual(leftExpr, rightExpr);
}
if (leftExpr.Type.IsGenericType)
{
Expression notNull = Expression.NotEqual(leftExpr, Expression.Constant(null));
Expression isNull = Expression.Equal(leftExpr, Expression.Constant(null));
leftExpr = Expression.Property(leftExpr, "Value");
rightExpr = Expression.Property(rightExpr, "Value");
if (compareOperator != TableFilterCompareOperator.TheSameDateWith)
{
leftExpr = RemoveMilliseconds(leftExpr);
}
switch (compareOperator)
{
case TableFilterCompareOperator.Equals:
return Expression.AndAlso(notNull, Expression.Equal(leftExpr, rightExpr));
case TableFilterCompareOperator.NotEquals:
return Expression.OrElse(isNull, Expression.NotEqual(leftExpr, rightExpr));
case TableFilterCompareOperator.GreaterThan:
return Expression.AndAlso(notNull, Expression.GreaterThan(leftExpr, rightExpr));
case TableFilterCompareOperator.GreaterThanOrEquals:
return Expression.AndAlso(notNull, Expression.GreaterThanOrEqual(leftExpr, rightExpr));
case TableFilterCompareOperator.LessThan:
return Expression.AndAlso(notNull, Expression.LessThan(leftExpr, rightExpr));
case TableFilterCompareOperator.LessThanOrEquals:
return Expression.AndAlso(notNull, Expression.LessThanOrEqual(leftExpr, rightExpr));
case TableFilterCompareOperator.TheSameDateWith:
return Expression.AndAlso(notNull,
Expression.Equal(
Expression.Property(leftExpr, "Date"),
Expression.Property(rightExpr, "Date")));
}
throw new InvalidOperationException();
}
if (compareOperator != TableFilterCompareOperator.TheSameDateWith)
{
leftExpr = RemoveMilliseconds(leftExpr);
}
switch (compareOperator)
{
case TableFilterCompareOperator.Equals:
return Expression.Equal(leftExpr, rightExpr);
case TableFilterCompareOperator.NotEquals:
return Expression.NotEqual(leftExpr, rightExpr);
case TableFilterCompareOperator.GreaterThan:
return Expression.GreaterThan(leftExpr, rightExpr);
case TableFilterCompareOperator.GreaterThanOrEquals:
return Expression.GreaterThanOrEqual(leftExpr, rightExpr);
case TableFilterCompareOperator.LessThan:
return Expression.LessThan(leftExpr, rightExpr);
case TableFilterCompareOperator.LessThanOrEquals:
return Expression.LessThanOrEqual(leftExpr, rightExpr);
case TableFilterCompareOperator.TheSameDateWith:
return Expression.Equal(
Expression.Property(leftExpr, "Date"),
Expression.Property(rightExpr, "Date"));
}
throw new InvalidOperationException();
}
private static Expression RemoveMilliseconds(Expression dateTimeExpression)
{
return Expression.Call(dateTimeExpression, typeof(DateTime).GetMethod("AddMilliseconds")!,
Expression.Convert(
Expression.Subtract(Expression.Constant(0),
Expression.MakeMemberAccess(dateTimeExpression,
typeof(DateTime).GetMember("Millisecond").First())), typeof(double)));
}
}
}

View File

@ -1,62 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
namespace AntDesign.FilterExpression
{
public class EnumFilterExpression : IFilterExpression
{
public TableFilterCompareOperator GetDefaultCompareOperator()
{
return TableFilterCompareOperator.Equals;
}
public Expression GetFilterExpression(TableFilterCompareOperator compareOperator, Expression leftExpr, Expression rightExpr)
{
switch (compareOperator)
{
case TableFilterCompareOperator.IsNull:
case TableFilterCompareOperator.Equals:
return Expression.Equal(leftExpr, rightExpr);
case TableFilterCompareOperator.IsNotNull:
case TableFilterCompareOperator.NotEquals:
return Expression.NotEqual(leftExpr, rightExpr);
case TableFilterCompareOperator.Contains:
{
MethodInfo mi = typeof(Enum).GetMethod(nameof(Enum.HasFlag));
if (leftExpr.Type.IsGenericType)
{
Expression notNull = Expression.NotEqual(leftExpr, Expression.Constant(null));
leftExpr = Expression.MakeMemberAccess(leftExpr, leftExpr.Type.GetMember("Value")[0]);
rightExpr = Expression.MakeMemberAccess(rightExpr, rightExpr.Type.GetMember("Value")[0]);
rightExpr = Expression.Convert(rightExpr, typeof(Enum));
return Expression.AndAlso(notNull, Expression.Call(leftExpr, mi, rightExpr));
}
rightExpr = Expression.Convert(rightExpr, typeof(Enum));
return Expression.Call(leftExpr, mi, rightExpr);
}
case TableFilterCompareOperator.NotContains:
{
MethodInfo mi = typeof(Enum).GetMethod(nameof(Enum.HasFlag));
if (leftExpr.Type.IsGenericType)
{
Expression notNull = Expression.NotEqual(leftExpr, Expression.Constant(null));
leftExpr = Expression.MakeMemberAccess(leftExpr, leftExpr.Type.GetMember("Value")[0]);
rightExpr = Expression.MakeMemberAccess(rightExpr, rightExpr.Type.GetMember("Value")[0]);
rightExpr = Expression.Convert(rightExpr, typeof(Enum));
return Expression.Equal(Expression.AndAlso(notNull, Expression.Call(leftExpr, mi, rightExpr)), Expression.Constant(false, typeof(bool)));
}
rightExpr = Expression.Convert(rightExpr, typeof(Enum));
return Expression.Equal(Expression.Call(leftExpr, mi, rightExpr), Expression.Constant(false, typeof(bool)));
}
}
throw new InvalidOperationException();
}
}
}

View File

@ -1,47 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace AntDesign.FilterExpression
{
public class FilterExpressionResolver<T>
{
private readonly StringFilterExpression _stringFilter = new StringFilterExpression();
private readonly NumberFilterExpression _numberFilter = new NumberFilterExpression(typeof(T));
private readonly DateFilterExpression _dateFilter = new DateFilterExpression();
private readonly EnumFilterExpression _enumFilter = new EnumFilterExpression();
private readonly GuidFilterExpression _guidFilter = new GuidFilterExpression();
public FilterExpressionResolver()
{
}
public IFilterExpression GetFilterExpression()
{
if (THelper.GetUnderlyingType<T>().IsEnum)
{
return _enumFilter;
}
if (THelper.IsNumericType<T>())
{
return _numberFilter;
}
var underlyingType = THelper.GetUnderlyingType<T>();
if (underlyingType == typeof(DateTime))
{
return _dateFilter;
}
if (underlyingType == typeof(string))
{
return _stringFilter;
}
if (underlyingType == typeof(Guid))
{
return _guidFilter;
}
throw new NotImplementedException();
}
}
}

View File

@ -1,35 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
namespace AntDesign.FilterExpression
{
public class GuidFilterExpression : IFilterExpression
{
public TableFilterCompareOperator GetDefaultCompareOperator()
{
return TableFilterCompareOperator.Equals;
}
public Expression GetFilterExpression(TableFilterCompareOperator compareOperator, Expression leftExpr, Expression rightExpr)
{
switch (compareOperator)
{
case TableFilterCompareOperator.IsNull:
case TableFilterCompareOperator.Equals:
return Expression.Equal(leftExpr, rightExpr);
case TableFilterCompareOperator.IsNotNull:
case TableFilterCompareOperator.NotEquals:
return Expression.NotEqual(leftExpr, rightExpr);
default:
throw new InvalidOperationException($"The compare operator {compareOperator} is not supported for Guid type.");
}
}
}
}

View File

@ -1,48 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
namespace AntDesign.FilterExpression
{
public class NumberFilterExpression : IFilterExpression
{
private readonly Type _type;
public NumberFilterExpression(Type type)
{
_type = type;
}
public TableFilterCompareOperator GetDefaultCompareOperator()
{
return TableFilterCompareOperator.Equals;
}
public Expression GetFilterExpression(TableFilterCompareOperator compareOperator, Expression leftExpr, Expression rightExpr)
{
rightExpr = Expression.Convert(rightExpr, _type);
switch (compareOperator)
{
case TableFilterCompareOperator.IsNull:
case TableFilterCompareOperator.Equals:
return Expression.Equal(leftExpr, rightExpr);
case TableFilterCompareOperator.IsNotNull:
case TableFilterCompareOperator.NotEquals:
return Expression.NotEqual(leftExpr, rightExpr);
case TableFilterCompareOperator.GreaterThan:
return Expression.GreaterThan(leftExpr, rightExpr);
case TableFilterCompareOperator.GreaterThanOrEquals:
return Expression.GreaterThanOrEqual(leftExpr, rightExpr);
case TableFilterCompareOperator.LessThan:
return Expression.LessThan(leftExpr, rightExpr);
case TableFilterCompareOperator.LessThanOrEquals:
return Expression.LessThanOrEqual(leftExpr, rightExpr);
}
throw new InvalidOperationException();
}
}
}

View File

@ -1,51 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
namespace AntDesign.FilterExpression
{
public class StringFilterExpression : IFilterExpression
{
public TableFilterCompareOperator GetDefaultCompareOperator()
{
return TableFilterCompareOperator.Contains;
}
public Expression GetFilterExpression(TableFilterCompareOperator compareOperator, Expression leftExpr, Expression rightExpr)
{
return GetCaseInsensitiveComparation(compareOperator, leftExpr, rightExpr);
}
private Expression GetCaseInsensitiveComparation(TableFilterCompareOperator compareOperator, Expression leftExpr, Expression rightExpr)
{
MethodInfo miLower = typeof(string).GetMethod("ToLower", Array.Empty<Type>());
Expression notNull = Expression.NotEqual(leftExpr, Expression.Constant(null));
MethodCallExpression lowerLeftExpr = Expression.Call(leftExpr, miLower);
MethodCallExpression lowerRightExpr = Expression.Call(rightExpr, miLower);
switch (compareOperator)
{
case TableFilterCompareOperator.IsNull:
return Expression.Equal(leftExpr, rightExpr);
case TableFilterCompareOperator.Equals:
return Expression.AndAlso(notNull, Expression.Equal(lowerLeftExpr, lowerRightExpr));
case TableFilterCompareOperator.IsNotNull:
return Expression.NotEqual(leftExpr, rightExpr);
case TableFilterCompareOperator.NotEquals:
return Expression.AndAlso(notNull, Expression.NotEqual(lowerLeftExpr, lowerRightExpr));
default:
string methodName = Enum.GetName(typeof(TableFilterCompareOperator), compareOperator);
MethodInfo mi = typeof(string).GetMethod(methodName, new[] { typeof(string) });
if (mi == null)
throw new MissingMethodException("There is no method - " + methodName);
return Expression.AndAlso(notNull, Expression.Call(lowerLeftExpr, mi, lowerRightExpr));
}
}
}
}

View File

@ -0,0 +1,40 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Components;
namespace AntDesign.Filters;
public abstract class BaseFieldFilterType : IFieldFilterType
{
public virtual TableFilterCompareOperator DefaultCompareOperator => TableFilterCompareOperator.Equals;
public abstract RenderFragment<TableFilterInputRenderOptions> FilterInput { get; }
public virtual IEnumerable<TableFilterCompareOperator> SupportedCompareOperators { get; set; } = _supportedCompareOperators;
private static IEnumerable<TableFilterCompareOperator> _supportedCompareOperators = new[]
{
TableFilterCompareOperator.Equals,
TableFilterCompareOperator.NotEquals,
};
public virtual Expression GetFilterExpression(TableFilterCompareOperator compareOperator, Expression leftExpr,
Expression rightExpr)
=> compareOperator switch
{
TableFilterCompareOperator.Equals => Expression.Equal(leftExpr, rightExpr),
TableFilterCompareOperator.NotEquals => Expression.NotEqual(leftExpr, rightExpr),
TableFilterCompareOperator.GreaterThan => Expression.GreaterThan(leftExpr, rightExpr),
TableFilterCompareOperator.LessThan => Expression.LessThan(leftExpr, rightExpr),
TableFilterCompareOperator.GreaterThanOrEquals => Expression.GreaterThanOrEqual(leftExpr, rightExpr),
TableFilterCompareOperator.LessThanOrEquals => Expression.LessThanOrEqual(leftExpr, rightExpr),
TableFilterCompareOperator.IsNull => Expression.Equal(leftExpr, rightExpr),
TableFilterCompareOperator.IsNotNull => Expression.NotEqual(leftExpr, rightExpr),
_ => throw new NotSupportedException($"{nameof(TableFilterCompareOperator)} {compareOperator} is not supported by {GetType().Name}!")
};
}

View File

@ -0,0 +1,93 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Components;
namespace AntDesign.Filters
{
public abstract class DateFieldFilterType : BaseFieldFilterType
{
private static IEnumerable<TableFilterCompareOperator> _supportedCompareOperators = new[]
{
TableFilterCompareOperator.Equals,
TableFilterCompareOperator.NotEquals,
TableFilterCompareOperator.GreaterThan,
TableFilterCompareOperator.LessThan,
TableFilterCompareOperator.GreaterThanOrEquals,
TableFilterCompareOperator.LessThanOrEquals
};
public DateFieldFilterType()
{
SupportedCompareOperators = _supportedCompareOperators;
}
protected virtual Expression GetNonNullFilterExpression(TableFilterCompareOperator compareOperator,
Expression leftExpr, Expression rightExpr)
=> base.GetFilterExpression(compareOperator, leftExpr, rightExpr);
public override sealed Expression GetFilterExpression(TableFilterCompareOperator compareOperator, Expression leftExpr, Expression rightExpr)
{
switch (compareOperator)
{
case TableFilterCompareOperator.IsNull:
return Expression.Equal(leftExpr, rightExpr);
case TableFilterCompareOperator.IsNotNull:
return Expression.NotEqual(leftExpr, rightExpr);
}
if (THelper.IsTypeNullable(leftExpr.Type))
{
Expression notNull = Expression.NotEqual(leftExpr, Expression.Constant(null));
Expression isNull = Expression.Equal(leftExpr, Expression.Constant(null));
leftExpr = Expression.Property(leftExpr, nameof(Nullable<DateTime>.Value));
rightExpr = Expression.Property(rightExpr, nameof(Nullable<DateTime>.Value));
return compareOperator switch
{
TableFilterCompareOperator.NotEquals => Expression.OrElse(isNull, GetNonNullFilterExpression(compareOperator, leftExpr, rightExpr)),
_ => Expression.AndAlso(notNull, GetNonNullFilterExpression(compareOperator, leftExpr, rightExpr))
};
}
return GetNonNullFilterExpression(compareOperator, leftExpr, rightExpr);
}
}
public class DateTimeFieldFilterType : DateFieldFilterType
{
public override RenderFragment<TableFilterInputRenderOptions> FilterInput => FilterInputs.Instance.DateTimeInput;
protected override Expression GetNonNullFilterExpression(TableFilterCompareOperator compareOperator,
Expression leftExpr, Expression rightExpr)
{
if (compareOperator != TableFilterCompareOperator.TheSameDateWith)
{
leftExpr = RemoveMilliseconds(leftExpr);
}
return compareOperator switch
{
TableFilterCompareOperator.TheSameDateWith => Expression.Equal(
Expression.Property(leftExpr, nameof(DateTime.Date)),
Expression.Property(rightExpr, nameof(DateTime.Date))),
_ => base.GetNonNullFilterExpression(compareOperator, leftExpr, rightExpr)
};
}
private static Expression RemoveMilliseconds(Expression dateTimeExpression)
{
return Expression.Call(dateTimeExpression, typeof(DateTime).GetMethod(nameof(DateTime.AddMilliseconds))!,
Expression.Convert(
Expression.Subtract(Expression.Constant(0),
Expression.MakeMemberAccess(dateTimeExpression,
typeof(DateTime).GetMember(nameof(DateTime.Millisecond)).First())), typeof(double)));
}
}
}

View File

@ -0,0 +1,61 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.AspNetCore.Components;
namespace AntDesign.Filters
{
public class EnumFieldFilterType<T> : BaseFieldFilterType
{
private static readonly MethodInfo _enumHasFlag = typeof(Enum).GetMethod(nameof(Enum.HasFlag));
public override RenderFragment<TableFilterInputRenderOptions> FilterInput { get; } = FilterInputs.Instance.GetEnumInput<T>();
private static IEnumerable<TableFilterCompareOperator> _supportedCompareOperators = new[]
{
TableFilterCompareOperator.Equals,
TableFilterCompareOperator.NotEquals,
TableFilterCompareOperator.Contains,
TableFilterCompareOperator.NotContains,
};
public EnumFieldFilterType()
{
SupportedCompareOperators = _supportedCompareOperators;
}
public override Expression GetFilterExpression(TableFilterCompareOperator compareOperator, Expression leftExpr, Expression rightExpr)
{
return compareOperator switch
{
TableFilterCompareOperator.Contains => ContainsExpression(),
TableFilterCompareOperator.NotContains => Expression.Not(ContainsExpression()),
_ => base.GetFilterExpression(compareOperator, leftExpr, rightExpr)
};
Expression ContainsExpression()
{
if (THelper.IsTypeNullable(leftExpr.Type))
{
Expression notNull = Expression.NotEqual(leftExpr, Expression.Constant(null));
leftExpr = Expression.MakeMemberAccess(leftExpr, leftExpr.Type.GetMember(nameof(Nullable<int>.Value))[0]);
rightExpr = Expression.MakeMemberAccess(rightExpr, rightExpr.Type.GetMember(nameof(Nullable<int>.Value))[0]);
return Expression.AndAlso(notNull, CallContains());
}
return CallContains();
Expression CallContains()
{
rightExpr = Expression.Convert(rightExpr, typeof(Enum));
return Expression.Call(leftExpr, _enumHasFlag, rightExpr);
}
}
}
}
}

View File

@ -0,0 +1,64 @@
@namespace AntDesign.Filters
@using AntDesign
@implements IHandleEvent
@{
// This is a utility .razor page, and it should not be used as a component.
throw new InvalidOperationException();
}
@code {
// Unfortunately, the razor compiler currently doesn't allow setting EventCallbacks (e.g. ValueChanged) in static fields/properties
// (see https://github.com/dotnet/aspnetcore/issues/24655 and https://github.com/dotnet/aspnetcore/issues/18919)
// Therefore, we need to implement them as instance properties and use a singleton pattern
public static FilterInputs Instance { get; } = new();
public readonly RenderFragment<TableFilterInputRenderOptions> DateTimeInput, GuidInput;
private FilterInputs() {
DateTimeInput = filter =>
@<text>
@if (filter.FilterCompareOperator != TableFilterCompareOperator.TheSameDateWith) {
<DatePicker Value="(DateTime?)filter.Value" ShowTime="true"
TValue="DateTime?" ValueChanged="value => filter.Value = value?.AddMilliseconds(-value.Value.Millisecond)"
PopupContainerSelector="@filter.PopupContainerSelector"
BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None"/>
}
else {
<DatePicker Value="(DateTime?)filter.Value" TValue="DateTime?" ValueChanged="value => filter.Value = value"
PopupContainerSelector="@filter.PopupContainerSelector"
BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None"/>
}
</text>;
GuidInput = filter =>
@<Input Value="(Guid?)filter.Value" TValue="Guid?" ValueChanged="value => filter.Value = value"/>;
}
public RenderFragment<TableFilterInputRenderOptions> GetEnumInput<TData>() => filter =>
@<text>
<EnumSelect TEnum="TData" Mode="multiple"
@*Values="EnumHelper<TData>.Split(filter.Value)" workaround for https: //github.com/ant-design-blazor/ant-design-blazor/issues/3006 *@
PopupContainerSelector="@filter.PopupContainerSelector"
Style="width:180px;"
ValuesChanged="value => filter.Value = EnumHelper<TData>.Combine(value)"
BoundaryAdjustMode="@TriggerBoundaryAdjustMode.None" />
</text>;
public RenderFragment<TableFilterInputRenderOptions> GetNumberInput<TData>() where TData : struct => filter =>
@<InputNumber Value="(TData?)filter.Value" Formatter="number => NumberFormatter(number, filter.Format)" TValue="TData?" ValueChanged="value => filter.Value = value"/>;
private string NumberFormatter(object value, string format)
{
if (value == null) return null;
return Convert.ToDouble(value).ToString(format);
}
public RenderFragment<TableFilterInputRenderOptions> GetInput<TData>() => filter =>
@<Input TValue="TData" Value="(TData)filter.Value" ValueChanged="value => filter.Value = value" />;
// https://github.com/dotnet/aspnetcore/issues/24655
Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object arg) => callback.InvokeAsync(arg);
}

View File

@ -0,0 +1,42 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace AntDesign.Filters;
public interface IFieldFilterTypeResolver
{
public IFieldFilterType Resolve<T>();
}
public class DefaultFieldFilterTypeResolver : IFieldFilterTypeResolver
{
public IFieldFilterType Resolve<T>()
{
var underlyingType = THelper.GetUnderlyingType<T>();
#pragma warning disable format
return underlyingType switch
{
_ when underlyingType.IsEnum => new EnumFieldFilterType<T>(),
_ when underlyingType == typeof(byte) => new NumberFieldFilterType<byte>(),
_ when underlyingType == typeof(decimal) => new NumberFieldFilterType<decimal>(),
_ when underlyingType == typeof(double) => new NumberFieldFilterType<double>(),
_ when underlyingType == typeof(short) => new NumberFieldFilterType<short>(),
_ when underlyingType == typeof(int) => new NumberFieldFilterType<int>(),
_ when underlyingType == typeof(long) => new NumberFieldFilterType<long>(),
_ when underlyingType == typeof(sbyte) => new NumberFieldFilterType<sbyte>(),
_ when underlyingType == typeof(float) => new NumberFieldFilterType<float>(),
_ when underlyingType == typeof(ushort) => new NumberFieldFilterType<ushort>(),
_ when underlyingType == typeof(uint) => new NumberFieldFilterType<uint>(),
_ when underlyingType == typeof(ulong) => new NumberFieldFilterType<ulong>(),
_ when underlyingType == typeof(DateTime) => new DateTimeFieldFilterType(),
_ when underlyingType == typeof(string) => new StringFieldFilterType(),
_ when underlyingType == typeof(Guid) => new GuidFieldFilterType(),
_ => throw new NotSupportedException()
};
#pragma warning restore format
}
}

View File

@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Components;
namespace AntDesign.Filters
{
public class GuidFieldFilterType : BaseFieldFilterType
{
public override RenderFragment<TableFilterInputRenderOptions> FilterInput => FilterInputs.Instance.GuidInput;
}
}

View File

@ -2,16 +2,18 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
using Microsoft.AspNetCore.Components;
namespace AntDesign.FilterExpression
namespace AntDesign.Filters
{
public interface IFilterExpression
public interface IFieldFilterType
{
TableFilterCompareOperator GetDefaultCompareOperator();
TableFilterCompareOperator DefaultCompareOperator { get; }
RenderFragment<TableFilterInputRenderOptions> FilterInput { get; }
IEnumerable<TableFilterCompareOperator> SupportedCompareOperators { get; set; }
Expression GetFilterExpression(TableFilterCompareOperator compareOperator, Expression leftExpr, Expression rightExpr);
}
}

View File

@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Components;
namespace AntDesign.Filters
{
public class NumberFieldFilterType<T> : BaseFieldFilterType where T : struct
{
public override RenderFragment<TableFilterInputRenderOptions> FilterInput { get; } =
FilterInputs.Instance.GetNumberInput<T>();
private static IEnumerable<TableFilterCompareOperator> _supportedCompareOperators = new[]
{
TableFilterCompareOperator.Equals,
TableFilterCompareOperator.NotEquals,
TableFilterCompareOperator.GreaterThan,
TableFilterCompareOperator.LessThan,
TableFilterCompareOperator.GreaterThanOrEquals,
TableFilterCompareOperator.LessThanOrEquals
};
public NumberFieldFilterType()
{
SupportedCompareOperators = _supportedCompareOperators;
}
public override Expression GetFilterExpression(TableFilterCompareOperator compareOperator, Expression leftExpr, Expression rightExpr)
{
return base.GetFilterExpression(compareOperator, leftExpr, Expression.Convert(rightExpr, leftExpr.Type));
}
}
}

View File

@ -0,0 +1,69 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.AspNetCore.Components;
namespace AntDesign.Filters
{
public class StringFieldFilterType : BaseFieldFilterType
{
private static readonly MethodInfo _stringToLower = typeof(string).GetMethod(nameof(string.ToLower), Array.Empty<Type>());
public override TableFilterCompareOperator DefaultCompareOperator => TableFilterCompareOperator.Contains;
public override RenderFragment<TableFilterInputRenderOptions> FilterInput { get; } =
FilterInputs.Instance.GetInput<string>();
private static IEnumerable<TableFilterCompareOperator> _supportedCompareOperators = new[]
{
TableFilterCompareOperator.Equals,
TableFilterCompareOperator.NotEquals,
TableFilterCompareOperator.Contains,
TableFilterCompareOperator.StartsWith,
TableFilterCompareOperator.EndsWith,
};
public StringFieldFilterType()
{
SupportedCompareOperators = _supportedCompareOperators;
}
public override Expression GetFilterExpression(TableFilterCompareOperator compareOperator, Expression leftExpr, Expression rightExpr)
{
MethodCallExpression lowerLeftExpr = Expression.Call(leftExpr, _stringToLower);
MethodCallExpression lowerRightExpr = Expression.Call(rightExpr, _stringToLower);
return compareOperator switch
{
TableFilterCompareOperator.IsNull or TableFilterCompareOperator.IsNotNull => base.GetFilterExpression(
compareOperator, leftExpr, rightExpr),
TableFilterCompareOperator.Contains => NotNullAnd(GetMethodExpression(nameof(string.Contains),
lowerLeftExpr, lowerRightExpr)),
TableFilterCompareOperator.StartsWith => NotNullAnd(GetMethodExpression(nameof(string.StartsWith),
lowerLeftExpr, lowerRightExpr)),
TableFilterCompareOperator.EndsWith => NotNullAnd(GetMethodExpression(nameof(string.EndsWith),
lowerLeftExpr, lowerRightExpr)),
TableFilterCompareOperator.NotEquals => Expression.OrElse(
Expression.Equal(leftExpr, Expression.Constant(null)),
base.GetFilterExpression(compareOperator, lowerLeftExpr, lowerRightExpr)),
_ => NotNullAnd(base.GetFilterExpression(compareOperator, lowerLeftExpr, lowerRightExpr))
};
Expression NotNullAnd(Expression innerExpression)
=> Expression.AndAlso(Expression.NotEqual(leftExpr, Expression.Constant(null)), innerExpression);
}
private static Expression GetMethodExpression(string methodName, Expression leftExpr, Expression rightExpr)
{
MethodInfo mi = typeof(string).GetMethod(methodName, new[] { typeof(string) });
if (mi == null)
throw new MissingMethodException("There is no method - " + methodName);
return Expression.Call(leftExpr, mi, rightExpr);
}
}
}

View File

@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace AntDesign.Filters;
public readonly record struct TableFilterInputRenderOptions(TableFilter Filter, string PopupContainerSelector, string Format)
{
public object Value { get => Filter.Value; set => Filter.Value = value; }
public TableFilterCompareOperator FilterCompareOperator => Filter.FilterCompareOperator;
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using AntDesign.Filters;
using AntDesign.TableModels;
namespace AntDesign
@ -72,5 +73,7 @@ namespace AntDesign
internal void AddSummaryRow(SummaryRow summaryRow);
internal void OnColumnInitialized();
IFieldFilterTypeResolver FieldFilterTypeResolver { get; }
}
}

View File

@ -207,13 +207,13 @@ RenderFragment<(Table<TItem> table, bool header)> colGroup = ctx =>
@if (table._tableWidth>0)
{
<div class="ant-table-expanded-row-fixed" style="width: @(table._tableWidth)px; position: sticky; left: 0px; overflow: hidden;">
@if (table.EmptyTemplate!=null) @table.EmptyTemplate else { <Empty Simple /> }
@if (table.EmptyTemplate!=null) { @table.EmptyTemplate } else { <Empty Simple /> }
</div>
}
}
else
{
@if (table.EmptyTemplate!=null) @table.EmptyTemplate else { <Empty Simple /> }
@if (table.EmptyTemplate!=null) { @table.EmptyTemplate } else { <Empty Simple /> }
}
</td>
</tr>

View File

@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading.Tasks;
using AntDesign.Core.HashCodes;
using AntDesign.Filters;
using AntDesign.JsInterop;
using AntDesign.TableModels;
using Microsoft.AspNetCore.Components;
@ -175,6 +176,9 @@ namespace AntDesign
/// </summary>
[Parameter] public bool Resizable { get; set; }
[Parameter]
public IFieldFilterTypeResolver FieldFilterTypeResolver { get; set; }
#if NET5_0_OR_GREATER
/// <summary>
/// Whether to enable virtualization feature or not, only works for .NET 5 and higher
@ -190,6 +194,9 @@ namespace AntDesign
[Inject]
private ILogger<Table<TItem>> Logger { get; set; }
[Inject]
private IFieldFilterTypeResolver InjectedFieldFilterTypeResolver { get; set; }
public ColumnContext ColumnContext { get; set; }
private IEnumerable<TItem> _showItems;
@ -594,6 +601,8 @@ namespace AntDesign
InitializePagination();
FlushCache();
FieldFilterTypeResolver ??= InjectedFieldFilterTypeResolver;
}
private IEnumerable<TItem> GetAllItemsByTopLevelItems(IEnumerable<TItem> items, bool onlySelectable = false)
@ -819,4 +828,4 @@ namespace AntDesign
_isReloading = true;
}
}
}
}

View File

@ -32,10 +32,10 @@ namespace AntDesign
public string CancelSort { get; set; } = "Click to cancel sort";
public FilterOptions FilterOptions { get; set; } = new();
public FilterOptionsLocale FilterOptions { get; set; } = new();
}
public class FilterOptions
public class FilterOptionsLocale
{
public string True { get; set; } = "True";
@ -70,5 +70,24 @@ namespace AntDesign
public string IsNotNull { get; set; } = "Is Not Null";
public string TheSameDateWith { get; set; } = "The Same Date With";
public string Operator(TableFilterCompareOperator compareOperator)
=> compareOperator switch
{
TableFilterCompareOperator.Equals => Equals,
TableFilterCompareOperator.Contains => Contains,
TableFilterCompareOperator.StartsWith => StartsWith,
TableFilterCompareOperator.EndsWith => EndsWith,
TableFilterCompareOperator.GreaterThan => GreaterThan,
TableFilterCompareOperator.LessThan => LessThan,
TableFilterCompareOperator.GreaterThanOrEquals => GreaterThanOrEquals,
TableFilterCompareOperator.LessThanOrEquals => LessThanOrEquals,
TableFilterCompareOperator.NotEquals => NotEquals,
TableFilterCompareOperator.IsNull => IsNull,
TableFilterCompareOperator.IsNotNull => IsNotNull,
TableFilterCompareOperator.NotContains => NotContains,
TableFilterCompareOperator.TheSameDateWith => TheSameDateWith,
_ => throw new ArgumentOutOfRangeException(nameof(compareOperator), compareOperator, null)
};
}
}

View File

@ -8,7 +8,7 @@ using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json.Serialization;
using AntDesign.FilterExpression;
using AntDesign.Filters;
namespace AntDesign.TableModels
{
@ -24,12 +24,11 @@ namespace AntDesign.TableModels
public int ColumnIndex => _columnIndex;
private readonly FilterExpressionResolver<TField> _filterExpressionResolver = new FilterExpressionResolver<TField>();
private TableFilterType FilterType { get; set; } = TableFilterType.List;
private LambdaExpression _getFieldExpression;
private int _columnIndex;
private readonly LambdaExpression _getFieldExpression;
private readonly int _columnIndex;
private readonly IFieldFilterType _fieldFilterType;
#if NET5_0_OR_GREATER
[JsonConstructor]
@ -42,7 +41,8 @@ namespace AntDesign.TableModels
this._columnIndex = columnIndex;
}
public FilterModel(IFieldColumn column, LambdaExpression getFieldExpression, string fieldName, Expression<Func<TField, TField, bool>> onFilter, IList<TableFilter> filters, TableFilterType filterType)
public FilterModel(IFieldColumn column, LambdaExpression getFieldExpression, string fieldName,
Expression<Func<TField, TField, bool>> onFilter, IList<TableFilter> filters, TableFilterType filterType, IFieldFilterType fieldFilterType)
{
this._getFieldExpression = getFieldExpression;
this.FieldName = fieldName;
@ -54,13 +54,15 @@ namespace AntDesign.TableModels
{
this.OnFilter = onFilter;
}
this.SelectedValues = filters.Select(x => x.Value?.ToString());
this.Filters = filters;
this.FilterType = filterType;
this._columnIndex = column.ColIndex;
this._fieldFilterType = fieldFilterType;
}
public IQueryable<TItem> FilterList<TItem>(IQueryable<TItem> source)
public IQueryable<TItem> FilterList<TItem>(IQueryable<TItem> source)
{
if (Filters?.Any() != true)
{
@ -74,12 +76,7 @@ namespace AntDesign.TableModels
{
lambda = Expression.Constant(false, typeof(bool));
}
IFilterExpression filterExpression = null;
if (FilterType == TableFilterType.FieldType)
{
filterExpression = _filterExpressionResolver.GetFilterExpression();
}
foreach (var filter in Filters)
{
if (this.FilterType == TableFilterType.List)
@ -88,17 +85,16 @@ namespace AntDesign.TableModels
}
else // TableFilterType.FieldType
{
if (filter.Value == null && (filter.FilterCompareOperator != TableFilterCompareOperator.IsNull && filter.FilterCompareOperator != TableFilterCompareOperator.IsNotNull)) continue;
Expression constantExpression = null;
if (filter.FilterCompareOperator == TableFilterCompareOperator.IsNull || filter.FilterCompareOperator == TableFilterCompareOperator.IsNotNull)
{
constantExpression = Expression.Constant(null, typeof(TField));
}
else
{
constantExpression = Expression.Constant(filter.Value, typeof(TField));
}
var expression = filterExpression!.GetFilterExpression(filter.FilterCompareOperator, _getFieldExpression.Body, constantExpression);
if (filter.Value == null
&& filter.FilterCompareOperator is not (TableFilterCompareOperator.IsNull or TableFilterCompareOperator.IsNotNull))
continue;
Expression constantExpression = Expression.Constant(
filter.FilterCompareOperator is TableFilterCompareOperator.IsNull
or TableFilterCompareOperator.IsNotNull
? null
: filter.Value);
var expression = _fieldFilterType.GetFilterExpression(filter.FilterCompareOperator, _getFieldExpression.Body, constantExpression);
if (lambda == null)
{
lambda = expression;

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AntDesign.Filters;
namespace AntDesign.TableModels
{

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using AntDesign.Filters;
namespace AntDesign.TableModels
{

View File

@ -27,5 +27,13 @@
<ItemGroup>
<ProjectReference Include="..\..\components\AntDesign.csproj" />
</ItemGroup>
<ItemGroup>
<UpToDateCheckInput Remove="Demos\Components\Table\demo\CustomFieldFilter.razor" />
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="Demos\Components\Table\demo\CustomFieldFilter.razor" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,65 @@
@using Color = System.Drawing.Color
@using System.Drawing
@using System.Linq.Expressions
@using System.Reflection
@using AntDesign.Filters
<Table DataSource="data" TItem="KnownColor">
<PropertyColumn Property="c=>c.ToString()" Title="Name" Sortable Filterable FieldFilterType="simpleStringFiler" />
<PropertyColumn Property="c=>Color.FromKnownColor(c)" Title="Color"
SorterCompare="(x, y) => x.GetHue().CompareTo(y.GetHue())"
FieldFilterType="colorFilterOptions" Filterable>
<CellRender Context="cell">
<span style="background-color: @(ColorTranslator.ToHtml(cell.FieldValue))">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
</CellRender>
</PropertyColumn>
<PropertyColumn Property="c=>Color.FromKnownColor(c).R" Title="Red" Sortable Filterable />
<PropertyColumn Property="c=>Color.FromKnownColor(c).G" Title="Green" Sortable Filterable />
<PropertyColumn Property="c=>Color.FromKnownColor(c).B" Title="Blue" Sortable Filterable />
</Table>
@code {
readonly KnownColor[] data = Enum.GetValues<KnownColor>();
readonly ColorFieldFilterType colorFilterOptions = new();
readonly StringFieldFilterType simpleStringFiler = new() { SupportedCompareOperators = new[] { TableFilterCompareOperator.Contains } };
class ColorFieldFilterType : BaseFieldFilterType
{
private static readonly MethodInfo colorGetBrightness = typeof(Color).GetMethod(nameof(Color.GetBrightness), Array.Empty<Type>());
public override RenderFragment<TableFilterInputRenderOptions> FilterInput { get; }
public override TableFilterCompareOperator DefaultCompareOperator => TableFilterCompareOperator.GreaterThan;
private static IEnumerable<TableFilterCompareOperator> _supportedCompareOperators = new[]
{
TableFilterCompareOperator.Equals,
TableFilterCompareOperator.NotEquals,
TableFilterCompareOperator.GreaterThan,
TableFilterCompareOperator.LessThan,
TableFilterCompareOperator.GreaterThanOrEquals,
TableFilterCompareOperator.LessThanOrEquals
};
public ColorFieldFilterType()
{
FilterInput = GetFilterInput;
SupportedCompareOperators = _supportedCompareOperators;
}
public RenderFragment GetFilterInput(TableFilterInputRenderOptions filter)
{
return @<Slider TValue="double" Value="(float?)filter.Value ?? 0" ValueChanged="value => filter.Value = (float?)value" Style="width: 70px" Min="0" Max="1" Step=".01" />;
}
public override Expression GetFilterExpression(TableFilterCompareOperator compareOperator, Expression leftExpr, Expression rightExpr)
{
// Compare the brightness of the color to the selected value
leftExpr = Expression.Call(leftExpr, colorGetBrightness);
return base.GetFilterExpression(compareOperator, leftExpr, rightExpr);
}
}
}

View File

@ -0,0 +1,26 @@
---
order: 6.55
title:
zh-CN: 自定义筛选器
en-US: Custom field filter
---
## zh-CN
当列类型不是内置筛选器支持的类型时,或者您想修改筛选器的比较操作,可以使用自定义筛选器 `FieldFilterType`
在本示例中Color 列就是用自定义筛选器,可以按"亮度"筛选 或按HUE值排序
自定义筛选器需要实现 `IFieldFilterType`,或者继承 `BaseFieldFilterType``DateFieldFilterType` 来按需重写相关方法。
另外,还可以通过设置 `FieldFilterTypeResolver` 属性为整个表格组件配置自定义筛选器,或者可以通过在依赖注入服务中注册不同的 `IFilterTypeResolver` 实现来为整个应用程序中的表格组件设置自定义筛选器(`DefaultFieldFilterTypeResolver` 是默认实现。)。
## en-US
For types that are not supported by the built-in filters, or when you want different semantics like customizing the default operator, you can specify a custom `FieldFilterType` on the column.
In this example, the Color column can be filtered by the colors brightness (or sorted by its hue).
The custom Filter Types should implement `IFieldFilterType`, but may use the provided `BaseFieldFilterType` or `DateFieldFilterType` as a base implementation.
Your custom filter types can also be applied for a whole table, by setting its `FieldFilterTypeResolver` property, or globally by registering a different `IFilterTypeResolver` service. (See `DefaultFieldFilterTypeResolver` as a reference/fallback implementation.)

View File

@ -1,4 +1,4 @@
---
---
order: 6.6
title:
en-US: Custom Filter Dropdown

View File

@ -1,4 +1,4 @@
---
---
category: Components
cols: 1
type: Data Display
@ -61,6 +61,7 @@ Specify `dataSource` of Table as an array of data, the `OnChange` event and its
| OnRowClick | Row click event (deprecated in antd v3) | EventCallback<RowData<TItem>> | - |
| HidePagination| To hide the pager, PageSize would equals the number of rows in the data source | bool | false |
| Resizable | Enable resizable column | bool | false |
| FieldFilterTypeResolver | Used to resolve filter types for columns | `IFilterTypeResolver` | Injected |
### Column
@ -84,6 +85,7 @@ The Column definition of the previous version, For .NET 6 and above, `PropertyCo
| FilterMultiple | Specify filter multiple selection and single selection | bool | true |
| OnFilter | Filter current data | Expression<Func<TData, TData, bool>> | - |
| FilterDropdown | Custom Filter Dropdown Template | RenderFragment | - |
| FieldFilterType | Specifies what filter options to display and how to filter the data | `IFieldFilterType` | Resolved using Table's `FieldFilterTypeResolver` |
### PropertyColumn

View File

@ -1,4 +1,4 @@
---
---
category: Components
cols: 1
type: 数据展示
@ -63,6 +63,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/f-SbcX2Lx/Table.svg
| OnRowClick | 行点击事件(于antd v3中已废弃) | EventCallback<RowData<TItem>> | - |
| HidePagination| 隐藏分页器PageSize 等于数据源的行数 | bool | false |
| Resizable | 启用可伸缩列 | bool | false |
| FieldFilterTypeResolver | 用于解析列的筛选器类型 | `IFilterTypeResolver` | 默认由全局注入 |
### Column
@ -85,6 +86,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/f-SbcX2Lx/Table.svg
| Filters | 指定需要筛选菜单的列 | IEnumerable<TableFilter<TData>> | - |
| FilterMultiple | 指定筛选器多选和单选 | bool | true |
| FilterDropdown | 自定义列筛选器模板 | RenderFragment | - |
| FieldFilterType | 筛选器配置 ,可用于自定义额外的筛选器 | `IFieldFilterType` | 由 `FieldFilterTypeResolver` 根据类型解析内置筛选器 |
| OnFilter | 筛选当前数据 | Expression<Func<TData, TData, bool>> | - |