From 692c31dc7945abd143e4a7b89152dfddb640a148 Mon Sep 17 00:00:00 2001 From: celadaris Date: Sat, 27 Dec 2025 21:54:33 -0600 Subject: [PATCH 1/7] Add formless mode to ValidateForm for in-cell table editing Introduces an IsFormless parameter to ValidateForm, allowing it to operate without rendering a form element, which is used for in-cell editing in Table. Updates Table to use this mode for in-cell editing, ensures validation is performed, and manages EditContext accordingly. Also fixes validation logic and EditTemplate usage to support the new mode. --- .../Components/Table/Table.razor | 40 +++++++++++++++---- .../Components/Table/Table.razor.Edit.cs | 8 +++- .../Components/Table/Table.razor.Toolbar.cs | 5 +++ .../Components/Table/Table.razor.cs | 2 +- .../ValidateForm/ValidateForm.razor | 24 +++++++---- .../ValidateForm/ValidateForm.razor.cs | 34 ++++++++++++++++ 6 files changed, 96 insertions(+), 17 deletions(-) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor b/src/BootstrapBlazor/Components/Table/Table.razor index 88533b388a9..450a63d0dac 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor +++ b/src/BootstrapBlazor/Components/Table/Table.razor @@ -787,18 +787,42 @@ { @RenderRowExtendButtons(item) } - @if (RowContentTemplate != null) - { - var columns = GetVisibleColumns(); - @RowContentTemplate(new(item, columns, ActiveRenderMode)) + @{ + var isInCellEditRow = InCellMode && SelectedRows.FirstOrDefault() == item; } - else + @if (isInCellEditRow) { - @RenderContentRow(item) + + @if (RowContentTemplate != null) + { + var columns = GetVisibleColumns(); + @RowContentTemplate(new(item, columns, ActiveRenderMode)) + } + else + { + @RenderContentRow(item) + } + @if (ShowExtendButtons && !IsExtendButtonsInRowHeader) + { + @RenderRowExtendButtons(item) + } + } - @if (ShowExtendButtons && !IsExtendButtonsInRowHeader) + else { - @RenderRowExtendButtons(item) + @if (RowContentTemplate != null) + { + var columns = GetVisibleColumns(); + @RowContentTemplate(new(item, columns, ActiveRenderMode)) + } + else + { + @RenderContentRow(item) + } + @if (ShowExtendButtons && !IsExtendButtonsInRowHeader) + { + @RenderRowExtendButtons(item) + } } ; diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs b/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs index b5637b6bdf6..1b6be70e9bb 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs @@ -703,7 +703,13 @@ private async Task ClickEditButton(TItem item) private async Task ClickUpdateButtonCallback() { - var context = new EditContext(EditModel); + // 验证 InCell 模式下的表单 + if (_inCellValidateForm != null && !_inCellValidateForm.Validate()) + { + return; + } + + var context = _inCellValidateForm?.GetEditContext() ?? new EditContext(EditModel); await SaveAsync(context, AddInCell ? ItemChangedType.Add : ItemChangedType.Update); } diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.Toolbar.cs b/src/BootstrapBlazor/Components/Table/Table.razor.Toolbar.cs index e8b4f1699ab..d96aef07b4e 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.Toolbar.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.Toolbar.cs @@ -499,6 +499,11 @@ private bool GetColumnsListState(ColumnVisibleItem item) private bool InCellMode => AddInCell || EditInCell; + /// + /// 获得 InCell 模式下的 ValidateForm 实例 + /// + private ValidateForm? _inCellValidateForm; + /// /// 新建按钮方法 /// diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index 7b85ff48ce4..80e24c23ceb 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -1501,7 +1501,7 @@ RenderFragment RenderTemplate() => col.Template == null : col.Template(item); RenderFragment RenderEditTemplate() => col.EditTemplate == null - ? new RenderFragment(builder => builder.CreateComponentByFieldType(this, col, item, changedType, isSearch: false, col.GetLookupService(InjectLookupService), skipValidate: true)) + ? new RenderFragment(builder => builder.CreateComponentByFieldType(this, col, item, changedType, isSearch: false, col.GetLookupService(InjectLookupService), skipValidate: false)) : col.EditTemplate(item); } diff --git a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor index 9b5ae3d6e45..97f46175c3b 100644 --- a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor +++ b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor @@ -5,12 +5,22 @@ @if (Model != null) { - - - @ChildContent - + @if (IsFormless) + { + + + @ChildContent + + } + else + { + + + @ChildContent + + } } diff --git a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs index 4cd3093e639..7926efe5264 100644 --- a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs +++ b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs @@ -83,6 +83,13 @@ public partial class ValidateForm [Parameter] public bool? ShowLabelTooltip { get; set; } + /// + /// 获得/设置 是否为无表单模式 默认 false + /// + /// 设置为 true 时不渲染 form 元素,仅级联 EditContext 用于 Table InCell 编辑模式 + [Parameter] + public bool IsFormless { get; set; } + /// /// 获得/设置 是否禁用表单内回车自动提交功能 默认 null 未设置 /// @@ -146,6 +153,15 @@ protected override void OnParametersSet() { DisableAutoSubmitFormByEnter = BootstrapBlazorOptions.CurrentValue.DisableAutoSubmitFormByEnter.Value; } + + // 无表单模式下创建/更新 EditContext + if (IsFormless && Model != null) + { + if (_formlessEditContext == null || _formlessEditContext.Model != Model) + { + _formlessEditContext = new EditContext(Model); + } + } } /// @@ -330,6 +346,13 @@ internal async Task ValidateObject(ValidationContext context, List 0; + } } } @@ -636,6 +659,17 @@ private async Task OnInvalidSubmitForm(EditContext context) [NotNull] private BootstrapBlazorDataAnnotationsValidator? Validator { get; set; } + /// + /// 获得/设置 无表单模式下的 EditContext 实例 + /// + private EditContext? _formlessEditContext; + + /// + /// 获取 EditContext 实例 + /// + /// + public EditContext? GetEditContext() => _formlessEditContext; + /// /// 验证方法 用于代码调用触发表单验证 /// From 4c111b25b3db75dc24a5f1664c7fa234891b7a4f Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sun, 28 Dec 2025 13:06:53 +0800 Subject: [PATCH 2/7] =?UTF-8?q?refactor:=20=E4=BB=A3=E7=A0=81=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E7=B2=BE=E7=AE=80=E4=BB=A3=E7=A0=81=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Table/Table.razor | 43 +++---------------- .../Components/Table/Table.razor.cs | 30 +++++++++++++ 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor b/src/BootstrapBlazor/Components/Table/Table.razor index 450a63d0dac..e464b841182 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor +++ b/src/BootstrapBlazor/Components/Table/Table.razor @@ -787,43 +787,7 @@ { @RenderRowExtendButtons(item) } - @{ - var isInCellEditRow = InCellMode && SelectedRows.FirstOrDefault() == item; - } - @if (isInCellEditRow) - { - - @if (RowContentTemplate != null) - { - var columns = GetVisibleColumns(); - @RowContentTemplate(new(item, columns, ActiveRenderMode)) - } - else - { - @RenderContentRow(item) - } - @if (ShowExtendButtons && !IsExtendButtonsInRowHeader) - { - @RenderRowExtendButtons(item) - } - - } - else - { - @if (RowContentTemplate != null) - { - var columns = GetVisibleColumns(); - @RowContentTemplate(new(item, columns, ActiveRenderMode)) - } - else - { - @RenderContentRow(item) - } - @if (ShowExtendButtons && !IsExtendButtonsInRowHeader) - { - @RenderRowExtendButtons(item) - } - } + @RenderRowCell(item) ; RenderFragment> RenderContentCell => context => @@ -1176,4 +1140,9 @@ OnValueChanged="visible => OnToggleColumnVisible(item.Name, visible)"> ; + + RenderFragment RenderRowContentWithValidateForm => item => + @ + @RenderRowContent(item) + ; } diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index 80e24c23ceb..cbcec66299a 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -1840,6 +1840,36 @@ private void OnTouchEnd() private object? GetKeyByITem(TItem item) => SortableList != null ? item : null; //OnGetRowKey?.Invoke(item); + private RenderFragment RenderRowCell(TItem item) => builder => + { + var isInCellEditRow = InCellMode && SelectedRows.FirstOrDefault() == item; + if (isInCellEditRow) + { + builder.AddContent(0, RenderRowContentWithValidateForm(item)); + } + else + { + builder.AddContent(1, RenderRowContent(item)); + } + }; + + private RenderFragment RenderRowContent(TItem item) => builder => + { + if (RowContentTemplate != null) + { + var columns = GetVisibleColumns(); + builder.AddContent(0, RowContentTemplate(new(item, columns, ActiveRenderMode))); + } + else + { + builder.AddContent(1, RenderContentRow(item)); + } + if (ShowExtendButtons && !IsExtendButtonsInRowHeader) + { + builder.AddContent(2, RenderRowExtendButtons(item)); + } + }; + /// /// Dispose 方法 /// From c7ae8b5dcb5474968f2fbb70d4d0d5f5ecd52aa3 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sun, 28 Dec 2025 13:26:35 +0800 Subject: [PATCH 3/7] =?UTF-8?q?refactor:=20=E7=B2=BE=E7=AE=80=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs | 2 +- .../Components/ValidateForm/ValidateForm.razor.cs | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs b/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs index 1b6be70e9bb..305a8f4058e 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs @@ -709,7 +709,7 @@ private async Task ClickUpdateButtonCallback() return; } - var context = _inCellValidateForm?.GetEditContext() ?? new EditContext(EditModel); + var context = new EditContext(EditModel); await SaveAsync(context, AddInCell ? ItemChangedType.Add : ItemChangedType.Update); } diff --git a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs index 7926efe5264..ddb492e85c8 100644 --- a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs +++ b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs @@ -664,12 +664,6 @@ private async Task OnInvalidSubmitForm(EditContext context) /// private EditContext? _formlessEditContext; - /// - /// 获取 EditContext 实例 - /// - /// - public EditContext? GetEditContext() => _formlessEditContext; - /// /// 验证方法 用于代码调用触发表单验证 /// From 8041285997c9c12918aad8a5852c298ce4ee12e6 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sun, 28 Dec 2025 13:38:38 +0800 Subject: [PATCH 4/7] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E6=80=A7?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/ValidateForm/ValidateForm.razor | 4 ++-- .../Components/ValidateForm/ValidateForm.razor.cs | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor index 97f46175c3b..a4dd260505d 100644 --- a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor +++ b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor @@ -1,4 +1,4 @@ -@namespace BootstrapBlazor.Components +@namespace BootstrapBlazor.Components @inherits BootstrapModuleComponentBase @attribute [BootstrapModuleAutoLoader] @@ -7,7 +7,7 @@ @if (IsFormless) { - + @ChildContent diff --git a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs index ddb492e85c8..4094f7997d9 100644 --- a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs +++ b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs @@ -155,12 +155,9 @@ protected override void OnParametersSet() } // 无表单模式下创建/更新 EditContext - if (IsFormless && Model != null) + if (IsFormless) { - if (_formlessEditContext == null || _formlessEditContext.Model != Model) - { - _formlessEditContext = new EditContext(Model); - } + _formlessEditContext ??= new EditContext(Model); } } From 49cc373c992805210963511cb212d737058b5c40 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sun, 28 Dec 2025 13:42:27 +0800 Subject: [PATCH 5/7] =?UTF-8?q?refactor:=20=E6=B6=88=E9=99=A4=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/ValidateForm/ValidateForm.razor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs index 4094f7997d9..271577b7ebe 100644 --- a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs +++ b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs @@ -346,7 +346,7 @@ internal async Task ValidateObject(ValidationContext context, List 0; } From 966a97b56db3a683dde147bfeaa2c10339582514 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sun, 28 Dec 2025 13:53:51 +0800 Subject: [PATCH 6/7] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTest/Components/TableTest.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index 6c9fb74cc91..602cc7aff1d 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -3887,6 +3887,19 @@ public async Task InCell_Ok() }); var button = cut.Find("tbody tr td button"); await cut.InvokeAsync(() => button.Click()); + + // 增加 Validate 测试 + // 设置姓名为 null 保存按钮不成功 + var nameField = cut.Find("tbody tr td input"); + Assert.Equal("张三 0001", nameField.GetAttribute("value")); + + await cut.InvokeAsync(() => nameField.Change("")); + Assert.Contains("is-invalid", nameField.ToMarkup()); + await cut.InvokeAsync(() => button.Click()); + + await cut.InvokeAsync(() => nameField.Change("张三 0001")); + Assert.Contains("is-valid", nameField.ToMarkup()); + await cut.InvokeAsync(() => button.Click()); } [Fact] From c437a9281604c635a2b494a7f3166f54139dcb03 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sun, 28 Dec 2025 14:03:20 +0800 Subject: [PATCH 7/7] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=96=B0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs | 2 +- src/BootstrapBlazor/Components/Table/Table.razor.Toolbar.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs b/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs index 305a8f4058e..f4136cd20ee 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.Edit.cs @@ -704,7 +704,7 @@ private async Task ClickEditButton(TItem item) private async Task ClickUpdateButtonCallback() { // 验证 InCell 模式下的表单 - if (_inCellValidateForm != null && !_inCellValidateForm.Validate()) + if (!_inCellValidateForm.Validate()) { return; } diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.Toolbar.cs b/src/BootstrapBlazor/Components/Table/Table.razor.Toolbar.cs index d96aef07b4e..f17a817dc23 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.Toolbar.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.Toolbar.cs @@ -502,7 +502,7 @@ private bool GetColumnsListState(ColumnVisibleItem item) /// /// 获得 InCell 模式下的 ValidateForm 实例 /// - private ValidateForm? _inCellValidateForm; + private ValidateForm _inCellValidateForm = default!; /// /// 新建按钮方法