using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Text.Json; using System.Threading.Tasks; using AntDesign.Core.HashCodes; using AntDesign.JsInterop; using AntDesign.TableModels; using Microsoft.AspNetCore.Components; namespace AntDesign { public partial class Table : AntDomComponentBase, ITable, IAsyncDisposable { private static readonly TItem _fieldModel = (TItem)RuntimeHelpers.GetUninitializedObject(typeof(TItem)); private static readonly EventCallbackFactory _callbackFactory = new EventCallbackFactory(); private bool _shouldRender = true; private int _parametersHashCode; [Parameter] public RenderMode RenderMode { get; set; } = RenderMode.Always; [Parameter] public IEnumerable DataSource { get => _dataSource; set { _waitingReload = true; _dataSourceCount = value?.Count() ?? 0; _dataSource = value ?? Enumerable.Empty(); } } [Parameter] public RenderFragment ChildContent { get; set; } [Parameter] public RenderFragment RowTemplate { get; set; } [Parameter] public RenderFragment> ExpandTemplate { get; set; } [Parameter] public bool DefaultExpandAllRows { get; set; } [Parameter] public Func, bool> RowExpandable { get; set; } = _ => true; [Parameter] public Func> TreeChildren { get; set; } = _ => Enumerable.Empty(); [Parameter] public EventCallback> OnChange { get; set; } [Parameter] public Func, Dictionary> OnRow { get; set; } [Parameter] public Func> OnHeaderRow { get; set; } [Parameter] public bool Loading { get; set; } [Parameter] public string Title { get; set; } [Parameter] public RenderFragment TitleTemplate { get; set; } [Parameter] public string Footer { get; set; } [Parameter] public RenderFragment FooterTemplate { get; set; } [Parameter] public TableSize Size { get; set; } [Parameter] public TableLocale Locale { get; set; } = LocaleProvider.CurrentLocale.Table; [Parameter] public bool Bordered { get; set; } = false; [Parameter] public string ScrollX { get; set; } [Parameter] public string ScrollY { get; set; } [Parameter] public int ScrollBarWidth { get; set; } = 17; [Parameter] public int IndentSize { get; set; } = 15; [Parameter] public int ExpandIconColumnIndex { get; set; } [Parameter] public Func, string> RowClassName { get; set; } = _ => ""; [Parameter] public Func, string> ExpandedRowClassName { get; set; } = _ => ""; [Parameter] public EventCallback> OnExpand { get; set; } [Parameter] public SortDirection[] SortDirections { get; set; } = SortDirection.Preset.Default; [Parameter] public string TableLayout { get; set; } [Parameter] public EventCallback> OnRowClick { get; set; } private bool _remoteDataSource; private bool _hasRemoteDataSourceAttribute; [Parameter] public bool RemoteDataSource { get => _remoteDataSource; set { _remoteDataSource = value; _hasRemoteDataSourceAttribute = true; } } [Inject] public DomEventService DomEventService { get; set; } public ColumnContext ColumnContext { get; set; } private IEnumerable _showItems; private IEnumerable _dataSource; private IList _summaryRows; private bool _waitingReload; private bool _waitingReloadAndInvokeChange; private bool _treeMode; private bool _hasFixLeft; private bool _hasFixRight; private bool _pingRight; private bool _pingLeft; private int _treeExpandIconColumnIndex; private ClassMapper _wrapperClassMapper = new ClassMapper(); private string TableLayoutStyle => TableLayout == null ? "" : $"table-layout: {TableLayout};"; private ElementReference _tableHeaderRef; private ElementReference _tableBodyRef; private bool ServerSide => _hasRemoteDataSourceAttribute ? RemoteDataSource : Total > _dataSourceCount; bool ITable.TreeMode => _treeMode; int ITable.IndentSize => IndentSize; string ITable.ScrollX => ScrollX; string ITable.ScrollY => ScrollY; int ITable.ScrollBarWidth => ScrollBarWidth; int ITable.ExpandIconColumnIndex => ExpandIconColumnIndex + (_selection != null && _selection.ColIndex <= ExpandIconColumnIndex ? 1 : 0); int ITable.TreeExpandIconColumnIndex => _treeExpandIconColumnIndex; bool ITable.HasExpandTemplate => ExpandTemplate != null; TableLocale ITable.Locale => this.Locale; SortDirection[] ITable.SortDirections => SortDirections; void ITable.OnExpandChange(int cacheKey) { if (OnExpand.HasDelegate && _dataSourceCache.TryGetValue(cacheKey, out var currentRowData)) { OnExpand.InvokeAsync(currentRowData); } } void ITable.AddSummaryRow(SummaryRow summaryRow) { _summaryRows ??= new List(); _summaryRows.Add(summaryRow); } public void ReloadData() { PageIndex = 1; FlushCache(); this.InternalReload(); } public QueryModel GetQueryModel() => BuildQueryModel(); private QueryModel BuildQueryModel() { var queryModel = new QueryModel(PageIndex, PageSize); foreach (var col in ColumnContext.HeaderColumns) { if (col is IFieldColumn fieldColumn) { if (fieldColumn.SortModel != null) { queryModel.AddSortModel(fieldColumn.SortModel); } if (fieldColumn.FilterModel != null) { queryModel.AddFilterModel(fieldColumn.FilterModel); } } } return queryModel; } void ITable.Refresh() { _shouldRender = true; StateHasChanged(); } void ITable.ReloadAndInvokeChange() { ReloadAndInvokeChange(); } void ITable.ColumnSorterChange(IFieldColumn column) { foreach (var col in ColumnContext.HeaderColumns) { if (col.ColIndex != column.ColIndex && col is IFieldColumn fieldCol && fieldCol.SorterMultiple <= 0 && fieldCol.Sortable) { fieldCol.ClearSorter(); } } ReloadAndInvokeChange(); } private void ReloadAndInvokeChange() { var queryModel = this.InternalReload(); if (OnChange.HasDelegate) { OnChange.InvokeAsync(queryModel); } } private QueryModel InternalReload() { var queryModel = BuildQueryModel(); if (ServerSide) { _showItems = _dataSource; _total = Total; } else { if (_dataSource != null) { var query = _dataSource.AsQueryable(); foreach (var sort in queryModel.SortModel.OrderBy(x => x.Priority)) { query = sort.SortList(query); } foreach (var filter in queryModel.FilterModel) { query = filter.FilterList(query); } _total = query.Count(); query = query.Skip((PageIndex - 1) * PageSize).Take(PageSize); queryModel.SetQueryableLambda(query); _showItems = query; if (_total != Total) { if (TotalChanged.HasDelegate) TotalChanged.InvokeAsync(_total); } } else { _showItems = Enumerable.Empty(); _total = 0; if (_total != Total) { if (TotalChanged.HasDelegate) TotalChanged.InvokeAsync(_total); } } } _treeMode = TreeChildren != null && (_showItems?.Any(x => TreeChildren(x)?.Any() == true) == true); if (_treeMode) { _treeExpandIconColumnIndex = ExpandIconColumnIndex + (_selection != null && _selection.ColIndex <= ExpandIconColumnIndex ? 1 : 0); } return queryModel; } private void SetClass() { string prefixCls = "ant-table"; ClassMapper.Add(prefixCls) .If($"{prefixCls}-fixed-header", () => ScrollY != null) .If($"{prefixCls}-bordered", () => Bordered) .If($"{prefixCls}-small", () => Size == TableSize.Small) .If($"{prefixCls}-middle", () => Size == TableSize.Middle) .If($"{prefixCls}-fixed-column {prefixCls}-scroll-horizontal", () => ScrollX != null) .If($"{prefixCls}-has-fix-left", () => _hasFixLeft) .If($"{prefixCls}-has-fix-right", () => _hasFixRight) .If($"{prefixCls}-ping-left", () => _pingLeft) .If($"{prefixCls}-ping-right", () => _pingRight) .If($"{prefixCls}-rtl", () => RTL) ; _wrapperClassMapper .Add($"{prefixCls}-wrapper") .If($"{prefixCls}-wrapper-rtl", () => RTL); } protected override void OnInitialized() { base.OnInitialized(); if (RowTemplate != null) { ChildContent = RowTemplate; } this.ColumnContext = new ColumnContext(this); SetClass(); if (ScrollX != null || ScrollY != null) { TableLayout = "fixed"; } InitializePagination(); FlushCache(); } protected override void OnAfterRender(bool firstRender) { base.OnAfterRender(firstRender); if (!firstRender) { this.FinishLoadPage(); } _shouldRender = false; } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); if (firstRender) { DomEventService.AddEventListener("window", "beforeunload", Reloading, false); if (ScrollX != null) { await SetScrollPositionClassName(); DomEventService.AddEventListener("window", "resize", OnResize, false); DomEventService.AddEventListener(_tableBodyRef, "scroll", OnScroll); } if (ScrollY != null && ScrollX != null) { await JsInvokeAsync(JSInteropConstants.BindTableHeaderAndBodyScroll, _tableBodyRef, _tableHeaderRef); } } } protected override void OnParametersSet() { base.OnParametersSet(); if (_waitingReloadAndInvokeChange) { _waitingReloadAndInvokeChange = false; _waitingReload = false; ReloadAndInvokeChange(); } else if (_waitingReload) { _waitingReload = false; InternalReload(); } if (this.RenderMode == RenderMode.ParametersHashCodeChanged) { var hashCode = this.GetParametersHashCode(); this._shouldRender = this._parametersHashCode != hashCode; this._parametersHashCode = hashCode; } else { this._shouldRender = true; } } protected override bool ShouldRender() { return this._shouldRender; } void ITable.HasFixLeft() => _hasFixLeft = true; void ITable.HasFixRight() => _hasFixRight = true; void ITable.TableLayoutIsFixed() { TableLayout = "fixed"; StateHasChanged(); } private async void OnResize(JsonElement _) => await SetScrollPositionClassName(); private async void OnScroll(JsonElement _) => await SetScrollPositionClassName(); private async Task SetScrollPositionClassName(bool clear = false) { if (_isReloading) return; var element = await JsInvokeAsync(JSInteropConstants.GetDomInfo, _tableBodyRef); var scrollWidth = element.ScrollWidth; var scrollLeft = element.ScrollLeft; var clientWidth = element.ClientWidth; var beforePingLeft = _pingLeft; var beforePingRight = _pingRight; if ((scrollWidth == clientWidth && scrollWidth != 0) || clear) { _pingLeft = false; _pingRight = false; } else if (scrollLeft == 0) { _pingLeft = false; _pingRight = true; } // allow the gap between 1 px, it's magic ✨ else if (Math.Abs(scrollWidth - (scrollLeft + clientWidth)) < 1) { _pingRight = false; _pingLeft = true; } else { _pingLeft = true; _pingRight = true; } if (beforePingLeft != _pingLeft || beforePingRight != _pingRight) { _shouldRender = true; } if (!clear) { StateHasChanged(); } } protected override void Dispose(bool disposing) { DomEventService.RemoveEventListerner("window", "resize", OnResize); DomEventService.RemoveEventListerner(_tableBodyRef, "scroll", OnScroll); DomEventService.RemoveEventListerner("window", "beforeunload", Reloading); base.Dispose(disposing); } public async ValueTask DisposeAsync() { if (!_isReloading) { if (ScrollY != null && ScrollX != null) { await JsInvokeAsync(JSInteropConstants.UnbindTableHeaderAndBodyScroll, _tableBodyRef); } } DomEventService.RemoveEventListerner("window", "beforeunload", Reloading); } bool ITable.RowExpandable(RowData rowData) { return RowExpandable(rowData as RowData); } /// /// Indicates that a page is being refreshed /// private bool _isReloading; private void Reloading(JsonElement jsonElement) { _isReloading = true; } } }