mirror of
https://gitee.com/ant-design-blazor/ant-design-blazor.git
synced 2024-12-02 03:57:38 +08:00
fix(module: table): Incorrect deselection in virtualized table (#3282)
* fix(module: table): Correctly deselect all rows in radio selection with a virtualized table * Implement internal inteface method correctly * fix(module: table): Fix correctly rerendering selection components, selecting tree data * Add documentation comments * Fix bind-SelectedRows --------- Co-authored-by: Rhodon <rhodonvantilburg@gmail.com> Co-authored-by: James Yeung <shunjiey@hotmail.com>
This commit is contained in:
parent
bb0e043b07
commit
cb88c530a8
@ -57,7 +57,7 @@ else if (!IsHeader && RowSpan != 0 && ColSpan != 0)
|
||||
@if (AppendExpandColumn)
|
||||
{
|
||||
<td class="ant-table-cell ant-table-row-expand-icon-cell">
|
||||
@if (Table.RowExpandable(RowData) && (!Table.TreeMode || !RowData.HasChildren))
|
||||
@if (Table.RowExpandable(RowData) && (!Table.TreeMode || !DataItem.HasChildren))
|
||||
{
|
||||
<button type="button" @onclick="ToggleTreeNode"
|
||||
class="ant-table-row-expand-icon @(RowData.Expanded?"ant-table-row-expand-icon-expanded":"ant-table-row-expand-icon-collapsed")"
|
||||
@ -71,7 +71,7 @@ else if (!IsHeader && RowSpan != 0 && ColSpan != 0)
|
||||
@if (ColIndex == Table.TreeExpandIconColumnIndex && Table.TreeMode)
|
||||
{
|
||||
<span class="ant-table-row-indent indent-level-@RowData.Level" style="padding-left: @((CssSizeLength)(RowData.Level * Table.IndentSize));"></span>
|
||||
@if (RowData.HasChildren)
|
||||
@if (DataItem.HasChildren)
|
||||
{
|
||||
<button type="button" @onclick="ToggleTreeNode" class="ant-table-row-expand-icon @(RowData?.Expanded==true?"ant-table-row-expand-icon-expanded":"ant-table-row-expand-icon-collapsed")" aria-label="@(RowData?.Expanded==true?Table.Locale.Collapse:Table.Locale.Expand)"></button>
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ else if (IsBody && RowSpan != 0 && ColSpan != 0)
|
||||
@if (AppendExpandColumn)
|
||||
{
|
||||
<td class="ant-table-cell ant-table-row-expand-icon-cell">
|
||||
@if (Table.RowExpandable(RowData) && (!Table.TreeMode || !RowData.HasChildren))
|
||||
@if (Table.RowExpandable(RowData) && (!Table.TreeMode || !DataItem.HasChildren))
|
||||
{
|
||||
<button type="button" @onclick="ToggleTreeNode" @onclick:stopPropagation
|
||||
class="ant-table-row-expand-icon @(RowData.Expanded ? "ant-table-row-expand-icon-expanded" : "ant-table-row-expand-icon-collapsed")"
|
||||
@ -85,17 +85,17 @@ else if (IsBody && RowSpan != 0 && ColSpan != 0)
|
||||
}
|
||||
<td class="@ClassMapper.Class" style="@FixedStyle @Style" rowspan="@RowSpan" colspan="@ColSpan" title="@(Ellipsis ? fieldText : false)" data-label="@title" @attributes="cellAttributes">
|
||||
@if (ColIndex == Table.TreeExpandIconColumnIndex && Table.TreeMode)
|
||||
{
|
||||
<span class="ant-table-row-indent indent-level-@RowData.Level" style="padding-left: @((CssSizeLength)(RowData.Level * Table.IndentSize));"></span>
|
||||
@if (RowData.HasChildren)
|
||||
{
|
||||
<button type="button" @onclick="ToggleTreeNode" @onclick:stopPropagation class="ant-table-row-expand-icon @(RowData?.Expanded == true ? "ant-table-row-expand-icon-expanded" : "ant-table-row-expand-icon-collapsed")" aria-label="@(RowData?.Expanded == true ? Table.Locale.Collapse : Table.Locale.Expand)"></button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced" aria-label="@Table.Locale.Expand"></button>
|
||||
}
|
||||
}
|
||||
{
|
||||
<span class="ant-table-row-indent indent-level-@RowData.Level" style="padding-left: @((CssSizeLength)(RowData.Level * Table.IndentSize));"></span>
|
||||
@if (DataItem.HasChildren)
|
||||
{
|
||||
<button type="button" @onclick="ToggleTreeNode" @onclick:stopPropagation class="ant-table-row-expand-icon @(RowData?.Expanded == true ? "ant-table-row-expand-icon-expanded" : "ant-table-row-expand-icon-collapsed")" aria-label="@(RowData?.Expanded == true ? Table.Locale.Collapse : Table.Locale.Expand)"></button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced" aria-label="@Table.Locale.Expand"></button>
|
||||
}
|
||||
}
|
||||
|
||||
@if (IsFiexedEllipsis)
|
||||
{
|
||||
|
@ -32,6 +32,8 @@ namespace AntDesign
|
||||
[CascadingParameter(Name = "RowData")]
|
||||
public RowData RowData { get; set; }
|
||||
|
||||
protected TableDataItem DataItem => RowData.TableDataItem;
|
||||
|
||||
[CascadingParameter(Name = "IsMeasure")]
|
||||
public bool IsMeasure { get; set; }
|
||||
|
||||
|
@ -15,7 +15,7 @@ namespace AntDesign
|
||||
|
||||
QueryModel GetQueryModel();
|
||||
|
||||
void SetSelection(string[] keys);
|
||||
void SetSelection(ICollection<string> keys);
|
||||
|
||||
void SelectAll();
|
||||
|
||||
@ -43,6 +43,8 @@ namespace AntDesign
|
||||
|
||||
internal SortDirection[] SortDirections { get; }
|
||||
|
||||
internal void SetSelection(ISelectionColumn selectItem);
|
||||
|
||||
internal bool AllSelected { get; }
|
||||
|
||||
internal bool AnySelected { get; }
|
||||
|
@ -29,7 +29,7 @@ namespace AntDesign
|
||||
else if (IsBody)
|
||||
{
|
||||
var compliedProperty = Property.Compile();
|
||||
GetValue = rowData => compliedProperty.Invoke(((RowData<TItem>)rowData).Data);
|
||||
GetValue = rowData => compliedProperty.Invoke(((RowData<TItem>)rowData).DataItem.Data);
|
||||
}
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
@ -22,8 +22,8 @@ namespace AntDesign
|
||||
//private bool _checked;
|
||||
|
||||
private bool Indeterminate => IsHeader
|
||||
&& !Table.AllSelected
|
||||
&& Table.AnySelected;
|
||||
&& Table.AnySelected
|
||||
&& !Table.AllSelected;
|
||||
|
||||
public IList<ISelectionColumn> RowSelections { get; set; } = new List<ISelectionColumn>();
|
||||
|
||||
@ -50,12 +50,11 @@ namespace AntDesign
|
||||
{
|
||||
if (Type == "radio")
|
||||
{
|
||||
Table.SetSelection(new[] { Key });
|
||||
Table.SetSelection(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
RowData.Selected = selected;
|
||||
Table.Selection.StateHasChanged();
|
||||
DataItem.Selected = selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -87,10 +86,10 @@ namespace AntDesign
|
||||
// avoid check the disabled one but allow default checked
|
||||
if (Disabled && _selected.HasValue)
|
||||
{
|
||||
RowData.SetSelected(_selected.Value);
|
||||
DataItem.SetSelected(_selected.Value);
|
||||
}
|
||||
|
||||
_selected = RowData.Selected;
|
||||
_selected = DataItem.Selected;
|
||||
}
|
||||
|
||||
void ISelectionColumn.StateHasChanged()
|
||||
|
@ -56,7 +56,7 @@
|
||||
@ChildContent(_fieldModel)
|
||||
</CascadingValue>
|
||||
</tr>
|
||||
@body(this, _showItems, 0, _dataSourceCache)
|
||||
@body(this, _showItems, 0, _rootRowDataCache)
|
||||
</tbody>
|
||||
@tfoot(this)
|
||||
</table>
|
||||
@ -74,7 +74,7 @@
|
||||
@ChildContent(_fieldModel)
|
||||
</CascadingValue>
|
||||
</tr>
|
||||
@body(this, _showItems, 0, _dataSourceCache)
|
||||
@body(this, _showItems, 0, _rootRowDataCache)
|
||||
</tbody>
|
||||
@tfoot(this)
|
||||
</table>
|
||||
@ -87,7 +87,7 @@
|
||||
@colGroup((this, true))
|
||||
@header(this)
|
||||
<tbody class="ant-table-tbody">
|
||||
@body(this, _showItems, 0, _dataSourceCache)
|
||||
@body(this, _showItems, 0, _rootRowDataCache)
|
||||
</tbody>
|
||||
@tfoot(this)
|
||||
</table>
|
||||
@ -169,7 +169,7 @@ RenderFragment<(Table<TItem> table, bool header)> colGroup = ctx =>
|
||||
</colgroup>
|
||||
</Template>;
|
||||
|
||||
RenderFragment body(Table<TItem> table, IEnumerable<TItem> showItems, int level, Dictionary<TItem, RowData<TItem>> rowDataCache) =>
|
||||
RenderFragment body(Table<TItem> table, IEnumerable<TItem> showItems, int level, IDictionary<TItem, RowData<TItem>> rowDataCache) =>
|
||||
@<Template>
|
||||
|
||||
@if (table._hasInitialized)
|
||||
@ -219,18 +219,18 @@ RenderFragment<(Table<TItem> table, bool header)> colGroup = ctx =>
|
||||
</tr>
|
||||
}
|
||||
#if NET5_0_OR_GREATER
|
||||
else if (table.EnableVirtualization)
|
||||
{
|
||||
<Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize
|
||||
OverscanCount="10"
|
||||
Items="showItems.Select((data, index) => (data, index)).ToList()"
|
||||
ChildContent="table.bodyRow(table, level, rowDataCache)" />
|
||||
else if (table.EnableVirtualization) {
|
||||
<Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize TItem="ValueTuple<TItem, int>"
|
||||
OverscanCount="10"
|
||||
Items="showItems.Select((data, index) => (data, index)).ToList()"
|
||||
ChildContent="table.bodyRow(table, level, rowDataCache)"/>
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
{ @*Build will fail without this block*@ }
|
||||
<ForeachLoop Items="showItems.Select((data, index) => (data, index))"
|
||||
<ForeachLoop TItem="ValueTuple<TItem, int>"
|
||||
Items="showItems.Select((data, index) => (data, index))"
|
||||
ChildContent="table.bodyRow(table, level, rowDataCache)" />
|
||||
}
|
||||
}
|
||||
@ -244,34 +244,27 @@ Func<Table<TItem>, int, IDictionary<TItem, RowData<TItem>>, RenderFragment<(TIte
|
||||
if (level == 0) rowIndex = table.PageSize * (table.PageIndex - 1) + tuple.index + 1;
|
||||
else rowIndex = tuple.index + 1;
|
||||
var data = tuple.data;
|
||||
bool selected = table.SelectedRows.Contains(data);
|
||||
if (!rowDataCache.ContainsKey(data) || rowDataCache[data] == null)
|
||||
if (!table._dataSourceCache.TryGetValue(data, out var currentDataItem) || currentDataItem == null)
|
||||
{
|
||||
var rowData = new RowData<TItem>(rowIndex, table.PageIndex, data)
|
||||
currentDataItem = new TableDataItem<TItem>(data, table);
|
||||
currentDataItem.SetSelected(table.SelectedRows.Contains(data), triggersSelectedChanged: false);
|
||||
table._dataSourceCache.Add(data, currentDataItem);
|
||||
}
|
||||
if (!rowDataCache.TryGetValue(data, out var currentRowData) || currentRowData == null)
|
||||
{
|
||||
currentRowData = new RowData<TItem>(currentDataItem)
|
||||
{
|
||||
Expanded = table.DefaultExpandAllRows && level < table.DefaultExpandMaxLevel
|
||||
};
|
||||
rowDataCache[data] = rowData;
|
||||
}
|
||||
var currentRowData = rowDataCache[data];
|
||||
if (!table._allRowDataCache.ContainsKey(data))
|
||||
{
|
||||
table._allRowDataCache.Add(data, new());
|
||||
}
|
||||
if (!table._allRowDataCache[data].Contains(currentRowData))
|
||||
{
|
||||
table._allRowDataCache[data].Add(currentRowData);
|
||||
rowDataCache.Add(data, currentRowData);
|
||||
}
|
||||
var childrenData = table.TreeChildren(data);
|
||||
var hasChildren = childrenData?.Any() == true;
|
||||
var childrenRowDataCache = currentRowData.Children;
|
||||
currentDataItem.HasChildren = childrenData?.Any() == true;
|
||||
currentRowData.Level = level;
|
||||
currentRowData.HasChildren = hasChildren;
|
||||
currentRowData.RowIndex = rowIndex;
|
||||
currentRowData.PageIndex = table.PageIndex;
|
||||
currentRowData.SetSelected(selected);
|
||||
|
||||
<TableRowWrapper RowData="currentRowData" RowDataSelectedChanged="table.RowDataSelectedChanged">
|
||||
<TableRowWrapper RowData="currentRowData">
|
||||
@{
|
||||
var rowAttributes = table.OnRow?.Invoke(currentRowData);
|
||||
if (table.OnRowClick.HasDelegate)
|
||||
@ -290,19 +283,20 @@ Func<Table<TItem>, int, IDictionary<TItem, RowData<TItem>>, RenderFragment<(TIte
|
||||
}
|
||||
else
|
||||
{
|
||||
<TableRow @key="currentRowData.Data">
|
||||
@table.ChildContent(currentRowData.Data)
|
||||
<TableRow @key="currentDataItem.Data">
|
||||
@table.ChildContent(currentDataItem.Data)
|
||||
</TableRow>
|
||||
}
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
|
||||
@if (currentRowData.HasChildren && currentRowData.Expanded)
|
||||
@if (currentDataItem.HasChildren && currentRowData.Expanded)
|
||||
{
|
||||
@table.body(table, table.SortFilterChildren(childrenData), currentRowData.Level + 1, childrenRowDataCache);
|
||||
currentRowData.Children ??= new Dictionary<TItem, RowData<TItem>>();
|
||||
@table.body(table, table.SortFilterChildren(childrenData), currentRowData.Level + 1, currentRowData.Children);
|
||||
}
|
||||
@if (!currentRowData.HasChildren && table.ExpandTemplate != null && table.RowExpandable(currentRowData) && currentRowData.Expanded)
|
||||
@if (!currentDataItem.HasChildren && table.ExpandTemplate != null && table.RowExpandable(currentRowData) && currentRowData.Expanded)
|
||||
{
|
||||
<tr class="ant-table-expanded-row ant-table-expanded-row-level-1 @table.ExpandedRowClassName(currentRowData)"
|
||||
style="@(currentRowData.Expanded?"":"display: none;")">
|
||||
|
@ -7,13 +7,13 @@ namespace AntDesign
|
||||
{
|
||||
public partial class Table<TItem> : ITable
|
||||
{
|
||||
private Dictionary<TItem, RowData<TItem>> _dataSourceCache = new();
|
||||
private Dictionary<TItem, List<RowData<TItem>>> _allRowDataCache = new();
|
||||
private Dictionary<TItem, TableDataItem<TItem>> _dataSourceCache = new();
|
||||
private Dictionary<TItem, RowData<TItem>> _rootRowDataCache = new();
|
||||
|
||||
private void FlushCache()
|
||||
{
|
||||
_dataSourceCache.Clear();
|
||||
_allRowDataCache.Clear();
|
||||
_rootRowDataCache.Clear();
|
||||
}
|
||||
|
||||
private void FinishLoadPage()
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using AntDesign.TableModels;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@ -24,42 +25,28 @@ namespace AntDesign
|
||||
public EventCallback<IEnumerable<TItem>> SelectedRowsChanged { get; set; }
|
||||
|
||||
private ISelectionColumn _selection;
|
||||
private HashSet<TItem> _selectedRows = new();
|
||||
private readonly HashSet<TItem> _selectedRows = new();
|
||||
private bool _preventRowDataTriggerSelectedRowsChanged;
|
||||
private bool _preventChangeRowDataWithSameData;
|
||||
private bool _preventRowDataSelectedChangedCallback;
|
||||
|
||||
private void RowDataSelectedChanged(RowData<TItem> rowData, bool selected)
|
||||
internal void DataItemSelectedChanged(TableDataItem<TItem> dataItem, bool selected)
|
||||
{
|
||||
if (_preventRowDataSelectedChangedCallback) return;
|
||||
if (!RowSelectable(rowData.Data))
|
||||
if (!RowSelectable(dataItem.Data))
|
||||
{
|
||||
rowData.SetSelected(!selected);
|
||||
dataItem.SetSelected(!selected, triggersSelectedChanged: false);
|
||||
return;
|
||||
}
|
||||
if (selected)
|
||||
{
|
||||
_selectedRows.Add(rowData.Data);
|
||||
_selectedRows.Add(dataItem.Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedRows.Remove(rowData.Data);
|
||||
}
|
||||
if (!_preventChangeRowDataWithSameData)
|
||||
{
|
||||
_preventRowDataSelectedChangedCallback = true;
|
||||
if (_allRowDataCache.ContainsKey(rowData.Data))
|
||||
{
|
||||
foreach (var rowDataWithSameData in _allRowDataCache[rowData.Data])
|
||||
{
|
||||
rowDataWithSameData.Selected = selected;
|
||||
}
|
||||
}
|
||||
_preventRowDataSelectedChangedCallback = false;
|
||||
_selectedRows.Remove(dataItem.Data);
|
||||
}
|
||||
if (!_preventRowDataTriggerSelectedRowsChanged)
|
||||
{
|
||||
SelectionChanged();
|
||||
_selection?.StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,70 +62,129 @@ namespace AntDesign
|
||||
|
||||
public void SelectAll()
|
||||
{
|
||||
_selectedRows = GetAllItemsByTopLevelItems(_showItems, true).ToHashSet();
|
||||
_preventRowDataSelectedChangedCallback = true;
|
||||
_preventRowDataTriggerSelectedRowsChanged = true;
|
||||
_preventChangeRowDataWithSameData = true;
|
||||
foreach (var rowDataList in _allRowDataCache.Values)
|
||||
_selectedRows.Clear();
|
||||
foreach (var selectedRow in GetAllItemsByTopLevelItems(_showItems, true))
|
||||
{
|
||||
foreach (var rowData in rowDataList)
|
||||
{
|
||||
rowData.Selected = true;
|
||||
}
|
||||
_selectedRows.Add(selectedRow);
|
||||
}
|
||||
_preventRowDataSelectedChangedCallback = false;
|
||||
_preventRowDataTriggerSelectedRowsChanged = false;
|
||||
_preventChangeRowDataWithSameData = false;
|
||||
if (_selection != null)
|
||||
foreach (TableDataItem<TItem> dataItem in _dataSourceCache.Values)
|
||||
{
|
||||
_selection.StateHasChanged();
|
||||
dataItem.SetSelected(_selectedRows.Contains(dataItem.Data));
|
||||
}
|
||||
|
||||
_selection?.StateHasChanged();
|
||||
SelectionChanged();
|
||||
}
|
||||
|
||||
private void ClearSelectedRows()
|
||||
{
|
||||
_selectedRows.Clear();
|
||||
foreach (TableDataItem<TItem> dataItem in _dataSourceCache.Values)
|
||||
{
|
||||
dataItem.SetSelected(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnselectAll()
|
||||
{
|
||||
_selectedRows.Clear();
|
||||
_preventRowDataTriggerSelectedRowsChanged = true;
|
||||
_preventChangeRowDataWithSameData = true;
|
||||
foreach (var rowDataList in _allRowDataCache.Values)
|
||||
{
|
||||
foreach (var rowData in rowDataList)
|
||||
{
|
||||
rowData.Selected = false;
|
||||
}
|
||||
}
|
||||
_preventRowDataTriggerSelectedRowsChanged = false;
|
||||
_preventChangeRowDataWithSameData = false;
|
||||
if (_selection != null)
|
||||
{
|
||||
_selection.StateHasChanged();
|
||||
}
|
||||
ClearSelectedRows();
|
||||
|
||||
_selection?.StateHasChanged();
|
||||
SelectionChanged();
|
||||
}
|
||||
|
||||
public void SetSelection(string[] keys)
|
||||
/// <summary>
|
||||
/// Please use <see cref="SetSelection(IEnumerable{TItem})"/> instead if possible,
|
||||
/// as this method won't correctly select items from invisible rows when virtualization is enabled.
|
||||
/// </summary>
|
||||
public void SetSelection(ICollection<string> keys)
|
||||
{
|
||||
if (keys == null || !keys.Any())
|
||||
{
|
||||
UnselectAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_selection == null)
|
||||
{
|
||||
throw new InvalidOperationException("To use SetSelection method for a table, you should add a Selection component to the column definition.");
|
||||
}
|
||||
|
||||
_preventRowDataTriggerSelectedRowsChanged = true;
|
||||
_preventChangeRowDataWithSameData = true;
|
||||
_selection.RowSelections.ForEach(x => x.RowData.Selected = x.Key.IsIn(keys));
|
||||
_preventRowDataTriggerSelectedRowsChanged = false;
|
||||
_preventChangeRowDataWithSameData = false;
|
||||
ClearSelectedRows();
|
||||
if (keys?.Count > 0)
|
||||
{
|
||||
_preventRowDataTriggerSelectedRowsChanged = true;
|
||||
_selection.RowSelections.ForEach(x => x.RowData.Selected = keys.Contains(x.Key));
|
||||
_preventRowDataTriggerSelectedRowsChanged = false;
|
||||
}
|
||||
|
||||
_selection.StateHasChanged();
|
||||
SelectionChanged();
|
||||
}
|
||||
|
||||
// Only select the given row (for radio selection)
|
||||
void ITable.SetSelection(ISelectionColumn selectItem)
|
||||
{
|
||||
ClearSelectedRows();
|
||||
|
||||
_preventRowDataTriggerSelectedRowsChanged = true;
|
||||
selectItem.RowData.Selected = true;
|
||||
_preventRowDataTriggerSelectedRowsChanged = false;
|
||||
|
||||
_selection.StateHasChanged();
|
||||
SelectionChanged();
|
||||
}
|
||||
|
||||
private void SelectItem(TItem item)
|
||||
{
|
||||
if (!RowSelectable(item))
|
||||
return;
|
||||
|
||||
_selectedRows.Add(item);
|
||||
if (_dataSourceCache.TryGetValue(item, out var rowData))
|
||||
{
|
||||
rowData.SetSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSelection(IEnumerable<TItem> items)
|
||||
{
|
||||
if (ReferenceEquals(items, _selectedRows))
|
||||
return;
|
||||
|
||||
EnsureSelection();
|
||||
|
||||
if (items is not null and not IReadOnlyCollection<TItem>)
|
||||
// Ensure that the given enumerable doesn't change when we clear the current collection
|
||||
// (which would happen when the given enumerable is based on _selectedRows with linq methods)
|
||||
items = items.ToArray();
|
||||
|
||||
ClearSelectedRows();
|
||||
items?.ForEach(SelectItem);
|
||||
|
||||
_selection.StateHasChanged();
|
||||
SelectionChanged();
|
||||
}
|
||||
|
||||
public void SetSelection(TItem item)
|
||||
{
|
||||
EnsureSelection();
|
||||
|
||||
ClearSelectedRows();
|
||||
if (item != null)
|
||||
{
|
||||
SelectItem(item);
|
||||
}
|
||||
|
||||
_selection.StateHasChanged();
|
||||
SelectionChanged();
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
[MemberNotNull(nameof(_selection))]
|
||||
#endif
|
||||
private void EnsureSelection()
|
||||
{
|
||||
if (_selection == null)
|
||||
{
|
||||
throw new InvalidOperationException("To use the SetSelection method for a table, you should add a Selection component to the column definition.");
|
||||
}
|
||||
}
|
||||
|
||||
void ITable.SelectionChanged() => SelectionChanged();
|
||||
|
||||
private void SelectionChanged()
|
||||
|
@ -471,11 +471,7 @@ namespace AntDesign
|
||||
{
|
||||
if (_outerSelectedRows != null)
|
||||
{
|
||||
_selectedRows = GetAllItemsByTopLevelItems(_showItems, true).Intersect(_outerSelectedRows).ToHashSet();
|
||||
if (_selectedRows.Count != _outerSelectedRows.Count())
|
||||
{
|
||||
SelectedRowsChanged.InvokeAsync(_selectedRows);
|
||||
}
|
||||
SetSelection(_outerSelectedRows);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -483,13 +479,9 @@ namespace AntDesign
|
||||
}
|
||||
|
||||
var removedCacheItems = _dataSourceCache.Keys.Except(_showItems).ToArray();
|
||||
if (removedCacheItems.Length > 0)
|
||||
foreach (var item in removedCacheItems)
|
||||
{
|
||||
foreach (var item in removedCacheItems)
|
||||
{
|
||||
_dataSourceCache.Remove(item);
|
||||
}
|
||||
_allRowDataCache.Clear();
|
||||
_dataSourceCache.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
@ -593,51 +585,28 @@ namespace AntDesign
|
||||
private IEnumerable<TItem> GetAllItemsByTopLevelItems(IEnumerable<TItem> items, bool onlySelectable = false)
|
||||
{
|
||||
if (items?.Any() != true) return Array.Empty<TItem>();
|
||||
if (TreeChildren == null) return items;
|
||||
var result = GetAllDataItemsWithParent(items.Select(x => new DataItemWithParent<TItem>
|
||||
if (TreeChildren != null)
|
||||
{
|
||||
Data = x,
|
||||
Parent = null
|
||||
})).Select(x => x.Data);
|
||||
if (onlySelectable) result = result.Where(x => RowSelectable(x));
|
||||
return result.ToHashSet();
|
||||
var itemsSet = new HashSet<TItem>();
|
||||
AddAllItemsAndChildren(items);
|
||||
items = itemsSet;
|
||||
|
||||
IEnumerable<DataItemWithParent<TItem>> GetAllDataItemsWithParent(IEnumerable<DataItemWithParent<TItem>> dataItems)
|
||||
{
|
||||
if (dataItems?.Any() != true) return Array.Empty<DataItemWithParent<TItem>>();
|
||||
if (TreeChildren == null) return dataItems ?? Array.Empty<DataItemWithParent<TItem>>();
|
||||
return dataItems.Union(
|
||||
dataItems.SelectMany(
|
||||
x1 =>
|
||||
{
|
||||
var ancestors = x1.GetAllAncestors().Select(x2 => x2.Data).ToHashSet();
|
||||
return GetAllDataItemsWithParent(TreeChildren(x1.Data)?.Select(x2 => new DataItemWithParent<TItem>
|
||||
{
|
||||
Data = x2,
|
||||
Parent = x1
|
||||
}).Where(x2 => !ancestors.Contains(x2.Data) && x2.Data?.Equals(x1.Data) == false));
|
||||
})
|
||||
).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private class DataItemWithParent<T>
|
||||
{
|
||||
public T Data { get; set; }
|
||||
|
||||
public DataItemWithParent<T> Parent { get; set; }
|
||||
|
||||
public IEnumerable<DataItemWithParent<T>> GetAllAncestors()
|
||||
{
|
||||
var result = new HashSet<DataItemWithParent<T>>();
|
||||
var parent = Parent;
|
||||
while (parent != null)
|
||||
void AddAllItemsAndChildren(IEnumerable<TItem> itemsToAdd)
|
||||
{
|
||||
result.Add(parent);
|
||||
parent = parent.Parent;
|
||||
if (itemsToAdd is null)
|
||||
return;
|
||||
foreach (TItem item in itemsToAdd)
|
||||
{
|
||||
if (!itemsSet.Add(item))
|
||||
continue;
|
||||
|
||||
AddAllItemsAndChildren(TreeChildren(item));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (onlySelectable) items = items.Where(x => RowSelectable(x));
|
||||
return items;
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
|
@ -4,43 +4,35 @@ using System.Text;
|
||||
|
||||
namespace AntDesign.TableModels
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class RowData<TItem> : RowData
|
||||
{
|
||||
public TItem Data { get; set; }
|
||||
internal TableDataItem<TItem> DataItem { get; }
|
||||
public override TableDataItem TableDataItem => DataItem;
|
||||
|
||||
internal Dictionary<TItem, RowData<TItem>> Children { get; set; } = new();
|
||||
public TItem Data => DataItem.Data;
|
||||
public Table<TItem> Table => DataItem.Table;
|
||||
|
||||
public RowData(int rowIndex, int pageIndex, TItem data)
|
||||
internal Dictionary<TItem, RowData<TItem>> Children { get; set; }
|
||||
|
||||
public RowData(TableDataItem<TItem> dataItem)
|
||||
{
|
||||
this.RowIndex = rowIndex;
|
||||
this.PageIndex = pageIndex;
|
||||
this.Data = data;
|
||||
DataItem = dataItem;
|
||||
}
|
||||
}
|
||||
|
||||
public class RowData
|
||||
/// <summary>
|
||||
/// Holds all data that is specific to a row, e.g. the row being expanded or not.
|
||||
/// See <see cref="TableDataItem"/> for all properties that are specific to an item instead of a row.
|
||||
/// </summary>
|
||||
public abstract class RowData
|
||||
{
|
||||
private bool _selected;
|
||||
|
||||
private bool _expanded;
|
||||
|
||||
public int RowIndex { get; set; }
|
||||
|
||||
public int PageIndex { get; set; }
|
||||
|
||||
public bool Selected
|
||||
{
|
||||
get => _selected;
|
||||
set
|
||||
{
|
||||
if (_selected != value)
|
||||
{
|
||||
_selected = value;
|
||||
SelectedChanged?.Invoke(this, _selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Expanded
|
||||
{
|
||||
get => _expanded;
|
||||
@ -56,22 +48,77 @@ namespace AntDesign.TableModels
|
||||
|
||||
public int Level { get; set; }
|
||||
|
||||
public int CacheKey { get; set; }
|
||||
public abstract TableDataItem TableDataItem { get; }
|
||||
|
||||
public bool HasChildren { get; set; }
|
||||
|
||||
public event Action<RowData, bool> SelectedChanged;
|
||||
public bool Selected { get => TableDataItem.Selected; set => TableDataItem.Selected = value; }
|
||||
public bool HasChildren { get => TableDataItem.HasChildren; set => TableDataItem.HasChildren = value; }
|
||||
|
||||
public event Action<RowData, bool> ExpandedChanged;
|
||||
|
||||
internal void SetSelected(bool selected)
|
||||
{
|
||||
_selected = selected;
|
||||
}
|
||||
|
||||
internal void SetExpanded(bool expanded)
|
||||
{
|
||||
_expanded = expanded;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public class TableDataItem<TItem> : TableDataItem
|
||||
{
|
||||
public TItem Data { get; }
|
||||
|
||||
public Table<TItem> Table { get; }
|
||||
|
||||
public TableDataItem(TItem data, Table<TItem> table)
|
||||
{
|
||||
this.Data = data;
|
||||
Table = table;
|
||||
}
|
||||
|
||||
protected override void OnSelectedChanged(bool value)
|
||||
{
|
||||
base.OnSelectedChanged(value);
|
||||
Table.DataItemSelectedChanged(this, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the properties of an item within a table.
|
||||
/// Is unique for each item in a table (e.g. even if the item is displayed more than once,
|
||||
/// there will only be one <see cref="TableDataItem"/>).
|
||||
/// Therefore, all rows with the same item will be selected/deselected all at once.
|
||||
/// <br/>
|
||||
/// For row specific data, see <see cref="RowData"/>.
|
||||
/// </summary>
|
||||
public abstract class TableDataItem
|
||||
{
|
||||
private bool _selected;
|
||||
|
||||
public bool Selected
|
||||
{
|
||||
get => _selected;
|
||||
set
|
||||
{
|
||||
if (_selected != value)
|
||||
{
|
||||
OnSelectedChanged(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasChildren { get; set; }
|
||||
|
||||
public event Action<TableDataItem, bool> SelectedChanged;
|
||||
|
||||
protected virtual void OnSelectedChanged(bool value)
|
||||
{
|
||||
SetSelected(value);
|
||||
}
|
||||
|
||||
internal void SetSelected(bool selected, bool triggersSelectedChanged = true)
|
||||
{
|
||||
_selected = selected;
|
||||
if (triggersSelectedChanged)
|
||||
SelectedChanged?.Invoke(this, _selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,13 +20,10 @@
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Action<RowData<TItem>, bool> RowDataSelectedChanged { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_rowData = RowData;
|
||||
_rowData.SelectedChanged += OnRowDataSelectedChanged;
|
||||
_rowData.DataItem.SelectedChanged += OnRowDataSelectedChanged;
|
||||
_rowData.ExpandedChanged += OnRowDataExpandedChanged;
|
||||
}
|
||||
|
||||
@ -34,18 +31,17 @@
|
||||
{
|
||||
if (_rowData != RowData)
|
||||
{
|
||||
_rowData.SelectedChanged -= OnRowDataSelectedChanged;
|
||||
_rowData.DataItem.SelectedChanged -= OnRowDataSelectedChanged;
|
||||
_rowData.ExpandedChanged -= OnRowDataExpandedChanged;
|
||||
_rowData = RowData;
|
||||
_rowData.SelectedChanged += OnRowDataSelectedChanged;
|
||||
_rowData.DataItem.SelectedChanged += OnRowDataSelectedChanged;
|
||||
_rowData.ExpandedChanged += OnRowDataExpandedChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRowDataSelectedChanged(RowData rowData, bool selected)
|
||||
private void OnRowDataSelectedChanged(TableDataItem rowData, bool selected)
|
||||
{
|
||||
_blockColumns = true;
|
||||
RowDataSelectedChanged?.Invoke(rowData as RowData<TItem>, selected);
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
@ -56,7 +52,7 @@
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
_rowData.SelectedChanged -= OnRowDataSelectedChanged;
|
||||
_rowData.DataItem.SelectedChanged -= OnRowDataSelectedChanged;
|
||||
_rowData.ExpandedChanged -= OnRowDataExpandedChanged;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user