Expand and clarify MgGrid system documentation

Major overhaul of MgGrid-related documentation:
- README.md: Reference full technical docs, update key files list, add new components.
- ARCHITECTURE.md: Add MgGridToolbarTemplate, MgGridSignalRDataSource; clarify component roles.
- GLOSSARY.md: Add comprehensive MgGrid System section with cross-references.
- MGGRID.md: Rewrite overview, hierarchy, CRUD, event wiring, lifecycle, layout, InfoPanel, toolbar, and data column sections for accuracy and completeness.
- copilot-instructions.md: Refine .md sync convention, add documentation layering rule.
- CONVENTIONS.md: Remove redundant code reuse section.

Brings docs in sync with codebase and improves clarity for developers.
This commit is contained in:
Loretta 2026-03-29 18:29:24 +02:00
parent 52131fdf25
commit 453e21a844
6 changed files with 234 additions and 105 deletions

View File

@ -27,4 +27,5 @@
10. Do not suggest removal/rollback as a solution — find a fix for the problem.
11. All DLL references to AyCode.Core projects are via `DllReference` (not ProjectReference) — this is intentional.
12. **No redundant code** — before writing new logic, search for existing methods. Reuse or extract shared logic into smaller methods rather than duplicating.
13. **Keep all .md files in sync** — when you modify code, update any affected .md file (README.md, docs/, GLOSSARY, ARCHITECTURE, CONVENTIONS, etc.). If you notice any .md content does not match the current code, fix it automatically.
13. **Keep all .md files in sync** — when you modify code, update any affected .md file in the same area. If you already read an .md file during your work and notice it contradicts the current code, fix the discrepancy — but do NOT proactively scan or open .md files just to check for issues.
16. **Documentation layering** — write `.md` documentation at the **defining layer** (where the code lives). Higher-layer `.md` files reference the base docs (e.g. `see AyCode.Core/docs/SIGNALR.md`) and document only project-specific overrides or extensions. Never duplicate base-layer descriptions in consumer-level docs.

View File

@ -1,15 +1,15 @@
# Grids
Core grid system built on DevExpress `DxGrid` with SignalR-based data binding, CRUD operations, layout persistence, fullscreen mode, and an info panel for row details.
Core grid system built on DevExpress `DxGrid`. For the full technical reference see `AyCode.Blazor/docs/MGGRID.md`.
## Key Files
- **`MgGridBase.cs`** -- `MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient>`, the main abstract grid component. Extends `DxGrid` and binds to an `AcSignalRDataSource` for real-time CRUD via SignalR message tags. Supports master-detail hierarchy (`ParentGrid`, `GetRootGrid`), row navigation, edit state tracking, layout auto-save/load/reset via localStorage, and fullscreen toggle.
- **`MgGridSignalRDataSource.cs`** -- `MgGridSignalRDataSource<TDataItem, TId>`, a `GridCustomDataSource` that wraps `AcSignalRDataSource`. Returns local data instantly for previously-seen filter criteria while refreshing from the server in the background. Handles filter, sort, paging, unique values, and summary calculations locally.
- **`MgGridDataColumn.cs`** -- Extends `DxGridDataColumn` with InfoPanel visibility/order parameters and a `UrlLink` template that renders cells as hyperlinks with `{Property}` placeholder substitution via compiled accessors.
- **`MgGridInfoPanel.razor.cs`** -- `MgGridInfoPanel` component implementing `IInfoPanelBase`. Displays focused-row details with responsive column layout, edit/view mode, sticky positioning via JS interop, and cached column/display-text lookups.
- **`MgGridToolbarBase.cs`** -- Extends `DxToolbar` with `Grid`, `RefreshClick`, and `ShowOnlyIcon` parameters.
- **`GridEditMode.cs`** -- `MgGridEditState` enum: `None`, `New`, `Edit`.
- **`MgGridHelper.cs`** -- Placeholder helper (empty).
- **`MgGridToolbarHelper.cs`** -- Placeholder helper (empty).
- **`MgGridInfoPanelHelper.cs`** -- Placeholder helper (empty).
- **`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.
- **`MgGridWithInfoPanel.razor`** — `DxSplitter` wrapper: grid (left) + InfoPanel (right), fullscreen overlay, splitter size persistence.
- **`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, fullscreen. Extensible via `ToolbarItemsExtended`.
- **`MgGridDataColumn.cs`** — Extends `DxGridDataColumn` with InfoPanel parameters (`ShowInInfoPanel`, `InfoPanelOrder`, `InfoPanelDisplayFormat`) and `UrlLink` template with `{Property}` placeholder substitution via compiled accessors.
- **`MgGridInfoPanel.razor`** / **`.razor.cs`** — `MgGridInfoPanel` implementing `IInfoPanelBase`. Responsive column layout (1-4 columns with breakpoints), edit/view mode with typed editors, template system, sticky positioning via JS interop.
- **`MgGridSignalRDataSource.cs`** — `GridCustomDataSource` wrapping `AcSignalRDataSource`. Local cache for seen filter criteria, background refresh.
- **`GridEditMode.cs`** — `MgGridEditState` enum: `None`, `New`, `Edit`.
- **`MgGridHelper.cs`**, **`MgGridToolbarHelper.cs`**, **`MgGridInfoPanelHelper.cs`** — Placeholder helpers (empty).

View File

@ -51,15 +51,10 @@ DxGrid (DevExpress)
|---|---|
| **MgGridBase** | Abstract base — SignalR CRUD, layout persistence, master-detail, edit state |
| **MgGridWithInfoPanel** | `DxSplitter` wrapper — grid + collapsible InfoPanel + fullscreen |
| **MgGridToolbarBase** | `DxToolbar` with grid reference and refresh action |
| **MgGridDataColumn** | `DxGridDataColumn` with URL template support |
| **MgGridToolbarTemplate** | Full toolbar: CRUD, navigation, layout, export, fullscreen |
| **MgGridDataColumn** | `DxGridDataColumn` with InfoPanel parameters and URL template support |
| **MgGridInfoPanel** | Default InfoPanel — column-value display with edit mode |
Key behaviors:
- CRUD via **SignalR message tags** (`SignalRCrudTags`) — not REST
- **Layout auto-persistence** to `localStorage` (per-user, per-grid, per-master/detail)
- **Master-detail** via `CascadingParameter<IMgGridBase>`
- **New item IDs**: negative ints (client-side temp) or `Guid.NewGuid()`
| **MgGridSignalRDataSource** | `GridCustomDataSource` with local cache and background refresh |
## Key Design Decisions

View File

@ -33,15 +33,3 @@ For core framework conventions (Ac prefix, Session/Transaction pattern, etc.) se
- All AyCode.Core project references are via **DLL** (not ProjectReference) — this is intentional to separate build graphs.
- DevExpress references are NuGet packages pinned to **25.1.3**.
## Code Reuse
- Before writing new code, search the codebase for existing implementations.
- If a method does most of what you need, extract the shared part into a smaller reusable method rather than copying and modifying.
- Prefer composing existing helpers over creating parallel implementations.
## Critical Rules
- Do not suggest removal/rollback as a solution — find a fix for the problem.
- SignalR uses **AcBinaryHubProtocol** — never switch to default JSON protocol.
- Grid data always flows through SignalR, never direct REST calls.

View File

@ -8,9 +8,28 @@ Blazor/MAUI UI terminology. For core framework terms see [`../../AyCode.Core/doc
|---|---|
| **DxGrid** | DevExpress Blazor data grid. Used with `AcSignalRDataSource` for real-time SignalR data. |
| **CardView** | Card-style layout wrapping DxGrid. Mobile-friendly alternative to table grids. |
| **AcSignalRDataSource** | Grid data source backed by SignalR. Handles load, CRUD, filtering, and change tracking. |
| **AcSignalRDataSource** | Grid data source backed by SignalR. Handles load, CRUD, filtering, and change tracking. See `AyCode.Core/docs/SIGNALR_DATASOURCE.md`. |
| **AcExpressionNode** | Serializable LINQ expression tree. Grid filters are serialized as expression nodes and sent to server. |
## MgGrid System
For full technical reference see [`MGGRID.md`](MGGRID.md).
| Term | Definition |
|---|---|
| **MgGridBase** | Abstract generic grid component extending `DxGrid` with SignalR CRUD, layout persistence, master-detail, InfoPanel, fullscreen. |
| **MgGridWithInfoPanel** | `DxSplitter` wrapper: grid (left pane) + InfoPanel (right pane), fullscreen overlay, splitter size persistence. |
| **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`. |
| **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. |
| **MgGridSignalRDataSource** | `GridCustomDataSource` wrapping `AcSignalRDataSource`. Local cache, background refresh. |
| **IMgGridBase** | Public interface: `IsSyncing`, `GridEditState`, `ParentGrid`, `StepPrevRow/NextRow`, layout persistence methods. |
| **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.Core/docs/SIGNALR_DATASOURCE.md`. |
| **IsMasterGrid** | `true` when `ParentDataItem == null` — top-level grid (not detail). |
| **AutoSaveLayoutName** | Base name for localStorage layout keys. Default: `"Grid{TDataItem.Name}"`. |
## Architecture
| Term | Definition |

View File

@ -2,11 +2,13 @@
> Comprehensive documentation for the **MgGrid** component family — the primary UI data grid pattern in the AyCode.Blazor framework.
> Source: `AyCode.Blazor.Components/Components/Grids/`
> For SignalR transport: `AyCode.Core/docs/SIGNALR.md`
> For `AcSignalRDataSource`: `AyCode.Core/docs/SIGNALR_DATASOURCE.md`
## Overview
**MgGridBase** is an abstract generic Blazor component that extends DevExpress `DxGrid` with:
- **Automatic SignalR CRUD** — data flows through `AcSignalRDataSource` with integer message tags
- **Automatic SignalR CRUD** via `AcSignalRDataSource` (see AyCode.Core docs)
- **Layout persistence** — column order, widths, sorting, grouping auto-saved to `localStorage`
- **Master-detail hierarchy** — nested grids share context via `CascadingParameter`
- **InfoPanel integration** — side panel shows focused row details, supports edit mode
@ -18,8 +20,8 @@
```
DxGrid (DevExpress)
└── MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> (AyCode.Blazor — abstract)
└── [Project-specific adapter, e.g. FruitBankGridBase<TDataItem>]
└── [Concrete grid, e.g. GridShippingBase]
└── [Project-specific adapter] ← consumer fixes TSignalRDataSource, TId, TLoggerClient
└── [Concrete entity grid] ← consumer sets CRUD tags in constructor
```
### Companion Components
@ -28,8 +30,10 @@ DxGrid (DevExpress)
|---|---|
| **MgGridWithInfoPanel** | `DxSplitter` wrapper: grid (left) + InfoPanel (right), fullscreen, splitter size persistence |
| **MgGridToolbarBase** | `DxToolbar` base with `Grid` reference, `RefreshClick` callback, `ShowOnlyIcon` toggle |
| **MgGridDataColumn** | Extended `DxGridDataColumn` with URL template support (`{PropertyName}` placeholders) |
| **MgGridInfoPanel** | Default InfoPanel showing column-value pairs, supports edit mode with save/cancel |
| **MgGridToolbarTemplate** | Full toolbar template: New/Edit/Delete/Save/Cancel, row navigation, layout menu, fullscreen, export, extensible via `ToolbarItemsExtended` |
| **MgGridDataColumn** | Extended `DxGridDataColumn` with InfoPanel parameters and `UrlLink` template (`{Property}` placeholders) |
| **MgGridInfoPanel** | Default InfoPanel: column-value pairs, edit mode with typed editors, responsive columns, sticky positioning |
| **MgGridSignalRDataSource** | `GridCustomDataSource` wrapper: server-side filter/sort/page, local cache for seen filter criteria, background refresh |
## Generic Type Parameters
@ -39,7 +43,7 @@ MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient>
| Parameter | Constraint | Purpose |
|---|---|---|
| `TSignalRDataSource` | `: AcSignalRDataSource<TDataItem, TId, AcObservableCollection<TDataItem>>` | SignalR-backed data source with CRUD operations |
| `TSignalRDataSource` | `: AcSignalRDataSource<…>` | SignalR-backed data source (see `AyCode.Core/docs/SIGNALR_DATASOURCE.md`) |
| `TDataItem` | `: class, IId<TId>` | Entity type displayed in the grid |
| `TId` | `: struct` | Primary key type (`int`, `Guid`) |
| `TLoggerClient` | `: AcLoggerBase` | Logger for diagnostics |
@ -48,21 +52,22 @@ MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient>
```csharp
// Project adapter — fixes TSignalRDataSource, TId, TLoggerClient for the entire project
public class FruitBankGridBase<TDataItem>
: MgGridBase<SignalRDataSourceObservable<TDataItem>, TDataItem, int, LoggerClient>
public class MyProjectGridBase<TDataItem>
: MgGridBase<MySignalRDataSource<TDataItem>, TDataItem, int, MyLoggerClient>
where TDataItem : class, IId<int>
{
protected override int GetLayoutUserId() => LoggedInModel.CustomerDto?.Id ?? 0;
[Inject] public required MyLoggedInModel LoggedInModel { get; set; }
protected override int GetLayoutUserId() => LoggedInModel.UserId;
}
// Concrete grid — only TDataItem remains open
public class GridShippingBase : FruitBankGridBase<Shipping>
public class GridOrderBase : MyProjectGridBase<Order>
{
public GridShippingBase()
public GridOrderBase()
{
GetAllMessageTag = SignalRTags.GetShippings;
AddMessageTag = SignalRTags.AddShipping;
UpdateMessageTag = SignalRTags.UpdateShipping;
GetAllMessageTag = MySignalRTags.GetOrders;
AddMessageTag = MySignalRTags.AddOrder;
UpdateMessageTag = MySignalRTags.UpdateOrder;
}
}
```
@ -87,7 +92,7 @@ public class GridShippingBase : FruitBankGridBase<Shipping>
| `UpdateMessageTag` | `int` | Tag for "update item" request |
| `RemoveMessageTag` | `int` | Tag for "remove item" request |
These are bundled into a `SignalRCrudTags` during `OnInitializedAsync`.
These are bundled into a `SignalRCrudTags` during `OnInitializedAsync`. See `AyCode.Core/docs/SIGNALR_DATASOURCE.md` for details.
### Data & Context Parameters
@ -122,6 +127,17 @@ All grid events are re-exposed with `OnGrid` prefix to avoid collisions with `Dx
| `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` |
## Lifecycle
```
@ -135,7 +151,7 @@ All grid events are re-exposed with `OnGrid` prefix to avoid collisions with `Dx
2. SetParametersAsyncCore() [first time]
├── Set KeyFieldName = "Id"
├── Wire DxGrid events → internal handlers (OnItemSaving, OnItemDeleting, etc.)
├── Wire 6 DxGrid events → internal handlers (see table above)
├── Add OnCustomizeElement handler (edit row highlighting, detail cell styling)
└── Set defaults: TextWrapEnabled=false, AllowSelectRowByClick=true, etc.
@ -143,41 +159,37 @@ All grid events are re-exposed with `OnGrid` prefix to avoid collisions with `Dx
├── Set GridName default: "{TDataItem.Name}Grid"
├── Set AutoSaveLayoutName default: "Grid{TDataItem.Name}"
├── Wire layout auto-loading/saving handlers
└── Register with GridWrapper for splitter persistence
└── Register with GridWrapper via GridWrapper.RegisterGrid(this)
4. OnAfterRenderAsync(firstRender: true)
├── If DataSource parameter was provided: LoadDataSource(dataSourceParam, sync, notify)
└── Else: LoadDataSourceAsync(notify) — fires SignalR GetAll request
```
### Data Load Flow
```
Grid.OnAfterRenderAsync
→ AcSignalRDataSource.LoadDataSourceAsync()
→ SignalRClient.SendMessage(GetAllMessageTag)
→ [AcBinary over SignalR]
→ Server Hub.OnReceiveMessage(tag, bytes, requestId)
→ DynamicMethodRegistry.TryFindMethod(tag)
→ [Tagged method executes, returns data]
→ [AcBinary response]
→ AcSignalRDataSource.BinaryToMerge()
→ OnDataSourceLoaded event
→ Grid.SetGridData() + StateHasChanged
```
## CRUD Operations
### Adding Items
```csharp
// Fire-and-forget (add to local list, sync later)
await grid.AddDataItem(item);
await grid.AddDataItem(item); // local add, sync later
await grid.AddDataItemAsync(item); // immediate server sync
// Immediate server sync
await grid.AddDataItemAsync(item);
await grid.InsertDataItem(0, item); // insert at index, sync later
await grid.InsertDataItemAsync(0, item); // insert at index, immediate sync
```
### Other CRUD Methods
| Method | Description |
|---|---|
| `UpdateDataItem(item)` | Local update, sync later |
| `UpdateDataItemAsync(item)` | Immediate server sync |
| `AddOrUpdateDataItem(item)` | Add if new, update if existing |
| `RemoveDataItem(item)` | Remove by entity reference |
| `RemoveDataItem(id)` | Remove by ID |
| `ReloadDataSourceAsync()` | Re-fetch all data from server |
| `ForceRenderAsync()` | Force grid re-initialization via new render key |
### ID Generation for New Items
New items get **temporary client-side IDs** until the server assigns real ones:
@ -212,17 +224,7 @@ User clicks Cancel → OnEditCanceling
### Edit Row Highlighting
When `GridEditState != None`, the focused row and its cells get `background-color: #fffbeb` (warm yellow).
### Server Sync
```csharp
// All CRUD methods eventually call:
AcSignalRDataSource.SaveChangesAsync()
// → Sends tracked changes via SignalR using appropriate CRUD tags
// → Server processes and responds
// → OnDataSourceItemChanged fires for each changed item
```
When `GridEditState != None`, the focused row and its cells get `background-color: #fffbeb` (warm yellow) via `OnCustomizeElement`.
## Layout Persistence
@ -238,10 +240,10 @@ Splitter: Splitter_{grid.AutomaticLayoutStorageKey}
**Examples:**
```
GridShipping_Master_AutoSave_42 ← master grid, user #42
GridShipping_Shipping_AutoSave_42 ← detail grid under Shipping parent
GridShipping_Master_UserSave_42 ← manually saved layout
Splitter_GridShipping_Master_AutoSave_42 ← splitter pane size
GridOrder_Master_AutoSave_42 ← master grid, user #42
GridOrder_Order_AutoSave_42 ← detail grid under Order parent
GridOrder_Master_UserSave_42 ← manually saved layout
Splitter_GridOrder_Master_AutoSave_42 ← splitter pane size
```
### Three Layout Tiers
@ -249,7 +251,7 @@ Splitter_GridShipping_Master_AutoSave_42 ← splitter pane size
| Tier | Key Contains | When Saved | When Loaded |
|---|---|---|---|
| **Default** | (in-memory `_defaultLayoutJson`) | First `LayoutAutoLoading` — captures layout before any load | `ResetLayoutAsync()` — restores original |
| **AutoSave** | `_AutoSave_` | Every `LayoutAutoSaving` event (on any layout change) | Every `LayoutAutoLoading` event (on grid init) |
| **AutoSave** | `_AutoSave_` | Every `LayoutAutoSaving` event (on any layout change) | Every `LayoutAutoLoading` event (on grid init, wrapped in `BeginUpdate`/`EndUpdate`) |
| **UserSave** | `_UserSave_` | `SaveUserLayoutAsync()` — explicit user action | `LoadUserLayoutAsync()` — explicit user action |
### Layout Operations
@ -258,21 +260,16 @@ Splitter_GridShipping_Master_AutoSave_42 ← splitter pane size
|---|---|
| `SaveUserLayoutAsync()` | Saves current layout to both UserSave AND AutoSave keys |
| `LoadUserLayoutAsync()` | Loads from UserSave key (if exists) |
| `ResetLayoutAsync()` | Removes AutoSave key, restores in-memory default layout |
| `ResetLayoutAsync()` | Removes AutoSave key, restores in-memory `_defaultLayoutJson` |
| `HasUserLayoutAsync()` | Checks if UserSave key exists in localStorage |
### Persisted State
The layout (`GridPersistentLayout`) includes: column order, column widths, sort descriptors, group descriptors, filter row values, page size — serialized as JSON.
The layout (`GridPersistentLayout`) includes: column order, column widths, sort descriptors, group descriptors, filter row values, page size — serialized as JSON via `System.Text.Json`.
### User Identification
`GetLayoutUserId()` is virtual — defaults to `0`. Override in project adapter:
```csharp
// FruitBankGridBase overrides this:
protected override int GetLayoutUserId() => LoggedInModel.CustomerDto?.Id ?? 0;
```
`GetLayoutUserId()` is virtual — defaults to `0`. Override in project adapter to provide the logged-in user's ID.
## Master-Detail Hierarchy
@ -288,9 +285,9 @@ protected override int GetLayoutUserId() => LoggedInModel.CustomerDto?.Id ?? 0;
```razor
<DetailRowTemplate>
@{
var parent = (Shipping)context.DataItem;
<GridShippingDocument ParentDataItem="@parent"
KeyFieldNameToParentId="ShippingId"
var parent = (ParentEntity)context.DataItem;
<GridChildEntity ParentDataItem="@parent"
KeyFieldNameToParentId="ParentEntityId"
ContextIds="@(new object[] { parent.Id })" />
}
</DetailRowTemplate>
@ -298,24 +295,83 @@ protected override int GetLayoutUserId() => LoggedInModel.CustomerDto?.Id ?? 0;
When `ParentDataItem` is set and `KeyFieldNameToParentId` is provided, new items automatically get their parent FK set via reflection.
## InfoPanel Integration
### MgGridWithInfoPanel Wrapper
## MgGridWithInfoPanel Wrapper
```razor
<MgGridWithInfoPanel ShowInfoPanel="true" InfoPanelSize="400px">
<GridContent>
<GridShippingBase @ref="Grid" ... />
<GridMyEntityBase @ref="Grid" ... />
</GridContent>
@* Optional: custom InfoPanel via ChildContent *@
<ChildContent>
@* Optional: custom InfoPanel — if omitted, default MgGridInfoPanel is used *@
</ChildContent>
</MgGridWithInfoPanel>
```
| Parameter | Type | Default | Description |
|---|---|---|---|
| `GridContent` | `RenderFragment` | — | The grid to display in the left pane |
| `ChildContent` | `RenderFragment?` | `null` | Custom InfoPanel. If `null`, renders `MgGridInfoPanel` |
| `ShowInfoPanel` | `bool` | `true` | Whether to show the right pane |
| `InfoPanelSize` | `string` | `"400px"` | Initial right pane size |
The wrapper provides:
- `DxSplitter` with collapsible right pane
- Fullscreen overlay (`mg-fullscreen-overlay`)
- Splitter size persistence (`Splitter_{key}` in localStorage)
- Default `MgGridInfoPanel` if no custom `ChildContent`
- `RegisterGrid(grid)` — called by MgGridBase in `OnParametersSet`
- `RegisterInfoPanel(infoPanel)` — called by MgGridInfoPanel in `OnAfterRenderAsync`
## MgGridToolbarTemplate
The standard toolbar rendered inside grid's `ToolbarTemplate`. Provides all standard grid operations:
### Toolbar Buttons
| Group | Buttons | Visible |
|---|---|---|
| **CRUD** | New, Edit, Delete | When NOT editing |
| **Edit mode** | Save, Cancel | When editing |
| **Navigation** | Prev Row, Next Row | When NOT editing |
| **Layout** | Column Chooser, Layout (Load/Save/Reset) | When `OnlyGridEditTools=false` |
| **Actions** | Export (CSV/XLSX/XLS/PDF), Reload Data, Fullscreen | When `OnlyGridEditTools=false` |
### Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| `Grid` | `IMgGridBase` | required | The grid to control |
| `OnlyGridEditTools` | `bool` | `false` | Show only CRUD + navigation (used by InfoPanel) |
| `ShowOnlyIcon` | `bool` | `false` | Hide button text, show only icons |
| `EnableNew` | `bool` | `true` | Enable "New" button |
| `EnableEdit` | `bool` | `true` | Enable "Edit" button |
| `EnableDelete` | `bool` | `false` | Enable "Delete" button |
| `ToolbarItemsExtended` | `RenderFragment?` | `null` | Extra toolbar items after standard buttons |
| `OnReloadDataClick` | `EventCallback` | — | Callback for "Reload Data" button |
### State Properties (computed from Grid)
| Property | Source |
|---|---|
| `IsEditing` | `Grid.GridEditState != MgGridEditState.None` |
| `IsSyncing` | `Grid.IsSyncing` |
| `HasFocusedRow` | `Grid.GetFocusedRowIndex() >= 0` |
| `IsFullscreenMode` | `Grid.IsFullscreen` |
## MgGridInfoPanel
Default InfoPanel component implementing `IInfoPanelBase`. Displays focused-row details with edit support.
### IInfoPanelBase Interface
```csharp
public interface IInfoPanelBase
{
void RefreshData(IMgGridBase grid, object? dataItem, int visibleIndex = -1);
void SetEditMode(IMgGridBase grid, object editModel);
void ClearEditMode();
}
```
### InfoPanel Data Flow
@ -325,7 +381,76 @@ Edit starts → InfoPanelInstance.SetEditMode(grid, editModel)
Edit ends/cancel → InfoPanelInstance.ClearEditMode()
```
`InfoPanelInstance` resolution: own `GridWrapper` → root grid's `GridWrapper``null`.
`InfoPanelInstance` resolution: own `GridWrapper.InfoPanelInstance` → root grid's `GridWrapper.InfoPanelInstance``null`.
### Responsive Column Layout
| Breakpoint Parameter | Default | Columns |
|---|---|---|
| — | < 400px | 1 column |
| `TwoColumnBreakpoint` | 400px | 2 columns |
| `ThreeColumnBreakpoint` | 800px | 3 columns |
| `FourColumnBreakpoint` | 1300px | 4 columns |
`FixedColumnCount` (1-4) overrides responsive breakpoints if set.
### Template System
| Template | Context | Purpose |
|---|---|---|
| `HeaderTemplate` | `InfoPanelContext` | Custom header (default: grid Caption) |
| `BeforeColumnsTemplate` | `InfoPanelContext` | Content before column-value pairs |
| `ColumnsTemplate` | `InfoPanelContext` | Replace default column rendering entirely |
| `AfterColumnsTemplate` | `InfoPanelContext` | Content after column-value pairs |
| `FooterTemplate` | `InfoPanelContext` | Custom footer |
`InfoPanelContext` = `record(object? DataItem, bool IsEditMode)`.
### Edit Mode Editors (by property type)
| Type | Editor Component |
|---|---|
| `bool` | `DxCheckBox<bool>` |
| `DateTime` / `DateTime?` | `DxDateEdit<DateTime>` / `DxDateEdit<DateTime?>` |
| `DateOnly` / `DateOnly?` | `DxDateEdit<DateOnly>` / `DxDateEdit<DateOnly?>` |
| `int` | `DxSpinEdit<int>` |
| `decimal` | `DxSpinEdit<decimal>` |
| `double` | `DxSpinEdit<double>` |
| ComboBox (via `DxComboBoxSettings`) | `DxComboBox<TValue, TItem>` |
| Memo (via `EditSettingsType.Memo`) | `DxMemo` |
| Other | `DxTextBox` |
### Additional Features
- **Sticky positioning** via JS interop (`MgGridInfoPanel.initSticky`)
- **Built-in toolbar** with `MgGridToolbarTemplate` (`OnlyGridEditTools=true`)
- **OnDataItemChanged** callback when focused row changes
## MgGridDataColumn
Extended `DxGridDataColumn` with InfoPanel and URL link support.
| Parameter | Type | Default | Description |
|---|---|---|---|
| `ShowInInfoPanel` | `bool` | `true` | Whether this column is visible in InfoPanel |
| `InfoPanelDisplayFormat` | `string?` | `null` | Custom display format for InfoPanel |
| `InfoPanelOrder` | `int` | `int.MaxValue` | Column order in InfoPanel (lower = earlier) |
| `UrlLink` | `string?` | `null` | URL template with `{Property}` placeholders |
**UrlLink example:** `https://admin.example.com/Entity/Edit/{Id}` — renders cell as `<a href="..." target="_blank">`.
Uses compiled property accessors (`ConcurrentDictionary` cache) for performance.
## MgGridSignalRDataSource
`GridCustomDataSource` wrapper around `AcSignalRDataSource` for server-side data operations.
- Returns local data instantly for previously-seen filter criteria
- Refreshes from the server in the background
- Handles filter, sort, paging, unique values, and summary calculations locally
- `OnBackgroundRefreshCompleted` event fires when background refresh completes
See `AyCode.Core/docs/SIGNALR_DATASOURCE.md` for the underlying `AcSignalRDataSource`.
## Fullscreen Mode
@ -358,7 +483,7 @@ Set in `SetParametersAsyncCore` (first call only):
| `AutoCollapseDetailRow` | `true` |
| `AutoExpandAllGroupRows` | `false` |
Project adapters add more defaults (e.g., `FruitBankGridBase` sets `EditMode=EditRow`, `FocusedRowEnabled=true`, `PageSize`, `ShowFilterRow` based on `IsMasterGrid`).
Project adapters typically add more defaults in `OnParametersSet` (e.g., `EditMode`, `FocusedRowEnabled`, `PageSize`, `ShowFilterRow`, `SizeMode` based on `IsMasterGrid`).
## Disposal
@ -366,6 +491,7 @@ Project adapters add more defaults (e.g., `FruitBankGridBase` sets `EditMode=Edi
1. Set `_isDisposed = true` (guards all async callbacks)
2. Unsubscribe from `OnDataSourceLoaded`, `OnDataSourceItemChanged`, `OnSyncingStateChanged`
3. Remove `OnCustomizeElement` handler
4. `GC.SuppressFinalize(this)`
All async callbacks check `_isDisposed` before proceeding.