feat(Table): advance search dialog support UseSearchForm parameter#7783
feat(Table): advance search dialog support UseSearchForm parameter#7783
Conversation
Reviewer's GuideRefactors the search-form metadata model and integrates SearchForm-based advanced search into Table and SearchDialog, adding reset support and localization-aware metadata building while updating tests, samples, and APIs to the new conventions. Sequence diagram for Table advanced search using SearchForm in dialogsequenceDiagram
actor User
participant Table as Table~TItem~
participant DialogService
participant SearchDialog as SearchDialog~TItem~
participant SearchForm
participant ISearchItemExt as ISearchItemExtensions
User->>Table: click search button
Table->>Table: ShowSearchDialog()
alt UseSearchForm is true
Table->>DialogService: ShowSearchDialog(CreateSearchFormDialog())
DialogService->>SearchDialog: create with UseSearchForm=true
SearchDialog->>SearchForm: render with SearchItems, OnChanged
loop for each ISearchItem
SearchForm->>ISearchItemExt: BuildSearchMetadata(options)
ISearchItemExt-->>SearchForm: ISearchFormItemMetadata
SearchForm->>ISearchItemExt: CreateSearchItemComponentByMetadata()
end
User->>SearchForm: modify search fields
SearchForm->>SearchForm: Item.Metadata.ValueChanged invoked
SearchForm->>SearchForm: Items.ToFilter()
SearchForm-->>SearchDialog: OnChanged(FilterKeyValueAction)
SearchDialog-->>Table: OnSearchFormFilterChanged(action)
Table->>Table: _advanceSearchFilter = action
User->>SearchDialog: click search button
SearchDialog-->>Table: OnSearchClick
Table->>Table: SearchClick()
Table->>Table: GetAdvanceSearches()
Table->>Table: _advanceSearchFilter.ToSearches()
Table-->>User: results filtered
else UseSearchForm is false
Table->>DialogService: ShowSearchDialog with model based options
DialogService->>SearchDialog: create with editor form
User->>SearchDialog: search using model fields
SearchDialog-->>Table: OnSearchClick
Table-->>User: results filtered
end
Class diagram for updated search metadata model and integrationclassDiagram
direction LR
class ISearchFormItemMetadata {
<<interface>>
+string PlaceHolder
+Func~Task~ ValueChanged
+FilterLogic FilterLogic
+FilterAction FilterAction
+Func~object, FilterKeyValueAction~ GetFilterCallback
+FilterKeyValueAction GetFilter(string fieldName)
+void Reset()
}
class SearchMetadataBase {
<<abstract>>
+string PlaceHolder
+Func~Task~ ValueChanged
+FilterLogic FilterLogic
+FilterAction FilterAction
+Func~object, FilterKeyValueAction~ GetFilterCallback
+FilterKeyValueAction GetFilter(string fieldName)
+FilterKeyValueAction CreateFilter(string fieldName, object value)
+void Reset()
}
class StringSearchMetadata {
+string Value
+FilterKeyValueAction GetFilter(string fieldName)
+Task ValueChangedHandler(string value)
+void Reset()
}
class MultipleStringSearchMetadata {
+FilterKeyValueAction GetFilter(string fieldName)
}
class SelectSearchMetadata {
+IEnumerable~SelectedItem~ Items
}
class MultipleSelectSearchMetadata {
+FilterKeyValueAction GetFilter(string fieldName)
}
class CheckboxListSearchMetadata {
}
class NumberSearchMetadata {
+string StartValue
+string EndValue
+string StartValueLabelText
+string EndValueLabelText
+Type ValueType
+FilterKeyValueAction GetFilter(string fieldName)
+Task StartValueChangedHandler(string value)
+Task EndValueChangedHandler(string value)
+void Reset()
}
class DateTimeSearchMetadata {
+DateTime Value
+FilterKeyValueAction GetFilter(string fieldName)
+Task ValueChangedHandler(DateTime value)
+void Reset()
}
class DateTimeRangeSearchMetadata {
+DateTimeRangeValue Value
+FilterKeyValueAction GetFilter(string fieldName)
+Task ValueChangedHandler(DateTimeRangeValue value)
+void Reset()
}
class ISearchItem {
<<interface>>
+string FieldName
+Type PropertyType
+string Text
+bool? ShowLabel
+bool? ShowLabelTooltip
+int Cols
+ISearchFormItemMetadata Metadata
+FilterKeyValueAction GetFilter()
+void Reset()
}
class SearchItem {
+string FieldName
+Type PropertyType
+string Text
+bool? ShowLabel
+bool? ShowLabelTooltip
+int Cols
+ISearchFormItemMetadata Metadata
+FilterKeyValueAction GetFilter()
+void Reset()
}
class ITableColumn {
<<interface>>
+string FieldName
+Type PropertyType
+bool? Searchable
+ISearchFormItemMetadata SearchFormItemMetadata
}
class TableColumn~TItem,TType~ {
+ISearchFormItemMetadata SearchFormItemMetadata
}
class InternalTableColumn {
+ISearchFormItemMetadata SearchFormItemMetadata
}
class ISearchItemExtensions {
<<static>>
+ISearchFormItemMetadata BuildSearchMetadata(SearchFormLocalizerOptions options)
+RenderFragment CreateSearchItemComponentByMetadata()
}
class ITableColumnExtensions {
<<static>>
+ISearchItem ParseSearchItem(SearchFormLocalizerOptions options)
+ISearchFormItemMetadata BuildSearchMetadata(SearchFormLocalizerOptions options)
}
class SearchForm {
+IEnumerable~ISearchItem~ Items
+bool ShowLabel
+bool ShowLabelTooltip
+EditorFormRowType RowType
+int ItemsPerRow
+bool ShowUnsetGroupItemsOnTop
+EditorFormGroupType GroupType
+SearchFormLocalizerOptions SearchFormLocalizerOptions
+Func~FilterKeyValueAction, Task~ OnChanged
+FilterKeyValueAction AutoGenerateTemplate(ISearchItem item)
}
class SearchDialogOption~TModel~ {
+bool UseSearchForm
+List~ISearchItem~ SearchItems
+Func~FilterKeyValueAction, Task~ OnFilterChanged
+SearchFormLocalizerOptions SearchFormLocalizerOptions
}
class SearchDialog~TModel~ {
+bool UseSearchForm
+List~ISearchItem~ SearchItems
+Func~FilterKeyValueAction, Task~ OnChanged
+SearchFormLocalizerOptions SearchFormLocalizerOptions
+Task OnSearchFormFilterChanged(FilterKeyValueAction action)
}
class Table~TItem~ {
+bool UseSearchForm
+List~ISearchItem~ SearchFormItems
+FilterKeyValueAction _searchFilter
+FilterKeyValueAction _advanceSearchFilter
+Task ShowSearchDialog()
+Task OnSearchFormFilterChanged(FilterKeyValueAction action)
+List~IFilterAction~ GetAdvanceSearches()
}
ISearchFormItemMetadata <|.. SearchMetadataBase
SearchMetadataBase <|-- StringSearchMetadata
StringSearchMetadata <|-- MultipleStringSearchMetadata
StringSearchMetadata <|-- SelectSearchMetadata
SelectSearchMetadata <|-- MultipleSelectSearchMetadata
MultipleSelectSearchMetadata <|-- CheckboxListSearchMetadata
SearchMetadataBase <|-- NumberSearchMetadata
SearchMetadataBase <|-- DateTimeSearchMetadata
SearchMetadataBase <|-- DateTimeRangeSearchMetadata
ISearchItem <|.. SearchItem
ISearchItem --> ISearchFormItemMetadata : Metadata
ITableColumn --> ISearchFormItemMetadata : SearchFormItemMetadata
TableColumn~TItem,TType~ ..|> ITableColumn
InternalTableColumn ..|> ITableColumn
ISearchItemExtensions ..> ISearchItem : extension
ISearchItemExtensions ..> ISearchFormItemMetadata : builds
ITableColumnExtensions ..> ITableColumn : extension
ITableColumnExtensions ..> ISearchItem : creates
ITableColumnExtensions ..> ISearchFormItemMetadata : builds
SearchForm o--> ISearchItem : Items
SearchForm ..> ISearchFormItemMetadata : uses via Metadata
SearchDialogOption~TModel~ o--> ISearchItem : SearchItems
SearchDialogOption~TModel~ ..> SearchFormLocalizerOptions
SearchDialog~TModel~ o--> ISearchItem : SearchItems
SearchDialog~TModel~ ..> SearchForm : renders
Table~TItem~ o--> ISearchItem : SearchFormItems
Table~TItem~ ..> SearchDialogOption~TItem~ : creates
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
Table<TItem>.GetAdvanceSearchesyou call_advanceSearchFilter.ToSearches()whenUseSearchFormis true without guarding for_advanceSearchFilterbeing null; consider null-checking or returning an empty list to avoid a potentialNullReferenceExceptionwhen no advanced filter has been set yet. - In
SearchForm,GetSearchOptionscaches the first computedSearchFormLocalizerOptionsinstance in_optionsand never refreshes it; if theSearchFormLocalizerOptionsparameter is updated after initial render, those changes will be ignored, so consider invalidating_optionswhen the parameter changes.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `Table<TItem>.GetAdvanceSearches` you call `_advanceSearchFilter.ToSearches()` when `UseSearchForm` is true without guarding for `_advanceSearchFilter` being null; consider null-checking or returning an empty list to avoid a potential `NullReferenceException` when no advanced filter has been set yet.
- In `SearchForm`, `GetSearchOptions` caches the first computed `SearchFormLocalizerOptions` instance in `_options` and never refreshes it; if the `SearchFormLocalizerOptions` parameter is updated after initial render, those changes will be ignored, so consider invalidating `_options` when the parameter changes.
## Individual Comments
### Comment 1
<location path="src/BootstrapBlazor/Components/Table/Table.razor.Search.cs" line_range="307" />
<code_context>
}
- SearchDialogOption<TItem> CreateModelDialog() => new()
+ SearchDialogOption<ITableSearchModel> CreateCustomerModelDialog() => new()
{
- Class = "modal-dialog-table",
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring the new search dialog and search-form state logic into shared helpers to avoid duplication and make the flow easier to follow and maintain.
You can reduce the added complexity without changing behavior by centralizing the shared logic and encapsulating search-form-specific state.
### 1. Unify dialog construction
`CreateCustomerModelDialog`, `CreateSearchFormDialog`, and `CreateModelDialog` all set the same properties. You can introduce a small generic helper that configures the common bits once and then only set the deltas:
```csharp
private SearchDialogOption<TModel> CreateSearchDialogOption<TModel>(
Action<SearchDialogOption<TModel>> configure)
{
var option = new SearchDialogOption<TModel>
{
IsScrolling = ScrollingDialogContent,
Title = SearchModalTitle,
OnResetSearchClick = ResetSearchClick,
OnSearchClick = SearchClick,
RowType = SearchDialogRowType,
ItemsPerRow = SearchDialogItemsPerRow,
LabelAlign = SearchDialogLabelAlign,
Size = SearchDialogSize,
IsDraggable = SearchDialogIsDraggable,
ShowMaximizeButton = SearchDialogShowMaximizeButton,
ShowUnsetGroupItemsOnTop = ShowUnsetGroupItemsOnTop
};
configure(option);
return option;
}
```
Then the factory methods become very small and harder to drift:
```csharp
SearchDialogOption<ITableSearchModel> CreateCustomerModelDialog() =>
CreateSearchDialogOption<ITableSearchModel>(opt =>
{
opt.Model = CustomerSearchModel;
opt.DialogBodyTemplate = CustomerSearchTemplate;
});
SearchDialogOption<TItem> CreateModelDialog() =>
CreateSearchDialogOption<TItem>(opt =>
{
opt.Class = "modal-dialog-table";
opt.Model = SearchModel;
opt.DialogBodyTemplate = SearchTemplate;
opt.Items = Columns.Where(i => i.GetSearchable());
});
SearchDialogOption<TItem> CreateSearchFormDialog() =>
CreateSearchDialogOption<TItem>(opt =>
{
opt.Class = "modal-dialog-table modal-dialog-search-form";
opt.DialogBodyTemplate = SearchTemplate;
opt.UseSearchForm = true;
opt.SearchFormLocalizerOptions = SearchFormLocalizerOptions;
opt.SearchItems = SearchFormItems;
opt.OnFilterChanged = action =>
{
_advanceSearchFilter = action;
return Task.CompletedTask;
};
});
```
`ShowSearchDialog` then stays as-is but becomes easier to read since each factory is trivial.
---
### 2. Encapsulate search-form state
Instead of `_searchItems`, `_searchFilter`, `_advanceSearchFilter` plus helper methods, you can wrap them in a small helper to simplify lifecycle reasoning:
```csharp
private sealed class SearchFormState
{
public List<ISearchItem>? Items { get; private set; }
public FilterKeyValueAction? Filter { get; set; }
public FilterKeyValueAction? AdvancedFilter { get; set; }
public List<ISearchItem> EnsureItems(Func<List<ISearchItem>> factory)
{
Items ??= factory();
return Items;
}
public void Reset()
{
Filter = null;
AdvancedFilter = null;
if (Items != null)
{
foreach (var item in Items)
{
item.Reset();
}
Items = null;
}
}
}
```
Use a single field:
```csharp
private readonly SearchFormState _searchFormState = new();
private List<ISearchItem> SearchFormItems =>
_searchFormState.EnsureItems(GetSearchFormItems);
private Task OnSearchFormFilterChanged(FilterKeyValueAction action)
{
_searchFormState.Filter = action;
return Task.CompletedTask;
}
```
In `CreateSearchFormDialog` you then bind to the encapsulated state:
```csharp
SearchDialogOption<TItem> CreateSearchFormDialog() =>
CreateSearchDialogOption<TItem>(opt =>
{
// ...
opt.SearchItems = SearchFormItems;
opt.OnFilterChanged = action =>
{
_searchFormState.AdvancedFilter = action;
return Task.CompletedTask;
};
});
```
---
### 3. Simplify reset logic
With the helper, `ResetSearchClick` can delegate the search-form specifics to a single method:
```csharp
protected async Task ResetSearchClick()
{
await ToggleLoading(true);
if (UseSearchForm)
{
_searchFormState.Reset();
}
else if (CustomerSearchModel != null)
{
CustomerSearchModel.Reset();
}
else if (OnResetSearchAsync != null)
{
await OnResetSearchAsync(SearchModel);
}
else if (SearchTemplate == null)
{
Utility.Reset(SearchModel, CreateSearchModel());
}
await SearchClick();
await ToggleLoading(false);
}
```
This keeps `ResetSearchClick` as a high-level orchestrator and removes field-level manipulation from that method.
---
### 4. Make `GetAdvanceSearches` safe and centralized
`GetAdvanceSearches` currently assumes `_advanceSearchFilter` is non-null in search-form mode. A small guard plus the encapsulated state improves robustness:
```csharp
protected List<IFilterAction> GetAdvanceSearches()
{
if (UseSearchForm)
{
return _searchFormState.AdvancedFilter?.ToSearches()
?? new List<IFilterAction>();
}
var searches = new List<IFilterAction>();
// existing logic...
}
```
This removes the hidden null assumption while keeping behavior consistent (no advanced filters → empty list).
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Pull request overview
Adds first-class UseSearchForm support to Table’s advanced search dialog flow, and modernizes the SearchForm/search-metadata APIs to support auto-generated metadata, reset behavior, and updated callback patterns.
Changes:
- Add
UseSearchForm+SearchItemswiring for Table advanced search dialogs viaSearchDialogandDialogServiceExtensions. - Rename “MetaData” types/properties to “Metadata”, introduce
Reset()on search metadata, and update parsing/building helpers. - Update unit tests, samples, and localization strings to match the new SearchForm/SearchDialog APIs and naming.
Reviewed changes
Copilot reviewed 42 out of 42 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test/UnitTest/Extensions/ITableColumnExtensionsTest.cs | Updates tests for renamed SearchFormItemMetadata and metadata types. |
| test/UnitTest/Components/TableTest.cs | Updates Table tests to use new SearchFormItemMetadata/SearchItem.Metadata APIs. |
| test/UnitTest/Components/TableDialogTest.cs | Improves async assertions and adds coverage for SearchForm-based advanced search dialog behavior. |
| test/UnitTest/Components/SearchFormTest.cs | Updates to new OnChanged/Metadata APIs; adds tests for localization options and ToFilter(). |
| test/UnitTest/Components/SearchFormItemMetadataTest.cs | Renames tests/types to *Metadata and validates new metadata behavior. |
| test/UnitTest/Components/SearchDialogTest.cs | New tests for SearchDialog in UseSearchForm mode. |
| test/UnitTest/Components/InternalTableColumnTest.cs | Updates reflection-based property set to SearchFormItemMetadata. |
| test/UnitTest/Components/EditDialogTest.cs | New basic coverage for rendering EditDialog. |
| test/UnitTest/Attributes/AutoGenerateClassTest.cs | Updates attribute interface tests for renamed metadata property/type. |
| src/BootstrapBlazor/Utils/Utility.cs | Forces materialization of generated columns (ToList()). |
| src/BootstrapBlazor/Extensions/ITableColumnExtensions.cs | Renames/updates search-item building to Metadata and supports SearchFormItemMetadata. |
| src/BootstrapBlazor/Extensions/ISearchItemExtensions.cs | Adds metadata inference/building, component selection by metadata, and ToFilter() helper. |
| src/BootstrapBlazor/Extensions/DialogServiceExtensions.cs | Passes new SearchForm-related parameters into SearchDialog. |
| src/BootstrapBlazor/Components/Table/TableColumn.cs | Renames column parameter to SearchFormItemMetadata. |
| src/BootstrapBlazor/Components/Table/Table.razor.Search.cs | Adds SearchForm-driven advanced search dialog path and reset behavior for SearchItems/metadata. |
| src/BootstrapBlazor/Components/Table/Table.razor | Switches SearchForm usage to OnChanged callback. |
| src/BootstrapBlazor/Components/Table/InternalTableColumn.cs | Renames internal column metadata property and doc reference. |
| src/BootstrapBlazor/Components/Table/ITableColumn.cs | Renames interface property to SearchFormItemMetadata. |
| src/BootstrapBlazor/Components/Searches/StringSearchMetadata.cs | Renames type/base and adds Reset() implementation. |
| src/BootstrapBlazor/Components/Searches/SelectSearchMetadata.cs | Renames select metadata type to *Metadata. |
| src/BootstrapBlazor/Components/Searches/SearchMetadataBase.cs | Renames base/interface and adds abstract Reset(). |
| src/BootstrapBlazor/Components/Searches/NumberSearchMetadata.cs | Renames type/base and adds Reset() implementation. |
| src/BootstrapBlazor/Components/Searches/MultipleStringSearchMetadata.cs | Renames type/base and doc cref updates. |
| src/BootstrapBlazor/Components/Searches/MultipleSelectSearchMetadata.cs | Renames type/base and doc cref updates. |
| src/BootstrapBlazor/Components/Searches/ISearchFormItemMetadata.cs | Renames interface and adds Reset() contract. |
| src/BootstrapBlazor/Components/Searches/DateTimeSearchMetadata.cs | Renames type/base and adds Reset() implementation. |
| src/BootstrapBlazor/Components/Searches/DateTimeRangeSearchMetadata.cs | Renames type/base and adds Reset() implementation. |
| src/BootstrapBlazor/Components/Searches/CheckboxListSearchMetadata.cs | Renames type/base to *Metadata. |
| src/BootstrapBlazor/Components/SearchForm/SearchItem.cs | Renames MetaData→Metadata and adds Reset() forwarding. |
| src/BootstrapBlazor/Components/SearchForm/SearchForm.razor.cs | Reworks API to OnChanged, auto-builds metadata, and adds localization options injection. |
| src/BootstrapBlazor/Components/SearchForm/ISearchItem.cs | Renames MetaData→Metadata and adds Reset(). |
| src/BootstrapBlazor/Components/Dialog/SearchDialogOption.cs | Adds SearchForm-mode options (UseSearchForm, SearchItems, OnFilterChanged, localizer options). |
| src/BootstrapBlazor/Components/Dialog/SearchDialog.razor.cs | Adds SearchForm-mode params, button rendering reuse, and conditional Items generation. |
| src/BootstrapBlazor/Components/Dialog/SearchDialog.razor | Renders SearchForm when UseSearchForm is enabled and reuses RenderButtons. |
| src/BootstrapBlazor/Components/Dialog/EditDialog.razor.cs | Adds Model null guard and auto-populates Items when no BodyTemplate is provided. |
| src/BootstrapBlazor/Components/Dialog/DialogBase.cs | Removes Model null check; introduces helper to generate Items from model attributes. |
| src/BootstrapBlazor/Attributes/AutoGenerateColumnAttribute.cs | Renames explicit interface metadata property to SearchFormItemMetadata. |
| src/BootstrapBlazor.Server/wwwroot/css/site.css | Adds .bb-grid layout helpers used by updated sample UI. |
| src/BootstrapBlazor.Server/Locales/zh-CN.json | Updates SearchForm docs strings and adds new UI text keys. |
| src/BootstrapBlazor.Server/Locales/en-US.json | Updates SearchForm docs strings and adds new UI text keys. |
| src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor.cs | Adds sample state for toggling UseSearchForm and defines SearchItems list. |
| src/BootstrapBlazor.Server/Components/Samples/Table/TablesSearch.razor | Adds/updates SearchForm demo and UI switches, and wires UseSearchForm/SearchItems. |
Comments suppressed due to low confidence (1)
src/BootstrapBlazor/Components/Searches/MultipleSelectSearchMetadata.cs:18
- GetFilter() parses MultipleSelect values by splitting on spaces, but MultiSelect/CheckboxList values are formatted as comma-separated strings (e.g., "v1,v2"). This will cause multi-selection filters to be built incorrectly when the value contains commas. Adjust the parsing to split on ',' (and trim) to match the component serialization format.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7783 +/- ##
==========================================
Coverage 100.00% 100.00%
==========================================
Files 764 764
Lines 33960 34115 +155
Branches 4675 4696 +21
==========================================
+ Hits 33960 34115 +155
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Link issues
fixes #7782
Summary By Copilot
Regression?
Risk
Verification
Packaging changes reviewed?
☑️ Self Check before Merge
Summary by Sourcery
Add support for using the SearchForm-based advanced search dialog in tables, introduce metadata-driven search item handling with reset capabilities, and align search-related APIs and naming across components.
New Features:
Enhancements:
Documentation:
Tests: