Table组件新增动态设置固定列的功能,同时固定列支持持久化#8110
Conversation
refactor: 可以动态设置固定列,同时固定列支持表格持久化
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8110 +/- ##
==========================================
Coverage 100.00% 100.00%
==========================================
Files 769 769
Lines 34470 34596 +126
==========================================
+ Hits 34470 34596 +126
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Thanks for your PR, @Tony-ST0754. Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
Reviewer's GuideAdds persistence and dynamic updating of fixed columns in the Table component by extending the column state model, wiring fixed state through C# and JS, and introducing a new instance method and JS helper to sync client-side fixed-column changes into persistent storage. Sequence diagram for updating and persisting fixed table columnssequenceDiagram
actor User
participant Table as Table
participant JSRuntime as JSRuntime
participant TableJs as TableJs
participant LocalStorage as LocalStorage
User->>Table: UpdateTableColumnClientStatus()
loop for each item in Columns
Table->>Table: _tableColumnStates.Find(...).Fixed = item.Fixed
end
Table->>Table: StateHasChanged()
Table->>JSRuntime: InvokeVoidAsync(updateColumnStates, Id)
JSRuntime->>TableJs: updateColumnStates(id)
TableJs->>TableJs: getColumnStateObject(table)
TableJs->>LocalStorage: saveColumnStateToLocalstorage(table, state)
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
UpdateTableColumnClientStatus, usingFind(... )!assumes every column has a corresponding_tableColumnStatesentry; consider handling the case where no match is found to avoid a potentialNullReferenceExceptionwhen columns are dynamically added or renamed. - In
updateColumnStates(Table.razor.js),Data.get(id)is used without checking for an undefined result; adding a guard (and early return/log) would prevent runtime errors if the table instance has not been registered or has been cleaned up when this function is called.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `UpdateTableColumnClientStatus`, using `Find(... )!` assumes every column has a corresponding `_tableColumnStates` entry; consider handling the case where no match is found to avoid a potential `NullReferenceException` when columns are dynamically added or renamed.
- In `updateColumnStates` (Table.razor.js), `Data.get(id)` is used without checking for an undefined result; adding a guard (and early return/log) would prevent runtime errors if the table instance has not been registered or has been cleaned up when this function is called.
## Individual Comments
### Comment 1
<location path="src/BootstrapBlazor/Components/Table/Table.razor.cs" line_range="1955-1957" />
<code_context>
+ if (Columns.Count != 0)
+ {
+ // 用户在外面变更了列状态后,为避免用户变更状态丢失,须将变更后的状态同步到缓存中
+ foreach (var item in Columns)
+ {
+ _tableColumnStates.Find(x => x.Name == item.GetFieldName())!.Fixed = item.Fixed;
+ }
+ StateHasChanged();
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid assuming `_tableColumnStates` always contains a matching entry for each column.
Using `!` on the `Find` result will cause a runtime exception if `Columns` and `_tableColumnStates` ever diverge (e.g., column added/removed/renamed without a matching state). Consider handling `null` and skipping or logging missing entries to keep this resilient to mismatched state.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
refactor:清除空行,增加缓存列可能为判断
There was a problem hiding this comment.
Pull request overview
This PR adds support for dynamically updating a Table’s fixed-column configuration at runtime and persisting/restoring that fixed state alongside existing client column state (width/visibility), addressing issue #8094.
Changes:
- Add
FixedtoTableColumnStateso fixed-column state can be persisted. - Extend the Table JS column-state serialization to include
fixed, and add a JS API to update stored column states. - Add
Table.UpdateTableColumnClientStatus()plus a unit test for the new behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| test/UnitTest/Components/TableTest.cs | Adds a unit test covering UpdateTableColumnClientStatus and fixed-column persistence behavior. |
| src/BootstrapBlazor/Components/Table/TableColumnState.cs | Introduces Fixed property to enable persistence of fixed-column state. |
| src/BootstrapBlazor/Components/Table/Table.razor.js | Persists fixed in column state and adds updateColumnStates export. |
| src/BootstrapBlazor/Components/Table/Table.razor.cs | Restores fixed status from cached state and adds UpdateTableColumnClientStatus() instance method. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export function updateColumnStates(id) { | ||
| const el = document.getElementById(id) | ||
| if (el === null) { | ||
| return | ||
| } | ||
| let table = Data.get(id) | ||
| table.el = el; | ||
| Data.set(id, table) | ||
| const state = getColumnStateObject(table); | ||
| saveColumnStateToLocalstorage(table, state); | ||
| } |
| pb.AddChildContent<Table<Foo>>(pb => | ||
| { | ||
| pb.Add(a => a.ClientTableName, "test_update"); | ||
| pb.Add(a => a.RenderMode, TableRenderMode.Table); | ||
| pb.Add(a => a.AllowResizing, true); | ||
| pb.Add(a => a.OnQueryAsync, OnQueryAsync(localizer)); |
| var item = _tableColumnStates[index]; | ||
| var col = columnMap[item.Name]; | ||
| col.Fixed = !string.IsNullOrEmpty(item.Name) && stateMap.TryGetValue(item.Name, out var state) ? stateMap[item.Name].Fixed : false; | ||
| if (item.Visible) |
| public async Task<TableColumnClientStatus> UpdateTableColumnClientStatus() | ||
| { | ||
| if (Columns.Count != 0) | ||
| { | ||
| // 用户在外面变更了列状态后,为避免用户变更状态丢失,须将变更后的状态同步到缓存中 | ||
| foreach (var item in Columns) | ||
| { | ||
| var columnState = _tableColumnStates.Find(x => x.Name == item.GetFieldName()); | ||
| if (columnState != null) | ||
| { | ||
| columnState.Fixed = item.Fixed; | ||
| columnState.Width = item.Fixed && !item.Width.HasValue ? DefaultFixedColumnWidth : item.Width; | ||
| } | ||
| } | ||
| StateHasChanged(); | ||
| } | ||
| // 如果启用了 ClientTableName 则更新浏览器持久化列状态 | ||
| await InvokeVoidAsync("updateColumnStates", Id); | ||
|
|
||
| return _tableColumnStateCache; | ||
| } |
refactor:将持久化回来的列宽给到_visibleColumnsCache,以避免style运算出来的值会以默认列宽或200的数值参与运算
4bba10d to
0958204
Compare
| { | ||
| var item = _tableColumnStates[index]; | ||
| var col = columnMap[item.Name]; | ||
| col.Fixed = !string.IsNullOrEmpty(item.Name) && stateMap.TryGetValue(item.Name, out var stateF) ? stateMap[item.Name].Fixed : false; |
* fix(Table): use auto table layout when AllowResizing is enabled (#8095) Revert table-layout-fixed condition to IsFixedHeader only so that tables with AllowResizing keep content-based initial column widths as before v10.6. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(Version): bump version to 10.7.2-beta03 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(Utility): improve addLink method * chore: bump version to 10.7.2-beta04 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor: 增加超时策略逻辑 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* doc: 调整键值与组件名称一致 * refactor: 更改图标键值 * doc: 更新标签键值
* doc: 调整键值与组件名称一致 * refactor: 更改图标键值 * doc: 更新标签键值 * doc: 更新分类设置 * doc: 更新排序 * doc: 增加服务列表 * doc: 更新组件分类
* doc: 增加扩展组件与服务配置项 * doc: 补扩展服务
* fix: 修复 AutoComplete SkipMatch 选中候选项后不生效问题 * test: 更新单元测试 * chore: bump version 10.7.3-beta01 * refactor: 精简代码 * chore: bump version 10.7.3-beta02 * chore: bump version 10.7.3-beta01
#8151) bump version 10.0.19
* feat: 增加样式支持 * test: 补充单元测试
* feat(TreeNodeBase): 增加 svg 雪碧图支持 * feat(TreeView): 支持自定义节点图标 * feat(TreeView): 增加 IconTemplate 参数 * feat: 增加样式支持 * feat: 增加样式 * chore: bump version 10.7.3-beta02
* feat: 增加 OnDisabledCallback 参数 * test: 补充单元测试 * feat: 增加禁用项不能选中逻辑 * test: 增加单元测试
# Conflicts: # src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj # src/BootstrapBlazor.Server/docs.json # src/BootstrapBlazor/BootstrapBlazor.csproj # src/BootstrapBlazor/Components/Transfer/Transfer.razor # src/BootstrapBlazor/Components/Transfer/Transfer.razor.cs
link #8094 - sync runtime changes of ITableColumn.Fixed back to column states before rebuilding columns, so dynamic fixed columns survive column regeneration (AutoGenerateColumns/DynamicContext) - replay column states by state item directly, fix newly added columns losing Fixed value - invalidate fl/fr fixed column style caches when columns rebuilt - pass latest column states to updateColumnStates script, fix stale fixed state persisted to localStorage - use DefaultFixedColumnWidth in GetLeftStyle instead of hard-coded 200 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
link #8094 - add getColumnWidths script to measure actual rendered column widths on client - backfill measured width instead of DefaultFixedColumnWidth when a column without explicit width is fixed at runtime, so sticky offsets match client rendering and width does not jump on toggle - track auto backfilled widths and restore browser auto layout when column is unfixed; keep user resized width - share the same logic in UpdateTableColumnClientStatus Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
link #8094 An isolated fixed column (preceded by a non-fixed visible column, e.g. when column state order restored from localStorage differs from Columns order) was treated as fixed-right and its right offset summed all following non-fixed column widths. Treat a column as fixed-right only when it and all following columns are fixed (a fixed suffix); otherwise fall back to left fixed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
link #8094 GetLeftStyle/GetRightStyle accumulated widths of all preceding/following visible columns including non-fixed ones, guessing DefaultFixedColumnWidth for columns without explicit width. Non-fixed columns scroll out of the viewport and must not contribute to the sticky left/right offset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…time link #8094 - refresh a client column width snapshot after each render instead of awaiting script measurement inside the render pipeline, which made RenderTemplate render empty content and rebuild the table DOM, invalidating client script element references - in auto table layout col width behaves as content-box while measured widths are border-box, so applied widths rendered 2*padding+border wider and sticky offsets misaligned; backfill measured widths for all columns and switch to fixed table layout while fixed columns exist so colgroup widths apply exactly with zero width jump - restore all backfilled widths and auto layout when no fixed column remains; keep user resized widths Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ixed columns link #8094 In auto table layout a column with explicit width renders wider than the specified value (plus cell padding and border), so switching to fixed layout when fixed columns exist made such columns visibly shrink. Override every column state width with the measured client width while fixed columns exist and remember the original value, restoring it when no fixed column remains. Skip the width sync in UpdateTableColumnClientStatus when the fixed state is unchanged so manually adjusted widths are not clobbered by a stale snapshot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ence link #8094 The component has no built-in UI for end users to change column fixed state, so it is entirely code-driven and code is the source of truth. Persisting it to localStorage let stale client data override markup declarations, and the restored fixed state could never restore original widths after unfixing because the width overlay cache is in-memory only. - mark TableColumnState.Fixed with JsonIgnore so it is neither saved to nor restored from client persistence; stale fixed values in existing localStorage data are ignored on load - initialize restored column states with the column instance fixed value on first replay so markup declared fixed columns keep working - remove fixed field from the JS column state object Runtime state sync is unchanged: toggling fixed columns still survives column rebuilds within a session. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
link #8094 TableColumnState.Fixed was introduced on this branch and after excluding it from client persistence its only remaining purpose was carrying the runtime fixed state across column rebuilds. Replace it with an internal name-keyed cache that records only columns whose fixed state actually changed at runtime, so replay no longer touches columns managed by code (markup declarations, OnColumnCreating) and the restore workaround for persisted states becomes unnecessary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…re timing link #8094 - SyncColumnsFixedState now only detects and records runtime changes and returns whether anything changed; ApplyColumnFixedState removed, its duplicated detection and state lookup are gone - UpdateTableColumnClientStatus reuses the same detection and unifies its measure/sync conditions; measuring also covers fixed columns with explicit width plus other width-less columns which previously skipped width backfill - run SyncColumnsWidthState after columns are rebuilt and replayed so the fixed state it inspects is final; previously toggling fixed off during the same render that OnColumnCreating unfixes columns left overridden widths applied forever, also sync overridden width back to the column instance since replay has already run Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 改动:Table.razor.cs ResetTableColumns 回放处增加占位判别,列实例无宽且列状态宽等于 #8241 兜底值(DefaultFixedColumnWidth 或 ColumnMinWidth)时跳过 col.Width = item.Width - 为什么:#8241 每轮重建给无宽列状态回填 64/200 占位宽,回放将其灌进 col.Width 后,getColumnWidths 实测快照守卫(!Width.HasValue)永假、IsFixedColumnLayout 误判启用 table-layout-fixed,固定列时全表被压成 64px 塌缩、横向滚动消失(实测复现) - 边界:判别用常量值而非 GetDefaultColumnStateWidth 动态求值(固定状态变化会把占位值误判为真实宽);真实持久化宽/显式宽/实测宽仍正常回放;占位宽保留在列状态中供 JS 布局,不改变 #8241 既有语义 - 验证:dotnet test --filter DynamicFixedColumn 等 10 项全过;TableTest 320/320;真实浏览器(作者 demo + Edge)固定不塌缩、1920/800 视口 sticky 偏移精确、取消固定还原 - 影响面:既有用例零删改;列状态持久化行为不变 Co-Authored-By: Kimi <noreply@moonshot.cn>
…8094) - 改动:SyncColumnsWidthState else 分支还原逻辑提取为 RestoreAutoFixedColumnWidths()(原样搬移无行为变化);ClearTableColumnClientStatus 改为在 _tableColumnStateCache.Clear() 之前调用 RestoreAutoFixedColumnWidths(),替换原 _autoFixedColumnWidthCache.Clear() - 为什么:原实现固定列期间调用 ClearTableColumnClientStatus 只清空 _autoFixedColumnWidthCache 不还原;且 _tableColumnStates 就是 _tableColumnStateCache.Columns(Table.razor.Toolbar.cs:547),先清空状态列表后还原目标必然丢失,自动回填的实测宽永久残留在列实例上,取消固定也无法还原 - 边界:还原条件 state.Width == item.Applied 保持不变,用户拖拽调整过的列宽仍按原逻辑保留不清除 - 验证:新增 ClearTableColumnClientStatus_RestoreAutoFixedWidth_Ok 由红转绿(修复前亲见失败);TableTest 320/320 全过 - 影响面:仅影响 Clear 调用场景下的宽度还原时机,清 localStorage、清状态缓存、StateHasChanged 等既有行为不变 Co-Authored-By: Kimi <noreply@moonshot.cn>
- 新增 DynamicFixedColumn_IgnoreColumn_Ok:Ignore 列无列状态时 SyncColumnsWidthState 命中 state == null 跳过分支,补齐 PR 原有 2 行未覆盖代码(Table.razor.cs:1493-1494),全项目行覆盖率由 99.99% 回到 100%(34596/34596) - 新增 ClearTableColumnClientStatus_RestoreAutoFixedWidth_Ok:固定期间清除客户端状态后再取消固定,自动回填宽还原为空不残留(红绿验证:修复前先见红) - 修复 UpdateTableColumnClientStatus_Ok 弱断言:删除 if 包裹改为直接断言 fixed 类与 col 宽度,消除 CS8602 编译警告 - 验证:dotnet test test/UnitTest --collect:"XPlat Code Coverage" 全量 2510/2510 通过,line-rate 1.0;回放占位判别条件 8/8 分支全覆盖 - 影响面:仅新增/修正测试,不改动任何既有用例断言 Co-Authored-By: Kimi <noreply@moonshot.cn>
refactor: 可以动态设置固定列,同时固定列支持表格持久化
Link issues
fixes #8094
功能描述 / Problem Description
TableColumnState增加Fixed属性,用以持久化用UpdateTableColumnClientStatus,用以在用户自定义前n列固定时,回调生效,并同步持久化代码示例
BootstrapBlazorApp11.zip
Regression?
Risk
Verification
Packaging changes reviewed?
☑️ Self Check before Merge
Summary by Sourcery
Enable dynamic configuration and persistence of fixed table columns and expose an API to update client-side column state.
New Features:
Enhancements:
Tests: