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))
{
@(Formatter<TData>.Format(GetValue != null ? GetValue(RowData) : Field, Format))
@(Formatter<TData>.Format(Field, Format))
}
else
{
@(GetValue != null ? GetValue(RowData) : Field)
@Field
}
}
</td>

View File

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

View File

@ -27,9 +27,9 @@ namespace AntDesign.Internal
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;
ITableSortModel sortModel = null;
LambdaExpression getFieldExpression = null;
var properties = dataIndex?.Split(".");
if (properties is {Length: > 0})
{
@ -40,21 +40,17 @@ namespace AntDesign.Internal
var rowData1Exp = Expression.TypeAs(rowDataExp, rowData1Type);
var dataMemberExp = Expression.Property(rowData1Exp, nameof(RowData<object>.Data));
Expression memberExp = canBeNull
? dataMemberExp.AccessNullableProperty(properties)
: dataMemberExp.AccessProperty(properties);
var memberExp = canBeNull
? dataMemberExp.AccessNullableProperty(properties)
: dataMemberExp.AccessProperty(properties);
getValue = Expression.Lambda<Func<RowData, TProp>>(memberExp, rowDataExp).Compile();
if (sortable)
{
var propertySelector = canBeNull
? itemType.BuildAccessNullablePropertyLambdaExpression(properties)
: itemType.BuildAccessPropertyLambdaExpression(properties);
sortModel = new DataIndexSortModel<TProp>(dataIndex, propertySelector, sorterMultiple, sort, sorterCompare);
}
getFieldExpression = canBeNull
? itemType.BuildAccessNullablePropertyLambdaExpression(properties)
: itemType.BuildAccessPropertyLambdaExpression(properties);
}
return new ColumnCacheItem(getValue, sortModel);
return new ColumnCacheItem(getValue, getFieldExpression);
}
internal readonly struct ColumnCacheKey
@ -65,39 +61,23 @@ namespace AntDesign.Internal
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)
{
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;
PropType = propType;
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;
propType = PropType;
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 ITableSortModel SortModel;
internal readonly LambdaExpression GetFieldExpression;
internal ColumnCacheItem(Func<RowData, TProp> getValue, ITableSortModel sortModel)
internal ColumnCacheItem(Func<RowData, TProp> getValue, LambdaExpression getFieldExpression)
{
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;
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"
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>
@table.ChildContent(data)
</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.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace AntDesign.TableModels
{
@ -16,12 +15,12 @@ namespace AntDesign.TableModels
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.FieldName = _propertyInfo.Name;
this.GetFieldExpression = getFieldExpression;
this.FieldName = GetFieldExpression.ReturnType.Name;
this.OnFilter = onFilter;
this.SelectedValues = filters.Select(x => x.Value.ToString());
this.Filters = filters;
@ -34,14 +33,13 @@ namespace AntDesign.TableModels
return source;
}
var sourceExpression = Expression.Parameter(typeof(TItem));
var propertyExpression = Expression.Property(sourceExpression, _propertyInfo);
var sourceExpression = GetFieldExpression.Parameters[0];
Expression invocationExpression = Expression.Invoke((Expression<Func<bool>>)(() => false));
Expression invocationExpression = Expression.Constant(false, typeof(bool));
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);

View File

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

View File

@ -87,8 +87,6 @@
public async Task OnChange(QueryModel<WeatherForecast> queryModel)
{
forecasts = await GetForecastAsync(queryModel.PageIndex, queryModel.PageSize);
_total = 50;
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 Title="DayOfWeek" @bind-Field="@context.DayOfWeek" Sortable/>
<Column Title="DayOfWeek DataIndex" TData="int" DataIndex="DayOfWeek" Sortable SorterCompare="@((v1, v2) => v1 - v2)"/>
<Column Title="N1.N12.N2A DataIndex" TData="string" DataIndex=@("N1.N12[\"test\"].N2A") Sortable SorterCompare="@((v1, v2) => string.CompareOrdinal(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/>
<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>
</Table>
@code {
ITable table;
TestData[] testData;
public class TestData
@ -40,6 +40,17 @@
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"};
Func<DateTime?, DateTime?, int> _dateTimeCompare = (t1, t2) =>
@ -63,7 +74,7 @@
public TestData[] GetForecastAsync()
{
var rng = new Random();
var rng = new Random(42);
var startDate = new DateTime(DateTime.Today.Year - 5, 1, 1);
var range = (DateTime.Today - startDate).TotalSeconds;
return Enumerable.Range(0, 5).Select(index =>