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:
rhodon-jargon 2023-10-21 17:45:37 +02:00 committed by GitHub
parent bb0e043b07
commit cb88c530a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 269 additions and 214 deletions

View File

@ -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>
}

View File

@ -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)
{

View File

@ -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; }

View File

@ -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; }

View File

@ -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();
}

View File

@ -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()

View File

@ -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;")">

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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);
}
}
}

View File

@ -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;
}
}