Refactor MgGridBase filter panel & unify filter criteria
- Moved quick-filter panel below toolbar in MgGridBase for improved layout and visual alignment; FilterPanel now renders as a styled band between toolbar and group panel. - Exposed GridSizeMode on IMgGridBase and MgGridBase; MgTagBox now inherits grid density by default for consistent sizing. - Renamed FilterText to CustomFilterCriteria across grid/data source classes and tests to clarify its role as a DevExpress criteria string. - Updated documentation (README.md, MGGRID_PARAMETERS.md, GLOSSARY.md, ADR 0004) to reflect filter panel placement, parameter naming, and sizing/layout changes. - Adjusted CSS for new filter panel layout and visual integration. - Updated example usages and tests to match new API and layout.
This commit is contained in:
parent
2d62fad300
commit
1e518d9a1e
|
|
@ -110,6 +110,13 @@ public interface IMgGridBase : IGrid
|
||||||
|
|
||||||
/// <summary>Shows/hides the quick-filter panel. No-op when the grid has no filter panel.</summary>
|
/// <summary>Shows/hides the quick-filter panel. No-op when the grid has no filter panel.</summary>
|
||||||
void ToggleFilterPanel();
|
void ToggleFilterPanel();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The grid's current DevExpress <c>SizeMode</c> (from <c>DxGrid</c>). Quick-filter controls in the
|
||||||
|
/// <c>FilterPanel</c> (e.g. <see cref="MgTagBox{TData,TValue}"/>) inherit it as their default when the
|
||||||
|
/// consumer didn't set one, so the panel matches the grid's density.
|
||||||
|
/// </summary>
|
||||||
|
SizeMode? GridSizeMode { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> : DxGrid, IMgGridBase, IAsyncDisposable
|
public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> : DxGrid, IMgGridBase, IAsyncDisposable
|
||||||
|
|
@ -260,13 +267,6 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
// Body
|
// Body
|
||||||
contentBuilder.OpenElement(12, "div");
|
contentBuilder.OpenElement(12, "div");
|
||||||
contentBuilder.AddAttribute(13, "class", "mg-fullscreen-body");
|
contentBuilder.AddAttribute(13, "class", "mg-fullscreen-body");
|
||||||
if (FilterPanel != null && IsFilterPanelVisible)
|
|
||||||
{
|
|
||||||
contentBuilder.OpenElement(14, "div");
|
|
||||||
contentBuilder.AddAttribute(15, "class", "mg-grid-filter-panel");
|
|
||||||
contentBuilder.AddContent(16, FilterPanel);
|
|
||||||
contentBuilder.CloseElement();
|
|
||||||
}
|
|
||||||
base.BuildRenderTree(contentBuilder);
|
base.BuildRenderTree(contentBuilder);
|
||||||
contentBuilder.CloseElement(); // body div
|
contentBuilder.CloseElement(); // body div
|
||||||
|
|
||||||
|
|
@ -278,13 +278,6 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
contentBuilder.OpenElement(0, "div");
|
contentBuilder.OpenElement(0, "div");
|
||||||
contentBuilder.SetKey(_gridRenderKey);
|
contentBuilder.SetKey(_gridRenderKey);
|
||||||
contentBuilder.AddAttribute(1, "style", "display: contents;");
|
contentBuilder.AddAttribute(1, "style", "display: contents;");
|
||||||
if (FilterPanel != null && IsFilterPanelVisible)
|
|
||||||
{
|
|
||||||
contentBuilder.OpenElement(2, "div");
|
|
||||||
contentBuilder.AddAttribute(3, "class", "mg-grid-filter-panel");
|
|
||||||
contentBuilder.AddContent(4, FilterPanel);
|
|
||||||
contentBuilder.CloseElement();
|
|
||||||
}
|
|
||||||
base.BuildRenderTree(contentBuilder);
|
base.BuildRenderTree(contentBuilder);
|
||||||
contentBuilder.CloseElement();
|
contentBuilder.CloseElement();
|
||||||
}
|
}
|
||||||
|
|
@ -315,7 +308,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
[Parameter] public string Caption { get; set; } = typeof(TDataItem).Name;
|
[Parameter] public string Caption { get; set; } = typeof(TDataItem).Name;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Optional quick-filter controls (typically <see cref="MgTagBox{TData,TValue}"/>) rendered above the grid
|
/// Optional quick-filter controls (typically <see cref="MgTagBox{TData,TValue}"/>) rendered below the toolbar (composed into the grid's ToolbarTemplate)
|
||||||
/// inside a styled <c>.mg-grid-filter-panel</c> container (the slot wraps the content). Within this grid's <see cref="IMgGridBase"/>
|
/// inside a styled <c>.mg-grid-filter-panel</c> container (the slot wraps the content). Within this grid's <see cref="IMgGridBase"/>
|
||||||
/// cascade, so the controls can reach this grid and apply per-field filters. See ADR 0004.
|
/// cascade, so the controls can reach this grid and apply per-field filters. See ADR 0004.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -337,6 +330,56 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
InvokeAsync(StateHasChanged);
|
InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public SizeMode? GridSizeMode => this.SizeMode;
|
||||||
|
|
||||||
|
private RenderFragment<GridToolbarTemplateContext>? _consumerToolbarTemplate;
|
||||||
|
private RenderFragment<GridToolbarTemplateContext>? _wrappedToolbarTemplate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the <see cref="FilterPanel"/> BELOW the toolbar by composing it into the grid's ToolbarTemplate:
|
||||||
|
/// the consumer's toolbar first, then the styled <c>.mg-grid-filter-panel</c> container. Re-wraps whenever the
|
||||||
|
/// consumer supplies a new template instance (Blazor re-applies markup parameters on every SetParametersAsync);
|
||||||
|
/// no-op when the incoming template is already our wrapper. <see cref="IsFilterPanelVisible"/> is read at render
|
||||||
|
/// time inside the fragment, so the toolbar toggle works without re-wrapping.
|
||||||
|
/// COMPILE-VERIFY: ToolbarTemplate is RenderFragment<GridToolbarTemplateContext> on this DevExpress version.
|
||||||
|
/// </summary>
|
||||||
|
private void WrapToolbarTemplateWithFilterPanel()
|
||||||
|
{
|
||||||
|
if (FilterPanel == null) return;
|
||||||
|
if (ReferenceEquals(ToolbarTemplate, _wrappedToolbarTemplate)) return;
|
||||||
|
|
||||||
|
_consumerToolbarTemplate = ToolbarTemplate;
|
||||||
|
|
||||||
|
_wrappedToolbarTemplate = context => builder =>
|
||||||
|
{
|
||||||
|
// Wrap toolbar + filter panel in a vertical (column) container so the panel renders BELOW the toolbar,
|
||||||
|
// not beside it — the grid's toolbar region lays its children out horizontally otherwise.
|
||||||
|
builder.OpenElement(0, "div");
|
||||||
|
builder.AddAttribute(1, "class", "mg-grid-toolbar-with-filter");
|
||||||
|
|
||||||
|
// Toolbar row carries the toolbar-container's native padding tokens (the container's own padding is
|
||||||
|
// zeroed by CSS when it hosts this wrapper) so the toolbar keeps its exact native look while the
|
||||||
|
// filter band below can span the grid's full width. See .mg-grid-toolbar-row in mg-grid-info-panel.css.
|
||||||
|
builder.OpenElement(2, "div");
|
||||||
|
builder.AddAttribute(3, "class", "mg-grid-toolbar-row");
|
||||||
|
if (_consumerToolbarTemplate != null) builder.AddContent(4, _consumerToolbarTemplate(context));
|
||||||
|
builder.CloseElement();
|
||||||
|
|
||||||
|
if (IsFilterPanelVisible)
|
||||||
|
{
|
||||||
|
builder.OpenElement(5, "div");
|
||||||
|
builder.AddAttribute(6, "class", "mg-grid-filter-panel");
|
||||||
|
builder.AddContent(7, FilterPanel);
|
||||||
|
builder.CloseElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.CloseElement();
|
||||||
|
};
|
||||||
|
|
||||||
|
ToolbarTemplate = _wrappedToolbarTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Name for auto-saving/loading grid layout. If not set, defaults to "Grid{TDataItem.Name}"
|
/// Name for auto-saving/loading grid layout. If not set, defaults to "Grid{TDataItem.Name}"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -345,19 +388,25 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
public bool IsMasterGrid => ParentDataItem == null;
|
public bool IsMasterGrid => ParentDataItem == null;
|
||||||
protected PropertyInfo? KeyFieldPropertyInfoToParent;
|
protected PropertyInfo? KeyFieldPropertyInfoToParent;
|
||||||
|
|
||||||
private string? _filterText = null;
|
private string? _customFilterCriteria = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Consumer-supplied base filter criteria (a DevExpress CriteriaOperator string) — propagated to the data source
|
||||||
|
/// (appended as the LAST positional GetAll parameter) and triggers a reload; the server parses + whitelists it.
|
||||||
|
/// Renamed FilterText → CustomFilterText → CustomFilterCriteria (2026-07-16) as the semantics settled to a
|
||||||
|
/// criteria string. Under the ADR-0004 unified model it is AND-merged with the grid's automatic FilterCriteria.
|
||||||
|
/// </summary>
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string? FilterText
|
public string? CustomFilterCriteria
|
||||||
{
|
{
|
||||||
get => _filterText;
|
get => _customFilterCriteria;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_filterText = value;
|
_customFilterCriteria = value;
|
||||||
|
|
||||||
if (_dataSource != null && _dataSource.FilterText != value)
|
if (_dataSource != null && _dataSource.CustomFilterCriteria != value)
|
||||||
{
|
{
|
||||||
_dataSource.FilterText = value;
|
_dataSource.CustomFilterCriteria = value;
|
||||||
ReloadDataSourceAsync().Forget(Logger);
|
ReloadDataSourceAsync().Forget(Logger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -454,7 +503,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
var crudTags = new SignalRCrudTags(GetAllMessageTag, GetItemMessageTag, AddMessageTag, UpdateMessageTag, RemoveMessageTag);
|
var crudTags = new SignalRCrudTags(GetAllMessageTag, GetItemMessageTag, AddMessageTag, UpdateMessageTag, RemoveMessageTag);
|
||||||
|
|
||||||
_dataSource = (TSignalRDataSource)Activator.CreateInstance(typeof(TSignalRDataSource), SignalRClient, crudTags, Logger, ContextIds)!;
|
_dataSource = (TSignalRDataSource)Activator.CreateInstance(typeof(TSignalRDataSource), SignalRClient, crudTags, Logger, ContextIds)!;
|
||||||
_dataSource.FilterText = FilterText;
|
_dataSource.CustomFilterCriteria = CustomFilterCriteria;
|
||||||
|
|
||||||
SetGridData(_dataSource.GetReferenceInnerList());
|
SetGridData(_dataSource.GetReferenceInnerList());
|
||||||
|
|
||||||
|
|
@ -772,6 +821,8 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
{
|
{
|
||||||
await base.SetParametersAsyncCore(parameters);
|
await base.SetParametersAsyncCore(parameters);
|
||||||
|
|
||||||
|
WrapToolbarTemplateWithFilterPanel();
|
||||||
|
|
||||||
if (!IsFirstInitializeParameterCore)
|
if (!IsFirstInitializeParameterCore)
|
||||||
{
|
{
|
||||||
//if (typeof(TDataItem) is IId<TId> || typeof(TDataItem) is IId<TId>)
|
//if (typeof(TDataItem) is IId<TId> || typeof(TDataItem) is IId<TId>)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Core grid system built on DevExpress `DxGrid`. For the full technical reference
|
||||||
|
|
||||||
## Key Files
|
## Key Files
|
||||||
|
|
||||||
- **`MgGridBase.cs`** — `MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient>`, the main abstract grid component. Extends `DxGrid` with SignalR CRUD, layout persistence, master-detail hierarchy, edit state tracking, fullscreen toggle, and a quick-filter `FilterPanel` slot (renders `MgTagBox` controls above the grid in a styled `.mg-grid-filter-panel` container; toolbar Show/Hide toggle).
|
- **`MgGridBase.cs`** — `MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient>`, the main abstract grid component. Extends `DxGrid` with SignalR CRUD, layout persistence, master-detail hierarchy, edit state tracking, fullscreen toggle, and a quick-filter `FilterPanel` slot (renders `MgTagBox` controls below the toolbar in a styled `.mg-grid-filter-panel` container; toolbar Show/Hide toggle).
|
||||||
- **`MgGridWithInfoPanel.razor`** — `DxSplitter` wrapper: grid (left) + InfoPanel (right), fullscreen overlay, splitter size persistence.
|
- **`MgGridWithInfoPanel.razor`** — `DxSplitter` wrapper: grid (left) + InfoPanel (right), fullscreen overlay, splitter size persistence.
|
||||||
- **`MgGridToolbarBase.cs`** — Extends `DxToolbar` with `Grid`, `RefreshClick`, and `ShowOnlyIcon` parameters.
|
- **`MgGridToolbarBase.cs`** — Extends `DxToolbar` with `Grid`, `RefreshClick`, and `ShowOnlyIcon` parameters.
|
||||||
- **`MgGridToolbarTemplate.razor`** — Full toolbar template: New/Edit/Delete/Save/Cancel, row navigation, layout menu (Load/Save/Reset), column chooser, export, reload, filter-panel toggle (visible only when the grid has a `FilterPanel`), fullscreen. Extensible via `ToolbarItemsExtended`.
|
- **`MgGridToolbarTemplate.razor`** — Full toolbar template: New/Edit/Delete/Save/Cancel, row navigation, layout menu (Load/Save/Reset), column chooser, export, reload, filter-panel toggle (visible only when the grid has a `FilterPanel`), fullscreen. Extensible via `ToolbarItemsExtended`.
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,31 @@ public class MgTagBox<TData, TValue> : DxTagBox<TData, TValue>
|
||||||
// Route the tag box's selection through our handler so a change applies the grid filter.
|
// Route the tag box's selection through our handler so a change applies the grid filter.
|
||||||
// (The consumer does not bind Values; the selection is owned internally — see OnSelectionChanged.)
|
// (The consumer does not bind Values; the selection is owned internally — see OnSelectionChanged.)
|
||||||
base.ValuesChanged = EventCallback.Factory.Create<IEnumerable<TValue>>(this, OnSelectionChanged);
|
base.ValuesChanged = EventCallback.Factory.Create<IEnumerable<TValue>>(this, OnSelectionChanged);
|
||||||
|
|
||||||
|
InheritSizeModeFromGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defaults <c>SizeMode</c> to the grid's (via the <see cref="Grid"/> cascade) so the filter panel matches the
|
||||||
|
/// grid's density — but only when the consumer left it unset (<c>null</c>); an explicit consumer value wins.
|
||||||
|
/// Once inherited, <c>SizeMode</c> is non-null, so this defaults once rather than overriding every render.
|
||||||
|
/// Imperative parameter writes on a DevExpress editor must be wrapped in <c>BeginUpdate</c>/<c>EndUpdate</c>
|
||||||
|
/// (same reason as <c>Values</c> in <see cref="OnSelectionChanged"/>).
|
||||||
|
/// </summary>
|
||||||
|
private void InheritSizeModeFromGrid()
|
||||||
|
{
|
||||||
|
// SizeMode != null => the consumer set it (or we already inherited it) => leave it alone.
|
||||||
|
if (this.SizeMode is not null || Grid?.GridSizeMode is not { } gridSizeMode) return;
|
||||||
|
|
||||||
|
BeginUpdate();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.SizeMode = gridSizeMode;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
EndUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSelectionChanged(IEnumerable<TValue>? values)
|
private void OnSelectionChanged(IEnumerable<TValue>? values)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
├── Validate Logger, SignalRClient (throw if null)
|
├── Validate Logger, SignalRClient (throw if null)
|
||||||
├── Create SignalRCrudTags from message tag parameters
|
├── Create SignalRCrudTags from message tag parameters
|
||||||
├── Create TSignalRDataSource via Activator.CreateInstance(SignalRClient, crudTags, ContextIds)
|
├── Create TSignalRDataSource via Activator.CreateInstance(SignalRClient, crudTags, ContextIds)
|
||||||
├── Set DataSource.FilterText
|
├── Set DataSource.CustomFilterCriteria
|
||||||
├── Bind grid Data to data source inner list
|
├── Bind grid Data to data source inner list
|
||||||
└── Subscribe to: OnDataSourceLoaded, OnDataSourceItemChanged, OnSyncingStateChanged
|
└── Subscribe to: OnDataSourceLoaded, OnDataSourceItemChanged, OnSyncingStateChanged
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -14,7 +14,7 @@
|
||||||
- **InfoPanel integration** — side panel shows focused row details, supports edit mode
|
- **InfoPanel integration** — side panel shows focused row details, supports edit mode
|
||||||
- **Fullscreen mode** — standalone overlay or via `MgGridWithInfoPanel` wrapper
|
- **Fullscreen mode** — standalone overlay or via `MgGridWithInfoPanel` wrapper
|
||||||
- **Change tracking** — client-side tracking with server sync via `SaveChangesAsync`
|
- **Change tracking** — client-side tracking with server sync via `SaveChangesAsync`
|
||||||
- **Quick-filter panel** — `FilterPanel` slot above the grid hosting `MgTagBox` controls; per-field criteria AND-combined by DevExpress with the user's filter row; toolbar Show/Hide toggle (see `MGGRID_PARAMETERS.md` → Quick-Filter Panel; design: AyCode.Core repo `docs/adr/0004-signalr-datasource-server-side-filtering.md`)
|
- **Quick-filter panel** — `FilterPanel` slot below the toolbar hosting `MgTagBox` controls; per-field criteria AND-combined by DevExpress with the user's filter row; toolbar Show/Hide toggle (see `MGGRID_PARAMETERS.md` → Quick-Filter Panel; design: AyCode.Core repo `docs/adr/0004-signalr-datasource-server-side-filtering.md`)
|
||||||
|
|
||||||
## Detailed Documentation
|
## Detailed Documentation
|
||||||
|
|
||||||
|
|
@ -101,6 +101,7 @@ The public contract exposed to companion components (toolbar, InfoPanel, wrapper
|
||||||
| `HasFilterPanel` | `bool` | Whether the grid has a `FilterPanel` (drives toolbar-toggle visibility) |
|
| `HasFilterPanel` | `bool` | Whether the grid has a `FilterPanel` (drives toolbar-toggle visibility) |
|
||||||
| `IsFilterPanelVisible` | `bool` | Current quick-filter panel visibility |
|
| `IsFilterPanelVisible` | `bool` | Current quick-filter panel visibility |
|
||||||
| `ToggleFilterPanel()` | `void` | Show/hide the quick-filter panel |
|
| `ToggleFilterPanel()` | `void` | Show/hide the quick-filter panel |
|
||||||
|
| `GridSizeMode` | `SizeMode?` | The grid's DevExpress `SizeMode`; `MgTagBox` in the `FilterPanel` inherits it as its default when the consumer sets none |
|
||||||
| `SaveUserLayoutAsync()` | `Task` | Save layout manually |
|
| `SaveUserLayoutAsync()` | `Task` | Save layout manually |
|
||||||
| `LoadUserLayoutAsync()` | `Task` | Load manually saved layout |
|
| `LoadUserLayoutAsync()` | `Task` | Load manually saved layout |
|
||||||
| `ResetLayoutAsync()` | `Task` | Reset to default layout |
|
| `ResetLayoutAsync()` | `Task` | Reset to default layout |
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,51 @@
|
||||||
These are defined on .dxbl-theme-fluent class and inherited by child elements.
|
These are defined on .dxbl-theme-fluent class and inherited by child elements.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Grid quick-filter panel (rendered above the grid; hosts MgTagBox quick-filter controls) */
|
/* Grid toolbar + quick-filter column: stacks the filter panel BELOW the toolbar
|
||||||
|
(the grid's toolbar region lays its children out horizontally otherwise) */
|
||||||
|
.mg-grid-toolbar-with-filter {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grid quick-filter panel — composed into the grid's ToolbarTemplate, so it physically lives INSIDE
|
||||||
|
.dxbl-grid-toolbar-container. Styled to read as its OWN band below the toolbar, aligned with the native
|
||||||
|
group panel ("Drag a column header...") band: it reuses the grid's OWN group-panel padding tokens, which
|
||||||
|
the grid root redefines per SizeMode — so the band's left edge and height track the group panel exactly
|
||||||
|
at every grid size. Separator above = our border-top (grid border tokens); separator below = the toolbar
|
||||||
|
container's own native border-bottom (its padding is zeroed below, so that border sits directly under
|
||||||
|
this band — one line, no double border, no extra gap). */
|
||||||
.mg-grid-filter-panel {
|
.mg-grid-filter-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--DS-sizing-80, 0.5rem);
|
gap: var(--DS-sizing-80, 0.5rem);
|
||||||
padding: var(--DS-sizing-80, 0.5rem) var(--DS-sizing-160, 1rem);
|
padding: var(--dxbl-grid-group-panel-container-padding-y, 0.5rem) var(--dxbl-grid-group-panel-container-padding-x, 0.75rem);
|
||||||
background-color: var(--DS-color-surface-neutral-subdued-rest, #f8f9fa);
|
border-top: var(--dxbl-grid-border-width, 1px) var(--dxbl-grid-border-style, solid) var(--dxbl-grid-border-color, #dee2e6);
|
||||||
border: 1px solid var(--DS-color-border-neutral-default-rest, #dee2e6);
|
}
|
||||||
border-bottom: none;
|
|
||||||
|
/* The toolbar container's own padding wrapped BOTH rows (toolbar + filter band), which broke the band
|
||||||
|
illusion: the band's separators stopped short of the grid's edges, and below the band the container's
|
||||||
|
leftover bottom padding + its native border produced a second line and an asymmetric gap. Zero the
|
||||||
|
container's padding when it hosts our stacked wrapper; each row then supplies its own padding. */
|
||||||
|
.dxbl-grid-toolbar-container:has(.mg-grid-toolbar-with-filter) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toolbar row inside the wrapper: carries the toolbar-container's native padding tokens (SizeMode-adaptive),
|
||||||
|
so the toolbar itself looks exactly as it does without a filter panel. */
|
||||||
|
.mg-grid-toolbar-row {
|
||||||
|
padding: var(--dxbl-grid-toolbar-container-padding-y, 0.5rem) var(--dxbl-grid-toolbar-container-padding-x, 0.75rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quick-filter controls: sit side by side and share the row width; wrap to the next line only when
|
||||||
|
they can't fit. Overrides the editors' default (near) full-width so they don't stack one-per-row.
|
||||||
|
flex-basis / min / max are tunable if a different density is wanted. */
|
||||||
|
.mg-grid-filter-panel > * {
|
||||||
|
flex: 1 1 240px;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 480px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Main panel - uses DevExpress Design System variables */
|
/* Main panel - uses DevExpress Design System variables */
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,16 @@ For full technical reference see `AyCode.Blazor.Components/docs/MGGRID/README.md
|
||||||
|
|
||||||
| Term | Definition |
|
| Term | Definition |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **MgGridBase** | Abstract generic grid component extending `DxGrid` with SignalR CRUD, layout persistence, master-detail, InfoPanel, fullscreen. |
|
| **MgGridBase** | Abstract generic grid component extending `DxGrid` with SignalR CRUD, layout persistence, master-detail, InfoPanel, fullscreen, and a quick-filter `FilterPanel` slot. |
|
||||||
|
| **FilterPanel** | `MgGridBase` `RenderFragment` slot for quick-filter controls (`MgTagBox`); rendered below the toolbar as its own full-bleed band (styled like the native group panel) in a `.mg-grid-filter-panel` container, with a toolbar Show/Hide toggle. |
|
||||||
|
| **MgTagBox** | `DxTagBox` seam with a `FieldName` — a selection applies a per-field `IN` criteria to the grid via `IMgGridBase.SetFieldQuickFilter` (DevExpress `SetFieldFilterCriteria`); used inside `FilterPanel`. Defaults its `SizeMode` to the grid's when unset; bind to lookup objects (`ValueFieldName`/`TextFieldName`) to filter an FK column by name. |
|
||||||
| **MgGridWithInfoPanel** | `DxSplitter` wrapper: grid (left pane) + InfoPanel (right pane), fullscreen overlay, splitter size persistence. |
|
| **MgGridWithInfoPanel** | `DxSplitter` wrapper: grid (left pane) + InfoPanel (right pane), fullscreen overlay, splitter size persistence. |
|
||||||
| **MgGridToolbarBase** | `DxToolbar` base with `Grid` (`IMgGridBase`) reference and `RefreshClick` callback. |
|
| **MgGridToolbarBase** | `DxToolbar` base with `Grid` (`IMgGridBase`) reference and `RefreshClick` callback. |
|
||||||
| **MgGridToolbarTemplate** | Full toolbar: New/Edit/Delete/Save/Cancel, row navigation, layout menu, export, fullscreen. Extensible via `ToolbarItemsExtended`. |
|
| **MgGridToolbarTemplate** | Full toolbar: New/Edit/Delete/Save/Cancel, row navigation, layout menu, export, filter-panel toggle (when the grid has a `FilterPanel`), fullscreen. Extensible via `ToolbarItemsExtended`. |
|
||||||
| **MgGridDataColumn** | Extended `DxGridDataColumn` with InfoPanel parameters and `UrlLink` template (`{Property}` placeholders). |
|
| **MgGridDataColumn** | Extended `DxGridDataColumn` with InfoPanel parameters and `UrlLink` template (`{Property}` placeholders). |
|
||||||
| **MgGridInfoPanel** | Default InfoPanel: column-value pairs for focused row, responsive columns, edit mode with typed editors, template system. |
|
| **MgGridInfoPanel** | Default InfoPanel: column-value pairs for focused row, responsive columns, edit mode with typed editors, template system. |
|
||||||
| **MgGridSignalRDataSource** | `GridCustomDataSource` wrapping `AcSignalRDataSource`. Local cache, background refresh. |
|
| **MgGridSignalRDataSource** | `GridCustomDataSource` wrapping `AcSignalRDataSource`. Local cache, background refresh. |
|
||||||
| **IMgGridBase** | Public interface: `IsSyncing`, `GridEditState`, `ParentGrid`, `StepPrevRow/NextRow`, layout persistence methods. |
|
| **IMgGridBase** | Public interface: `IsSyncing`, `GridEditState`, `ParentGrid`, `StepPrevRow/NextRow`, layout persistence, quick-filter (`SetFieldQuickFilter`, `HasFilterPanel`, `IsFilterPanelVisible`, `ToggleFilterPanel`), `GridSizeMode`. |
|
||||||
| **MgGridEditState** | Enum: `None` (no edit), `New` (adding item), `Edit` (modifying item). |
|
| **MgGridEditState** | Enum: `None` (no edit), `New` (adding item), `Edit` (modifying item). |
|
||||||
| **SignalRCrudTags** | Bundle of 5 integer message tags (GetAll, GetItem, Add, Update, Remove) for one entity type. See `AyCode.Services/docs/SIGNALR_DATASOURCE/README.md` in AyCode.Core repo. |
|
| **SignalRCrudTags** | Bundle of 5 integer message tags (GetAll, GetItem, Add, Update, Remove) for one entity type. See `AyCode.Services/docs/SIGNALR_DATASOURCE/README.md` in AyCode.Core repo. |
|
||||||
| **IsMasterGrid** | `true` when `ParentDataItem == null` — top-level grid (not detail). |
|
| **IsMasterGrid** | `true` when `ParentDataItem == null` — top-level grid (not detail). |
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue