526 lines
21 KiB
Markdown
526 lines
21 KiB
Markdown
# MgGrid System
|
|
|
|
> 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** 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
|
|
- **Fullscreen mode** — standalone overlay or via `MgGridWithInfoPanel` wrapper
|
|
- **Change tracking** — client-side tracking with server sync via `SaveChangesAsync`
|
|
|
|
## Component Hierarchy
|
|
|
|
```
|
|
DxGrid (DevExpress)
|
|
└── MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> (AyCode.Blazor — abstract)
|
|
└── [Project-specific adapter] ← consumer fixes TSignalRDataSource, TId, TLoggerClient
|
|
└── [Concrete entity grid] ← consumer sets CRUD tags in constructor
|
|
```
|
|
|
|
### Companion Components
|
|
|
|
| Component | Purpose |
|
|
|---|---|
|
|
| **MgGridWithInfoPanel** | `DxSplitter` wrapper: grid (left) + InfoPanel (right), fullscreen, splitter size persistence |
|
|
| **MgGridToolbarBase** | `DxToolbar` base with `Grid` reference, `RefreshClick` callback, `ShowOnlyIcon` toggle |
|
|
| **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
|
|
|
|
```csharp
|
|
MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient>
|
|
```
|
|
|
|
| Parameter | Constraint | Purpose |
|
|
|---|---|---|
|
|
| `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 |
|
|
|
|
### Usage Example (Project-Specific Adapter)
|
|
|
|
```csharp
|
|
// Project adapter — fixes TSignalRDataSource, TId, TLoggerClient for the entire project
|
|
public class MyProjectGridBase<TDataItem>
|
|
: MgGridBase<MySignalRDataSource<TDataItem>, TDataItem, int, MyLoggerClient>
|
|
where TDataItem : class, IId<int>
|
|
{
|
|
[Inject] public required MyLoggedInModel LoggedInModel { get; set; }
|
|
protected override int GetLayoutUserId() => LoggedInModel.UserId;
|
|
}
|
|
|
|
// Concrete grid — only TDataItem remains open
|
|
public class GridOrderBase : MyProjectGridBase<Order>
|
|
{
|
|
public GridOrderBase()
|
|
{
|
|
GetAllMessageTag = MySignalRTags.GetOrders;
|
|
AddMessageTag = MySignalRTags.AddOrder;
|
|
UpdateMessageTag = MySignalRTags.UpdateOrder;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Component Parameters
|
|
|
|
### 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.Core/docs/SIGNALR_DATASOURCE.md` 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 |
|
|
| `FilterText` | `string?` | Text filter — propagated to data source, triggers reload |
|
|
|
|
### 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 |
|
|
|
|
## 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` |
|
|
|
|
## Lifecycle
|
|
|
|
```
|
|
1. OnInitializedAsync()
|
|
├── Validate Logger, SignalRClient (throw if null)
|
|
├── Create SignalRCrudTags from message tag parameters
|
|
├── Create TSignalRDataSource via Activator.CreateInstance(SignalRClient, crudTags, ContextIds)
|
|
├── Set DataSource.FilterText
|
|
├── Bind grid Data to data source inner list
|
|
└── Subscribe to: OnDataSourceLoaded, OnDataSourceItemChanged, OnSyncingStateChanged
|
|
|
|
2. SetParametersAsyncCore() [first time]
|
|
├── Set KeyFieldName = "Id"
|
|
├── 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.
|
|
|
|
3. OnParametersSet() [first time]
|
|
├── Set GridName default: "{TDataItem.Name}Grid"
|
|
├── Set AutoSaveLayoutName default: "Grid{TDataItem.Name}"
|
|
├── Wire layout auto-loading/saving handlers
|
|
└── 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
|
|
```
|
|
|
|
## CRUD Operations
|
|
|
|
### Adding Items
|
|
|
|
```csharp
|
|
await grid.AddDataItem(item); // local add, sync later
|
|
await grid.AddDataItemAsync(item); // immediate server sync
|
|
|
|
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:
|
|
|
|
| TId Type | Strategy | Example |
|
|
|---|---|---|
|
|
| `Guid` | `Guid.NewGuid()` | `a1b2c3d4-...` |
|
|
| `int` | `-1 * AcDomain.NextUniqueInt32` | `-1`, `-2`, `-3`, ... |
|
|
|
|
**Convention:** Negative integer IDs indicate unsaved items. The server replaces them with real auto-increment IDs.
|
|
|
|
### Edit Flow (Inline)
|
|
|
|
```
|
|
User clicks Edit → OnEditStart → OnCustomizeEditModel
|
|
├── Set GridEditState = New/Edit
|
|
├── For new items: assign temp ID, set parent FK if detail grid
|
|
├── Notify InfoPanel: SetEditMode()
|
|
└── Fire OnGridCustomizeEditModel callback
|
|
|
|
User clicks Save → OnItemSaving
|
|
├── Fire OnGridEditModelSaving callback (can cancel)
|
|
├── If new: AddDataItemAsync / InsertDataItemAsync
|
|
├── If existing: UpdateDataItemAsync
|
|
├── Reset GridEditState = None
|
|
└── Clear InfoPanel edit mode
|
|
|
|
User clicks Cancel → OnEditCanceling
|
|
├── Reset GridEditState = None
|
|
└── Clear InfoPanel edit mode
|
|
```
|
|
|
|
### Edit Row Highlighting
|
|
|
|
When `GridEditState != None`, the focused row and its cells get `background-color: #fffbeb` (warm yellow) via `OnCustomizeElement`.
|
|
|
|
## Layout Persistence
|
|
|
|
### Storage Keys
|
|
|
|
Grid layouts are stored in **localStorage** with structured keys:
|
|
|
|
```
|
|
AutoSave: {AutoSaveLayoutName}_{MasterOrParentTypeName}_AutoSave_{UserId}
|
|
UserSave: {AutoSaveLayoutName}_{MasterOrParentTypeName}_UserSave_{UserId}
|
|
Splitter: Splitter_{grid.AutomaticLayoutStorageKey}
|
|
```
|
|
|
|
**Examples:**
|
|
```
|
|
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
|
|
|
|
| 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, wrapped in `BeginUpdate`/`EndUpdate`) |
|
|
| **UserSave** | `_UserSave_` | `SaveUserLayoutAsync()` — explicit user action | `LoadUserLayoutAsync()` — explicit user action |
|
|
|
|
### Layout Operations
|
|
|
|
| Method | Behavior |
|
|
|---|---|
|
|
| `SaveUserLayoutAsync()` | Saves current layout to both UserSave AND AutoSave keys |
|
|
| `LoadUserLayoutAsync()` | Loads from UserSave key (if exists) |
|
|
| `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 via `System.Text.Json`.
|
|
|
|
### User Identification
|
|
|
|
`GetLayoutUserId()` is virtual — defaults to `0`. Override in project adapter to provide the logged-in user's ID.
|
|
|
|
## Master-Detail Hierarchy
|
|
|
|
### How It Works
|
|
|
|
1. `MgGridBase.BuildRenderTree` wraps content in `CascadingValue<IMgGridBase>`
|
|
2. Child grids receive this via `[CascadingParameter] IMgGridBase? ParentGrid`
|
|
3. `IsMasterGrid` = `ParentDataItem == null`
|
|
4. `GetRootGrid()` walks the `ParentGrid` chain to find the topmost grid
|
|
|
|
### Detail Grid Setup
|
|
|
|
```razor
|
|
<DetailRowTemplate>
|
|
@{
|
|
var parent = (ParentEntity)context.DataItem;
|
|
<GridChildEntity ParentDataItem="@parent"
|
|
KeyFieldNameToParentId="ParentEntityId"
|
|
ContextIds="@(new object[] { parent.Id })" />
|
|
}
|
|
</DetailRowTemplate>
|
|
```
|
|
|
|
When `ParentDataItem` is set and `KeyFieldNameToParentId` is provided, new items automatically get their parent FK set via reflection.
|
|
|
|
## MgGridWithInfoPanel Wrapper
|
|
|
|
```razor
|
|
<MgGridWithInfoPanel ShowInfoPanel="true" InfoPanelSize="400px">
|
|
<GridContent>
|
|
<GridMyEntityBase @ref="Grid" ... />
|
|
</GridContent>
|
|
<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)
|
|
- `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
|
|
|
|
```
|
|
FocusedRowChanged → InfoPanelInstance.RefreshData(grid, dataItem, visibleIndex)
|
|
Edit starts → InfoPanelInstance.SetEditMode(grid, editModel)
|
|
Edit ends/cancel → InfoPanelInstance.ClearEditMode()
|
|
```
|
|
|
|
`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
|
|
|
|
Two modes depending on whether `MgGridWithInfoPanel` wraps the grid:
|
|
|
|
| Scenario | Behavior |
|
|
|---|---|
|
|
| **With wrapper** | `ToggleFullscreen()` delegates to wrapper — fullscreen includes grid + InfoPanel |
|
|
| **Standalone** | Grid renders its own `mg-fullscreen-overlay` with header (Caption + close button) and body |
|
|
|
|
## Rendering
|
|
|
|
`BuildRenderTree` uses manual render tree building (not Razor markup):
|
|
|
|
1. Outer `CascadingValue<IMgGridBase>` — provides this grid as `ParentGrid` to children
|
|
2. If standalone fullscreen: `div.mg-fullscreen-overlay` > `div.mg-fullscreen-header` + `div.mg-fullscreen-body` > `base.BuildRenderTree`
|
|
3. If normal: `div[style=display:contents]` > `base.BuildRenderTree`
|
|
4. `_gridRenderKey` (Guid) used as element key — changed by `ForceRenderAsync()` to force re-initialization
|
|
|
|
## 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`).
|
|
|
|
## Disposal
|
|
|
|
`DisposeAsync()` handles cleanup:
|
|
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.
|
|
|
|
## Interface: IMgGridBase
|
|
|
|
The public contract exposed to companion components (toolbar, InfoPanel, wrapper):
|
|
|
|
| Member | Type | Description |
|
|
|---|---|---|
|
|
| `IsSyncing` | `bool` | Whether SignalR sync is in progress |
|
|
| `Caption` | `string` | Grid title |
|
|
| `GridEditState` | `MgGridEditState` | `None` / `New` / `Edit` |
|
|
| `ParentGrid` | `IMgGridBase?` | Parent in master-detail hierarchy |
|
|
| `GetRootGrid()` | `IMgGridBase` | Walks to topmost grid |
|
|
| `StepPrevRow()` | `void` | Navigate to previous visible row |
|
|
| `StepNextRow()` | `void` | Navigate to next visible row |
|
|
| `InfoPanelInstance` | `IInfoPanelBase?` | Resolved InfoPanel reference |
|
|
| `IsFullscreen` | `bool` | Current fullscreen state |
|
|
| `AutomaticLayoutStorageKey` | `string` | Current auto-save storage key |
|
|
| `ToggleFullscreen()` | `void` | Toggle fullscreen mode |
|
|
| `SaveUserLayoutAsync()` | `Task` | Save layout manually |
|
|
| `LoadUserLayoutAsync()` | `Task` | Load manually saved layout |
|
|
| `ResetLayoutAsync()` | `Task` | Reset to default layout |
|
|
| `HasUserLayoutAsync()` | `Task<bool>` | Check if manual save exists |
|
|
|
|
## Event Args Classes
|
|
|
|
| Class | Base | Extra Properties |
|
|
|---|---|---|
|
|
| `GridDataItemChangedEventArgs<T>` | — | `Grid`, `DataItem`, `TrackingState`, `CancelStateChangeInvoke` |
|
|
| `GridDataItemChangingEventArgs<T>` | `GridDataItemChangedEventArgs<T>` | `IsCanceled` |
|