fix(module: table): fix DataIndex Column with incorrect sorting and filter (#1295)

* refactor(module: table): Unify the behavior of Field and DataIndex

* fix(module: table): Fix DataIndex Column doesn't refresh

* doc(module: table): add DataIndex column filter demo

* doc(module: table): fix Blazor table

Co-authored-by: James Yeung <shunjiey@hotmail.com>
This commit is contained in:
Zonciu Liang 2021-04-02 23:54:56 +08:00 committed by GitHub
parent 527f39456b
commit 3bf616b735
10 changed files with 135 additions and 147 deletions

View File

@ -102,11 +102,11 @@ else if (IsBody && RowSpan != 0 && ColSpan != 0)
{ {
if (!string.IsNullOrWhiteSpace(Format)) if (!string.IsNullOrWhiteSpace(Format))
{ {
@(Formatter<TData>.Format(GetValue != null ? GetValue(RowData) : Field, Format)) @(Formatter<TData>.Format(Field, Format))
} }
else else
{ {
@(GetValue != null ? GetValue(RowData) : Field) @Field
} }
} }
</td> </td>

View File

@ -2,7 +2,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection;
using AntDesign.Core.Reflection; using AntDesign.Core.Reflection;
using AntDesign.Internal; using AntDesign.Internal;
using AntDesign.TableModels; using AntDesign.TableModels;
@ -25,7 +27,22 @@ namespace AntDesign
public RenderFragment<TData> CellRender { get; set; } public RenderFragment<TData> CellRender { get; set; }
[Parameter] [Parameter]
public TData Field { get; set; } public TData Field
{
get
{
return GetValue != null ? GetValue(RowData) : _field;
}
set
{
if (GetValue == null)
{
_field = value;
}
}
}
private TData _field;
[Parameter] [Parameter]
public string DataIndex { get; set; } public string DataIndex { get; set; }
@ -75,11 +92,9 @@ namespace AntDesign
[Parameter] [Parameter]
public Expression<Func<TData, TData, bool>> OnFilter { get; set; } public Expression<Func<TData, TData, bool>> OnFilter { get; set; }
private PropertyReflector? _propertyReflector; public string DisplayName { get; private set; }
public string DisplayName => _propertyReflector?.DisplayName; public string FieldName { get; private set; }
public string FieldName => _propertyReflector?.PropertyName;
public ITableSortModel SortModel { get; private set; } public ITableSortModel SortModel { get; private set; }
@ -89,12 +104,16 @@ namespace AntDesign
public Func<RowData, TData> GetValue { get; private set; } public Func<RowData, TData> GetValue { get; private set; }
public LambdaExpression GetFieldExpression { get; private set; }
void IFieldColumn.ClearSorter() => SetSorter(SortDirection.None); void IFieldColumn.ClearSorter() => SetSorter(SortDirection.None);
private static readonly EventCallbackFactory _callbackFactory = new EventCallbackFactory(); private static readonly EventCallbackFactory _callbackFactory = new EventCallbackFactory();
private bool _filterOpened; private bool _filterOpened;
private bool _hasFilterSelected; private bool _hasFilterSelected;
private string[] _selectedFilterValues; private string[] _selectedFilterValues;
private ElementReference _filterTriggerRef; private ElementReference _filterTriggerRef;
@ -109,19 +128,26 @@ namespace AntDesign
{ {
if (FieldExpression != null) if (FieldExpression != null)
{ {
_propertyReflector = PropertyReflector.Create(FieldExpression); var paramExp = Expression.Parameter(ItemType);
var member = ColumnExpressionHelper.GetReturnMemberInfo(FieldExpression);
var bodyExp = Expression.MakeMemberAccess(paramExp, member);
GetFieldExpression = Expression.Lambda(bodyExp, paramExp);
}
else if (DataIndex != null)
{
(_, GetFieldExpression) = ColumnDataIndexHelper<TData>.GetDataIndexConfig(this);
} }
if (Sortable) if (Sortable && GetFieldExpression != null)
{ {
if (_propertyReflector.HasValue) SortModel = new SortModel<TData>(GetFieldExpression, SorterMultiple, DefaultSortOrder, SorterCompare);
{ }
SortModel = new SortModel<TData>(_propertyReflector.Value.PropertyInfo, SorterMultiple, DefaultSortOrder, SorterCompare);
} if (GetFieldExpression != null)
else {
{ var member = ColumnExpressionHelper.GetReturnMemberInfo(GetFieldExpression);
(GetValue, SortModel) = ColumnDataIndexHelper<TData>.GetDataIndexConfig(this); DisplayName = member.GetCustomAttribute<DisplayNameAttribute>(true)?.DisplayName ?? member.Name;
} FieldName = member.Name;
} }
} }
else if (IsBody) else if (IsBody)
@ -137,8 +163,8 @@ namespace AntDesign
_sortDirection = SortModel?.SortDirection ?? DefaultSortOrder ?? SortDirection.None; _sortDirection = SortModel?.SortDirection ?? DefaultSortOrder ?? SortDirection.None;
ClassMapper ClassMapper
.If("ant-table-column-has-sorters", () => Sortable) .If("ant-table-column-has-sorters", () => Sortable)
.If($"ant-table-column-sort", () => Sortable && SortModel != null && SortModel.SortDirection.IsIn(SortDirection.Ascending, SortDirection.Descending)); .If($"ant-table-column-sort", () => Sortable && SortModel != null && SortModel.SortDirection.IsIn(SortDirection.Ascending, SortDirection.Descending));
} }
private void HandleSort() private void HandleSort()
@ -187,6 +213,7 @@ namespace AntDesign
{ {
_sortDirection = sortDirection; _sortDirection = sortDirection;
SortModel.SetSortDirection(sortDirection); SortModel.SetSortDirection(sortDirection);
StateHasChanged();
} }
private void FilterSelected(TableFilter<TData> filter) private void FilterSelected(TableFilter<TData> filter)
@ -210,7 +237,7 @@ namespace AntDesign
_filterOpened = false; _filterOpened = false;
_hasFilterSelected = Filters?.Any(x => x.Selected) == true; _hasFilterSelected = Filters?.Any(x => x.Selected) == true;
FilterModel = _hasFilterSelected ? new FilterModel<TData>(_propertyReflector.Value.PropertyInfo, OnFilter, Filters.Where(x => x.Selected).ToList()) : null; FilterModel = _hasFilterSelected ? new FilterModel<TData>(GetFieldExpression, OnFilter, Filters.Where(x => x.Selected).ToList()) : null;
Table?.ReloadAndInvokeChange(); Table?.ReloadAndInvokeChange();
} }

View File

@ -27,9 +27,9 @@ namespace AntDesign.Internal
private static ColumnCacheItem CreateDataIndexConfig(ColumnCacheKey key) private static ColumnCacheItem CreateDataIndexConfig(ColumnCacheKey key)
{ {
var (itemType, propType, dataIndex, sortable, sort, sorterMultiple, sorterCompare) = key; var (itemType, propType, dataIndex) = key;
Func<RowData, TProp> getValue = null; Func<RowData, TProp> getValue = null;
ITableSortModel sortModel = null; LambdaExpression getFieldExpression = null;
var properties = dataIndex?.Split("."); var properties = dataIndex?.Split(".");
if (properties is {Length: > 0}) if (properties is {Length: > 0})
{ {
@ -40,21 +40,17 @@ namespace AntDesign.Internal
var rowData1Exp = Expression.TypeAs(rowDataExp, rowData1Type); var rowData1Exp = Expression.TypeAs(rowDataExp, rowData1Type);
var dataMemberExp = Expression.Property(rowData1Exp, nameof(RowData<object>.Data)); var dataMemberExp = Expression.Property(rowData1Exp, nameof(RowData<object>.Data));
Expression memberExp = canBeNull var memberExp = canBeNull
? dataMemberExp.AccessNullableProperty(properties) ? dataMemberExp.AccessNullableProperty(properties)
: dataMemberExp.AccessProperty(properties); : dataMemberExp.AccessProperty(properties);
getValue = Expression.Lambda<Func<RowData, TProp>>(memberExp, rowDataExp).Compile(); getValue = Expression.Lambda<Func<RowData, TProp>>(memberExp, rowDataExp).Compile();
if (sortable) getFieldExpression = canBeNull
{ ? itemType.BuildAccessNullablePropertyLambdaExpression(properties)
var propertySelector = canBeNull : itemType.BuildAccessPropertyLambdaExpression(properties);
? itemType.BuildAccessNullablePropertyLambdaExpression(properties)
: itemType.BuildAccessPropertyLambdaExpression(properties);
sortModel = new DataIndexSortModel<TProp>(dataIndex, propertySelector, sorterMultiple, sort, sorterCompare);
}
} }
return new ColumnCacheItem(getValue, sortModel); return new ColumnCacheItem(getValue, getFieldExpression);
} }
internal readonly struct ColumnCacheKey internal readonly struct ColumnCacheKey
@ -65,39 +61,23 @@ namespace AntDesign.Internal
internal readonly string DataIndex; internal readonly string DataIndex;
internal readonly bool Sortable;
internal readonly SortDirection DefaultSortOrder;
internal readonly int SorterMultiple;
internal readonly Func<TProp, TProp, int> SorterCompare;
internal static ColumnCacheKey Create(Column<TProp> column) internal static ColumnCacheKey Create(Column<TProp> column)
{ {
return new(column.ItemType, typeof(TProp), column.DataIndex, column.Sortable, column.DefaultSortOrder, column.SorterMultiple, column.SorterCompare); return new(column.ItemType, typeof(TProp), column.DataIndex);
} }
internal ColumnCacheKey(Type itemType, Type propType, string dataIndex, bool sortable, SortDirection defaultSortOrder, int sorterMultiple, Func<TProp, TProp, int> sorterCompare) internal ColumnCacheKey(Type itemType, Type propType, string dataIndex)
{ {
ItemType = itemType; ItemType = itemType;
PropType = propType; PropType = propType;
DataIndex = dataIndex; DataIndex = dataIndex;
Sortable = sortable;
DefaultSortOrder = defaultSortOrder;
SorterMultiple = sorterMultiple;
SorterCompare = sorterCompare;
} }
internal void Deconstruct(out Type itemType, out Type propType, out string dataIndex, out bool sortable, out SortDirection defaultSortOrder, out int sorterMultiple, out Func<TProp, TProp, int> sorterCompare) internal void Deconstruct(out Type itemType, out Type propType, out string dataIndex)
{ {
itemType = ItemType; itemType = ItemType;
propType = PropType; propType = PropType;
dataIndex = DataIndex; dataIndex = DataIndex;
sortable = Sortable;
defaultSortOrder = DefaultSortOrder;
sorterMultiple = SorterMultiple;
sorterCompare = SorterCompare;
} }
} }
@ -105,18 +85,18 @@ namespace AntDesign.Internal
{ {
internal readonly Func<RowData, TProp> GetValue; internal readonly Func<RowData, TProp> GetValue;
internal readonly ITableSortModel SortModel; internal readonly LambdaExpression GetFieldExpression;
internal ColumnCacheItem(Func<RowData, TProp> getValue, ITableSortModel sortModel) internal ColumnCacheItem(Func<RowData, TProp> getValue, LambdaExpression getFieldExpression)
{ {
GetValue = getValue; GetValue = getValue;
SortModel = sortModel; GetFieldExpression = getFieldExpression;
} }
internal void Deconstruct(out Func<RowData, TProp> getValue, out ITableSortModel sortModel) internal void Deconstruct(out Func<RowData, TProp> getValue, out LambdaExpression getFieldExpression)
{ {
getValue = GetValue; getValue = GetValue;
sortModel = SortModel; getFieldExpression = GetFieldExpression;
} }
} }
} }

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.Linq.Expressions;
using System.Reflection;
namespace AntDesign.Internal
{
internal static class ColumnExpressionHelper
{
internal static MemberInfo GetReturnMemberInfo(LambdaExpression expression)
{
var accessorBody = expression.Body;
if (accessorBody is UnaryExpression unaryExpression
&& unaryExpression.NodeType == ExpressionType.Convert
&& unaryExpression.Type == typeof(object))
{
accessorBody = unaryExpression.Operand;
}
if (accessorBody is ConditionalExpression conditionalExpression)
{
accessorBody = conditionalExpression.IfTrue;
}
if (!(accessorBody is MemberExpression memberExpression))
{
throw new ArgumentException($"The type of the provided expression {accessorBody.GetType().Name} is not supported, it should be {nameof(MemberExpression)}.");
}
return memberExpression.Member;
}
}
}

View File

@ -182,7 +182,7 @@ RenderFragment<(Table<TItem> table, IEnumerable<TItem> showItems, int level)> bo
<tr @attributes="rowAttributes" <tr @attributes="rowAttributes"
data-row-key="@(rowIndex-1)" class="ant-table-row ant-table-row-level-@level @(currentRowData.Selected ? "ant-table-row-selected" : "") @table.RowClassName(currentRowData) @rowAttributes?.GetValueOrDefault("class")"> data-row-key="@(rowIndex-1)" class="ant-table-row ant-table-row-level-@level @(currentRowData.Selected ? "ant-table-row-selected" : "") @table.RowClassName(currentRowData) @rowAttributes?.GetValueOrDefault("class")">
<CascadingValue Value="currentRowData" Name="RowData" IsFixed> <CascadingValue Value="currentRowData" Name="RowData" IsFixed="false">
<CascadingValue Value="true" Name="IsBody" IsFixed> <CascadingValue Value="true" Name="IsBody" IsFixed>
@table.ChildContent(data) @table.ChildContent(data)
</CascadingValue> </CascadingValue>

View File

@ -1,63 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace AntDesign.TableModels
{
public class DataIndexSortModel<TField> : ITableSortModel, IComparer<TField>
{
private readonly LambdaExpression _propertySelect;
private readonly Func<TField, TField, int> _comparer;
public int Priority { get; }
public string FieldName { get; }
public string Sort => _sortDirection?.Name;
SortDirection ITableSortModel.SortDirection => _sortDirection;
private SortDirection _sortDirection;
public DataIndexSortModel(string dataIndex, LambdaExpression propertySelect, int priority, SortDirection sortDirection, Func<TField, TField, int> comparer)
{
this.FieldName = dataIndex;
this._propertySelect = propertySelect;
this._comparer = comparer;
this.Priority = priority;
this._sortDirection = sortDirection ?? SortDirection.None;
}
void ITableSortModel.SetSortDirection(SortDirection sortDirection)
{
_sortDirection = sortDirection;
}
IOrderedQueryable<TItem> ITableSortModel.SortList<TItem>(IQueryable<TItem> source)
{
if (_sortDirection == SortDirection.None)
{
return source as IOrderedQueryable<TItem>;
}
var lambda = (Expression<Func<TItem, TField>>)_propertySelect;
if (_sortDirection == SortDirection.Ascending)
{
return _comparer == null ? source.OrderBy(lambda) : source.OrderBy(lambda, this);
}
else
{
return _comparer == null ? source.OrderByDescending(lambda) : source.OrderByDescending(lambda, this);
}
}
/// <inheritdoc />
public int Compare(TField x, TField y)
{
return _comparer?.Invoke(x, y) ?? 0;
}
}
}

View File

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection;
namespace AntDesign.TableModels namespace AntDesign.TableModels
{ {
@ -16,12 +15,12 @@ namespace AntDesign.TableModels
public Expression<Func<TField, TField, bool>> OnFilter { get; set; } public Expression<Func<TField, TField, bool>> OnFilter { get; set; }
private PropertyInfo _propertyInfo; public LambdaExpression GetFieldExpression { get; set; }
public FilterModel(PropertyInfo propertyInfo, Expression<Func<TField, TField, bool>> onFilter, IList<TableFilter<TField>> filters) public FilterModel(LambdaExpression getFieldExpression, Expression<Func<TField, TField, bool>> onFilter, IList<TableFilter<TField>> filters)
{ {
this._propertyInfo = propertyInfo; this.GetFieldExpression = getFieldExpression;
this.FieldName = _propertyInfo.Name; this.FieldName = GetFieldExpression.ReturnType.Name;
this.OnFilter = onFilter; this.OnFilter = onFilter;
this.SelectedValues = filters.Select(x => x.Value.ToString()); this.SelectedValues = filters.Select(x => x.Value.ToString());
this.Filters = filters; this.Filters = filters;
@ -34,14 +33,13 @@ namespace AntDesign.TableModels
return source; return source;
} }
var sourceExpression = Expression.Parameter(typeof(TItem)); var sourceExpression = GetFieldExpression.Parameters[0];
var propertyExpression = Expression.Property(sourceExpression, _propertyInfo);
Expression invocationExpression = Expression.Invoke((Expression<Func<bool>>)(() => false)); Expression invocationExpression = Expression.Constant(false, typeof(bool));
foreach (var filter in Filters) foreach (var filter in Filters)
{ {
invocationExpression = Expression.OrElse(invocationExpression, Expression.Invoke(OnFilter, Expression.Constant(filter.Value), propertyExpression)); invocationExpression = Expression.OrElse(invocationExpression, Expression.Invoke(OnFilter, Expression.Constant(filter.Value), GetFieldExpression.Body));
} }
var lambda = Expression.Lambda<Func<TItem, bool>>(invocationExpression, sourceExpression); var lambda = Expression.Lambda<Func<TItem, bool>>(invocationExpression, sourceExpression);

View File

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using AntDesign.Internal;
namespace AntDesign.TableModels namespace AntDesign.TableModels
{ {
@ -16,16 +18,18 @@ namespace AntDesign.TableModels
SortDirection ITableSortModel.SortDirection => _sortDirection; SortDirection ITableSortModel.SortDirection => _sortDirection;
private readonly PropertyInfo _propertyInfo;
private readonly Func<TField, TField, int> _comparer; private readonly Func<TField, TField, int> _comparer;
private SortDirection _sortDirection; private SortDirection _sortDirection;
public SortModel(PropertyInfo propertyInfo, int priority, SortDirection defaultSortOrder, Func<TField, TField, int> comparer) private LambdaExpression _getFieldExpression;
public SortModel(LambdaExpression getFieldExpression, int priority, SortDirection defaultSortOrder, Func<TField, TField, int> comparer)
{ {
this.Priority = priority; this.Priority = priority;
this.FieldName = propertyInfo?.Name; this._getFieldExpression = getFieldExpression;
this._propertyInfo = propertyInfo; var member = ColumnExpressionHelper.GetReturnMemberInfo(_getFieldExpression);
this.FieldName = member.GetCustomAttribute<DisplayAttribute>(true)?.Name ?? member.Name;
this._comparer = comparer; this._comparer = comparer;
this._sortDirection = defaultSortOrder ?? SortDirection.None; this._sortDirection = defaultSortOrder ?? SortDirection.None;
} }
@ -42,11 +46,7 @@ namespace AntDesign.TableModels
return source as IOrderedQueryable<TItem>; return source as IOrderedQueryable<TItem>;
} }
var sourceExpression = Expression.Parameter(typeof(TItem)); var lambda = (Expression<Func<TItem, TField>>)_getFieldExpression;
var propertyExpression = Expression.Property(sourceExpression, _propertyInfo);
var lambda = Expression.Lambda<Func<TItem, TField>>(propertyExpression, sourceExpression);
if (_sortDirection == SortDirection.Ascending) if (_sortDirection == SortDirection.Ascending)
{ {

View File

@ -87,8 +87,6 @@
public async Task OnChange(QueryModel<WeatherForecast> queryModel) public async Task OnChange(QueryModel<WeatherForecast> queryModel)
{ {
forecasts = await GetForecastAsync(queryModel.PageIndex, queryModel.PageSize);
_total = 50;
Console.WriteLine(JsonSerializer.Serialize(queryModel)); Console.WriteLine(JsonSerializer.Serialize(queryModel));
} }

View File

@ -1,16 +1,16 @@
<Table @ref=" table" TItem="TestData" DataSource="@testData"> @using AntDesign.TableModels
<Table TItem="TestData" DataSource="@testData">
<Column @bind-Field="@context.Id" Sortable/> <Column @bind-Field="@context.Id" Sortable/>
<Column Title="DayOfWeek" @bind-Field="@context.DayOfWeek" Sortable/> <Column Title="DayOfWeek" @bind-Field="@context.DayOfWeek" Sortable/>
<Column Title="DayOfWeek DataIndex" TData="int" DataIndex="DayOfWeek" Sortable SorterCompare="@((v1, v2) => v1 - v2)"/> <Column Title="DayOfWeek DataIndex" TData="int" DataIndex="DayOfWeek" Sortable SorterCompare="@((v1, v2) => v1 - v2)" Filters="DayOfWeekFilter" OnFilter="(a, b) => a == b"/>
<Column Title="N1.N12.N2A DataIndex" TData="string" DataIndex=@("N1.N12[\"test\"].N2A") Sortable SorterCompare="@((v1, v2) => string.CompareOrdinal(v1, v2))"/> <Column Title="N1.N12.N2A DataIndex" TData="string" DataIndex=@("N1.N12[\"test\"].N2A") Sortable/>
<Column Title="N1.N1A DataIndex Nullable" TData="int?" DataIndex="N1.N1A" Sortable SorterCompare="CompareNullableInt"/> <Column Title="N1.N1A DataIndex Nullable" TData="int?" DataIndex="N1.N1A" Sortable SorterCompare="CompareNullableInt"/>
<Column Title="Time" TData="DateTime?" DataIndex=@(@"N1.N12[""test""].Time") Format="yyyy-MM-dd hh:mm:ss" Sortable SorterCompare="_dateTimeCompare"></Column> <Column Title="Time" TData="DateTime?" DataIndex=@(@"N1.N12[""test""].Time") Format="yyyy-MM-dd hh:mm:ss" Sortable SorterCompare="_dateTimeCompare"></Column>
</Table> </Table>
@code { @code {
ITable table;
TestData[] testData; TestData[] testData;
public class TestData public class TestData
@ -40,6 +40,17 @@
public DateTime? Time { get; set; } public DateTime? Time { get; set; }
} }
public TableFilter<int>[] DayOfWeekFilter =
{
new() {Text = "Monday", Value = 1},
new() {Text = "Tuesday", Value = 2},
new() {Text = "Wednesdays", Value = 3},
new() {Text = "Thursday", Value = 4},
new() {Text = "Friday", Value = 5},
new() {Text = "Saturday", Value = 6},
new() {Text = "Sunday", Value = 7},
};
public string[] DayName = {"None", "Monday", "Tuesday", "Wednesdays", "Thursday", "Friday", "Saturday", "Sunday"}; public string[] DayName = {"None", "Monday", "Tuesday", "Wednesdays", "Thursday", "Friday", "Saturday", "Sunday"};
Func<DateTime?, DateTime?, int> _dateTimeCompare = (t1, t2) => Func<DateTime?, DateTime?, int> _dateTimeCompare = (t1, t2) =>
@ -63,7 +74,7 @@
public TestData[] GetForecastAsync() public TestData[] GetForecastAsync()
{ {
var rng = new Random(); var rng = new Random(42);
var startDate = new DateTime(DateTime.Today.Year - 5, 1, 1); var startDate = new DateTime(DateTime.Today.Year - 5, 1, 1);
var range = (DateTime.Today - startDate).TotalSeconds; var range = (DateTime.Today - startDate).TotalSeconds;
return Enumerable.Range(0, 5).Select(index => return Enumerable.Range(0, 5).Select(index =>