13 KiB
MgGrid — Parameters & Events
Part of the MgGrid system. See
README.mdfor overview and component hierarchy.
Required Parameters
| Parameter | Type | Description |
|---|---|---|
SignalRClient |
AcSignalRClientBase |
SignalR client for server communication |
Logger |
TLoggerClient |
Logger instance |
GetAllMessageTag |
int |
SignalR tag for loading all items |
CRUD Message Tags
| Parameter | Type | Description |
|---|---|---|
GetAllMessageTag |
int |
Tag for "get all items" request |
GetItemMessageTag |
int |
Tag for "get single item" request |
AddMessageTag |
int |
Tag for "add item" request |
UpdateMessageTag |
int |
Tag for "update item" request |
RemoveMessageTag |
int |
Tag for "remove item" request |
These are bundled into a SignalRCrudTags during OnInitializedAsync. See AyCode.Services/docs/SIGNALR_DATASOURCE/README.md (in AyCode.Core repo) for details.
Data & Context Parameters
| Parameter | Type | Description |
|---|---|---|
DataSource |
IList<TDataItem> |
Bind with AcObservableCollection<TDataItem> for external data. If not set, grid creates its own. |
ParentDataItem |
IId<TId>? |
Parent entity for detail grids. null = master grid. |
KeyFieldNameToParentId |
string? |
Property name on TDataItem that references the parent's ID |
ContextIds |
object[]? |
Additional context passed to AcSignalRDataSource constructor |
CustomFilterCriteria |
string? |
Consumer-supplied base filter criteria (a DevExpress criteria string) — propagated to the data source, triggers reload; server parses + whitelists it. AND-merged with the grid's automatic FilterCriteria (ADR-0004 unified model). Renamed FilterText → CustomFilterText → CustomFilterCriteria (2026-07-16). |
Display & Behavior Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
Caption |
string |
typeof(TDataItem).Name |
Grid title (shown in fullscreen header) |
GridName |
string |
"{TDataItem.Name}Grid" |
Name used in log messages |
AutoSaveLayoutName |
string? |
"Grid{TDataItem.Name}" |
Base name for layout storage keys |
EnableServerLayouts |
bool |
true |
Roams the UserSave layout tier via the SignalR server endpoint (see MGGRID_LAYOUT.md → Server Roaming). Server calls happen only on the Save/Load layout buttons. |
Filter Panel
| Parameter | Type | Default | Description |
|---|---|---|---|
FilterPanel |
RenderFragment? |
null |
Filter controls rendered below the toolbar (composed into the grid's ToolbarTemplate); the slot wraps them in a styled .mg-grid-filter-panel container with a built-in right-aligned Clear button. When set, the toolbar shows a Show/Hide Filters toggle (MGGRID_TOOLBAR.md). |
ShowFilterPanel |
bool |
true |
Initial visibility of the filter panel; the toolbar toggle flips it at runtime (IsFilterPanelVisible / ToggleFilterPanel). |
FilterPanelClearButtonText |
string |
"Clear Filters" |
Text of the panel's built-in Clear button (IMgGridBase.ClearFilterPanel — clears the panel's own filters; the filter row is untouched). Enabled only while a panel-owned criteria is active (HasActiveFilterPanelCriteria) — enabled + empty-looking controls = an invisible panel filter, surfaced on purpose. |
Place MgGridFilterPanelTagBox components (from Components/Grids/) in the slot. Each applies a per-field IN(FieldName, selected...) criteria via IMgGridBase.SetFilterPanelCriteria → DevExpress SetFieldFilterCriteria; DevExpress AND-combines the per-field criteria with each other and with the user's filter row automatically (no clobber). Empty selection clears that field's criteria. Several components in one panel therefore narrow the grid cumulatively (field A and field B).
Seam split:
MgTagBoxitself is the pure, behavior-freeDxTagBoxseam (universal — usable anywhere). All grid wiring lives in the derivedMgGridFilterPanelTagBox : MgTagBox, IMgGridFilterPanelComponent. Future filter components (dropdown, textbox, checkbox…) follow the same pattern: own Mg* seam +IMgGridFilterPanelComponent; when the second one lands, the shared glue (registration + criteria adapter) is to be extracted into a helper.
Ownership (shared per-field slot). DevExpress keeps ONE criteria slot per field — the filter row and the panel both write it (SetFieldFilterCriteria). The panel therefore only manages criteria its components recognize as their own shape (IMgGridFilterPanelComponent.OwnsCriteria — for the tag box: the IN(FieldName, ...) it writes). A value typed into the column's filter row (e.g. Contains("EUR")) is never cleared by the panel — not on hide, not by the Clear button; the tag box simply shows no selection for it (it is visible in the filter row, so it is not invisible filtering).
<FilterPanel>
<MgGridFilterPanelTagBox TData="string" TValue="string" FieldName="Status"
Data="@_statusOptions" NullText="All statuses" />
</FilterPanel>
Lookup (foreign-key) columns. When the target column holds an ID (e.g. PartnerId) but users pick by name, bind the tag box to the lookup objects and let ValueFieldName supply the IDs. FieldName still names the grid column, so the emitted IN(PartnerId, 3, 7) matches the underlying data field — not the cell's display template:
<FilterPanel>
<MgGridFilterPanelTagBox TData="Partner" TValue="int" FieldName="PartnerId"
Data="@Partners" ValueFieldName="Id" TextFieldName="Name"
NullText="All partners" />
</FilterPanel>
A nullable FK column (int?) behaves the same — rows whose value is null match no selection.
Sizing. MgGridFilterPanelTagBox defaults its SizeMode to the grid's (IMgGridBase.GridSizeMode) when the consumer leaves it unset, so the panel matches the grid's density with no per-component declaration. An explicit SizeMode always wins.
Layout. The panel is a flex row: controls sit side by side sharing the width and wrap to the next line only when they no longer fit (.mg-grid-filter-panel > * — flex / min-width / max-width in mg-grid-info-panel.css; tune there for a different density). It is styled to read as its own band between the toolbar and the group panel, aligned with the native group panel via the grid's own SizeMode-adaptive tokens (--dxbl-grid-group-panel-container-padding-*). Since it is composed into the ToolbarTemplate it physically lives inside .dxbl-grid-toolbar-container; that container's padding is zeroed (:has(.mg-grid-toolbar-with-filter)) and each row supplies its own: the toolbar row (.mg-grid-toolbar-row) reuses the container's native padding tokens, the band reuses the group panel's. Separator above = the band's border-top; below = the container's native border-bottom. A consumer .hide-toolbar class (detail grids) therefore hides the panel together with the toolbar.
Persistence & visibility. The panel's criteria is part of the grid's persisted layout (GridPersistentLayout.FilterCriteria — the AutoSave tier and the server-roamed UserSave tier alike; see MGGRID_LAYOUT.md). Components register with the grid (IMgGridFilterPanelComponent, weakly held; MgGridFilterPanelTagBox does this automatically) and the grid reconciles them after layout restore (incl. on another machine), user-layout load, layout reset, and panel toggle:
- panel visible → each component re-reads its field's criteria and re-selects (a restored panel filter is visible again). See Late lookup data below for why this is more than a re-render;
- panel hidden (toolbar toggle or
ShowFilterPanel="false") → the components' own criteria are cleared — a panel filter never filters invisibly. Hiding the panel therefore deliberately discards the current selection (filter-row values survive, per Ownership above); - a per-field criteria the component does not own is left untouched — the component just shows no selection.
The panel content is always rendered (CSS-hidden when toggled off) so the components stay registered. Known edge — a layout with panel criteria loaded where the grid has no FilterPanel at all: MGGRID_ISSUES.md → ACBLAZOR-GRID-I-D4T7.
Late lookup data (why restore is not just a re-render). A restore has two inputs arriving in arbitrary order: the criteria (async layout restore) and the lookup Data (async load). DevExpress snapshots the editor's item list and detects Data changes by REFERENCE only — so the common consumer shape of binding a pre-created empty collection that is later filled in place (AcObservableCollection.Replace, unchanged reference) leaves the editor holding an empty list, and asserted Values can never resolve into tags no matter how often the component re-renders.
MgGridFilterPanelTagBox therefore tracks a level-triggered fact — "this editor's snapshot was taken while Data was empty" — armed on every parameter pass regardless of criteria presence, and cleared only once it has healed: DxListEditorBase.Reload() (forcing a re-read of Data) plus a fresh-instance Values assert, run from both the parameter pass and SyncFromGrid, followed by one deferred post-render re-assert (Reload() carries no documented settle guarantee). Consumers may additionally force a reconcile via IMgGridBase.RefreshFilterPanel() after a late lookup load.
Why level-triggered: two earlier edge-triggered designs (detect the
Datareference change; then reference or item-count change) each looked correct and each shipped a bug: an edge detector needs an arming pass that records the pre-fill state, so the last-filled lookup on a page — whose fill lands after its final parameter pass — was systematically stranded (rows filtered,Clearenabled, chips empty). Do not reintroduce an edge gate here. The v2 layout envelope removes the need for this heal entirely (see ADR 0004 → planned v2).
ValuesChanged is not only the user — safe-order restore. The editor also raises ValuesChanged for the echo of a programmatic Values write and for an internal revalidation drop (console-proven: ValuesChanged([]) after a heal, via a JS-interop hop whose latency is unbounded in render ticks — a windowed callback guard shipped and was refuted the same day). Unhandled, the drop was misread as a user edit and cleared the restored criteria, which the AutoSave then persisted (saved filter permanently lost). The fix removes the drop's precondition instead of filtering callbacks:
- no value ever enters an empty editor — while the lookup
Datais empty, nothing is asserted; the criteria stays untouched (rows filter, the Clear button signals it); - the heal
Reload()s the empty editor — nothing to drop ⇒ no spurious callback at all; - one delayed assert (~750 ms > the measured ~300 ms hop; re-reads the criteria at fire time) selects into the settled editor — its echo rewrites the same criteria (harmless);
- ownership-guarded clearing: an empty selection only clears criteria the component OWNS — a filter-row value can never be wiped by an empty callback.
Every ambiguous path resolves toward clearing (never an invisible filter); the v2 envelope removes the heal + delay entirely (ADR 0004 → planned v2).
Implementation note: imperative parameter writes inside DevExpress editors (Values, SizeMode) must be wrapped in BeginUpdate()/EndUpdate() (the parameter tracker throws otherwise) — MgGridFilterPanelTagBox handles this internally.
Design + the server-side forward path (FilterMode.Server): AyCode.Core repo docs/adr/0004-signalr-datasource-server-side-filtering.md.
Event Callbacks
All grid events are re-exposed with OnGrid prefix to avoid collisions with DxGrid base events:
| Event | DxGrid Equivalent | When Fired |
|---|---|---|
OnGridItemDeleting |
DataItemDeleting |
Before item removal (can cancel via e.Cancel) |
OnGridEditModelSaving |
EditModelSaving |
Before item save (can cancel via e.Cancel) |
OnGridEditStart |
EditStart |
When edit mode begins |
OnGridCustomizeEditModel |
CustomizeEditModel |
When edit model is being prepared |
OnGridFocusedRowChanged |
FocusedRowChanged |
When focused row changes |
OnDataSourceChanged |
— | After data source is loaded/reloaded |
OnGridItemChanged |
— | After server confirms a CRUD operation |
OnGridItemChanging |
— | Before a CRUD operation is sent to server |
Internal event wiring (in SetParametersAsyncCore, first call):
| DxGrid Event | → Internal Handler |
|---|---|
DataItemDeleting |
OnItemDeleting |
EditModelSaving |
OnItemSaving |
CustomizeEditModel |
OnCustomizeEditModel |
FocusedRowChanged |
OnFocusedRowChanged |
EditStart |
OnEditStart |
EditCanceling |
OnEditCanceling |
Default Grid Settings
Set in SetParametersAsyncCore (first call only):
| Setting | Value |
|---|---|
KeyFieldName |
"Id" |
TextWrapEnabled |
false |
AllowSelectRowByClick |
true |
HighlightRowOnHover |
true |
AutoCollapseDetailRow |
true |
AutoExpandAllGroupRows |
false |
Project adapters typically add more defaults in OnParametersSet (e.g., EditMode, FocusedRowEnabled, PageSize, ShowFilterRow, SizeMode based on IsMasterGrid).