diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 5362856e581..3be134664f5 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 11.1.0-rc.3.3 + 11.1.0-rc.3.4 diff --git a/src/BootstrapBlazor/Components/Table/Table.razor b/src/BootstrapBlazor/Components/Table/Table.razor index 5b6ef0c9ba9..24bbaefb827 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor +++ b/src/BootstrapBlazor/Components/Table/Table.razor @@ -194,7 +194,7 @@ if (IsShowEmpty) { - + diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index 7ca935f78d1..da00d1f2626 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -2049,6 +2049,8 @@ private async Task OnContextMenu(MouseEventArgs e, TItem item) /// /// 获得/设置 表格客户端状态调整回调方法 /// Gets or sets Table Column Client Status Changed Callback + /// 第一个参数为发生变化的列名称;调用 批量调整时为 "*",第二个参数包含完整列状态。 + /// The first argument is the changed column name, or "*" for a batch update by . The second argument contains the full column state. /// v10.6.1 /// [Parameter] @@ -2072,6 +2074,8 @@ private async Task OnContextMenu(MouseEventArgs e, TItem item) /// /// 列宽自适应方法 /// Fit All Column Width Method + /// 启用 时,按当前已渲染内容调整所有可见数据列的宽度;发生调整后统一保存列状态、触发一次回调。 + /// When is enabled, fits all visible data columns to their currently rendered content, then saves the column state and invokes the callback once if widths were fitted. /// public async Task FitAllColumnWidth() { diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.js b/src/BootstrapBlazor/Components/Table/Table.razor.js index a257c1fd4d4..a77960841cc 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.js +++ b/src/BootstrapBlazor/Components/Table/Table.razor.js @@ -120,6 +120,32 @@ export async function reset(id) { } } +export async function fitAllColumnWidth(id) { + const table = Data.get(id); + if (!table?.tables?.length) { + return; + } + + let resized = false; + for (const col of getResizableColumns(table)) { + if (await autoFitColumnWidth(table, col, false)) { + resized = true; + } + } + + if (resized) { + const state = getColumnStateObject(table); + saveColumnStateToLocalstorage(table, state); + + // use wildcard to notify all columns have been resized + await table.invoke.invokeMethodAsync( + table.options.resizeColumnCallback, + "*", + state + ); + } +} + export async function switchCardView(id) { const table = Data.get(id); @@ -471,7 +497,7 @@ const setResizeListener = table => { let colIndex = 0 let originalX = 0 - const columns = [...table.tables[0].querySelectorAll('.col-resizer')] + const columns = getResizableColumns(table); columns.forEach(col => { table.columns.push(col) EventHandler.on(col, 'click', e => e.stopPropagation()); @@ -610,19 +636,20 @@ const getColumnName = col => getColumnHeader(col).getAttribute('data-bb-field'); const getResizableColumnWidth = col => getWidth(getColumnHeader(col)) | 0; +const getResizableColumns = table => [...table.tables[0].querySelectorAll(':scope > thead > tr > th > .col-resizer')]; + const indexOfCol = col => { const th = getColumnHeader(col); const row = th.parentElement; return [...row.children].indexOf(th); } -const autoFitColumnWidth = async (table, col) => { +const autoFitColumnWidth = async (table, col, notify = true) => { stopAutoColumnWidth(table); const field = getColumnName(col); const index = indexOfCol(col); - let rows = null; let maxWidth = getColumnMaxCellWidth(table, index); - + if (table.options.fitColumnWidthIncludeHeader) { const th = getColumnHeader(col); maxWidth = Math.max(maxWidth, getCellWidth(th)); @@ -630,15 +657,15 @@ const autoFitColumnWidth = async (table, col) => { if (maxWidth > 0) { table.tables.forEach(table => { - const colEl = table.querySelectorAll('colgroup col')[index]; + const colEl = table.querySelectorAll(':scope > colgroup > col')[index]; if (colEl) { colEl.style.setProperty('width', `${maxWidth}px`); } - const th = table.querySelectorAll('thead > tr > th')[index]; + const th = table.querySelectorAll(':scope > thead > tr > th')[index]; if (th) { const span = th.querySelector('.table-text'); - span.style.removeProperty('width'); + span?.style.removeProperty('width'); } const tableWidth = getTableWidth(table); @@ -647,26 +674,28 @@ const autoFitColumnWidth = async (table, col) => { resetColumnWidthTips(table, col); - const state = getColumnStateObject(table); - saveColumnStateToLocalstorage(table, state); + // notify or not + if (notify) { + const state = getColumnStateObject(table); + saveColumnStateToLocalstorage(table, state); - await table.invoke.invokeMethodAsync(table.options.resizeColumnCallback, field, state); + await table.invoke.invokeMethodAsync(table.options.resizeColumnCallback, field, state); + } + return true; } + return false; } const getColumnMaxCellWidth = (table, index) => { - let rows = null; + const body = table.thead ? table.tables[1] : table.tables[0]; + const rows = body.querySelectorAll(':scope > tbody > tr:not(.is-detail):not(.is-empty)'); let maxWidth = 0; - if (table.thead) { - rows = [...table.tables[1].tBodies[0].rows].filter(x => !x.classList.contains('is-detail')); - } - else { - rows = [...table.tables[0].tBodies[0].rows].filter(x => !x.classList.contains('is-detail')); - } rows.forEach(row => { const cell = row.cells[index]; - maxWidth = Math.max(maxWidth, calcCellWidth(cell)); + if (cell && cell.colSpan === 1) { + maxWidth = Math.max(maxWidth, calcCellWidth(cell)); + } }); return maxWidth; diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index 395953bbebc..e885cdb0616 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -3153,6 +3153,7 @@ public void ShowEmpty_Ok() }); cut.Contains("test-empty"); cut.Contains("colspan=\"1\""); + Assert.Single(cut.FindAll("tbody > tr.is-empty")); var table = cut.FindComponent>(); table.Render(pb => @@ -9034,8 +9035,10 @@ public async Task AllowDragColumn_FixedColumn_Ok() await cut.InvokeAsync(() => table.Instance.DragColumnCallback(1, 0)); } - [Fact] - public async Task OnTableColumnClientStatusChanged_ResizeColumn_Ok() + [Theory] + [InlineData(nameof(Foo.Address))] + [InlineData("*")] + public async Task OnTableColumnClientStatusChanged_ResizeColumn_Ok(string columnName) { var state = new TableColumnClientStatus(); state.TableWidth = 500; @@ -9045,6 +9048,7 @@ public async Task OnTableColumnClientStatusChanged_ResizeColumn_Ok() Context.JSInterop.Setup("getColumnStates", "test").SetResult(state); var name = ""; + var callbackCount = 0; TableColumnClientStatus? clientState = null; var localizer = Context.Services.GetRequiredService>(); var cut = Context.Render(pb => @@ -9057,6 +9061,7 @@ public async Task OnTableColumnClientStatusChanged_ResizeColumn_Ok() pb.Add(a => a.FitColumnWidthIncludeHeader, true); pb.Add(a => a.OnTableColumnClientStatusChanged, (field, state) => { + callbackCount++; name = field; clientState = state; return Task.CompletedTask; @@ -9082,9 +9087,15 @@ public async Task OnTableColumnClientStatusChanged_ResizeColumn_Ok() newState.TableWidth = 100; newState.Columns.Add(new TableColumnState() { Name = "Name", Width = 50 }); newState.Columns.Add(new TableColumnState() { Name = "Address", Width = 50 }); - await cut.InvokeAsync(() => table.Instance.ResizeColumnCallback(nameof(Foo.Address), newState)); - Assert.Equal("Address", name); + await cut.InvokeAsync(() => table.Instance.ResizeColumnCallback(columnName, newState)); + Assert.Equal(columnName, name); + Assert.Equal(1, callbackCount); Assert.NotNull(clientState); + Assert.Equal(100, clientState.TableWidth); + Assert.Equal(50, clientState.Columns.Single(c => c.Name == nameof(Foo.Name)).Width); + Assert.Equal(50, clientState.Columns.Single(c => c.Name == nameof(Foo.Address)).Width); + Assert.False(clientState.Columns.Single(c => c.Name == nameof(Foo.Name)).Visible); + Assert.True(clientState.Columns.Single(c => c.Name == nameof(Foo.Address)).Visible); } [Fact]