From d1567323d89983d8c2e3926236c4d6fc98c0579c Mon Sep 17 00:00:00 2001 From: Loretta Date: Mon, 30 Mar 2026 08:00:53 +0200 Subject: [PATCH] Refactor MgGrid documentation into modular files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split MGGRID.md into focused docs (parameters, CRUD, layout, detail, rendering, InfoPanel, toolbar, columns, datasource) under AyCode.Blazor.Components/docs/. Updated all references, READMEs, and project/solution files to match new structure. Clarified doc layering conventions and core doc links. No code changes—documentation and project organization only. --- .github/copilot-instructions.md | 10 +- .../AyCode.Blazor.Components.csproj | 4 + .../Components/Grids/README.md | 2 +- AyCode.Blazor.Components/README.md | 15 + AyCode.Blazor.Components/docs/MGGRID.md | 116 ++++ .../docs/MGGRID_COLUMNS.md | 18 + AyCode.Blazor.Components/docs/MGGRID_CRUD.md | 101 ++++ .../docs/MGGRID_DATASOURCE.md | 13 + .../docs/MGGRID_DETAIL.md | 25 + .../docs/MGGRID_INFOPANEL.md | 98 ++++ .../docs/MGGRID_LAYOUT.md | 46 ++ .../docs/MGGRID_PARAMETERS.md | 82 +++ .../docs/MGGRID_RENDERING.md | 21 + .../docs/MGGRID_TOOLBAR.md | 39 ++ Aycode.Blazor.sln | 1 - README.md | 38 +- docs/ARCHITECTURE.md | 2 +- docs/CONVENTIONS.md | 2 +- docs/GLOSSARY.md | 8 +- docs/MGGRID.md | 525 ------------------ 20 files changed, 615 insertions(+), 551 deletions(-) create mode 100644 AyCode.Blazor.Components/docs/MGGRID.md create mode 100644 AyCode.Blazor.Components/docs/MGGRID_COLUMNS.md create mode 100644 AyCode.Blazor.Components/docs/MGGRID_CRUD.md create mode 100644 AyCode.Blazor.Components/docs/MGGRID_DATASOURCE.md create mode 100644 AyCode.Blazor.Components/docs/MGGRID_DETAIL.md create mode 100644 AyCode.Blazor.Components/docs/MGGRID_INFOPANEL.md create mode 100644 AyCode.Blazor.Components/docs/MGGRID_LAYOUT.md create mode 100644 AyCode.Blazor.Components/docs/MGGRID_PARAMETERS.md create mode 100644 AyCode.Blazor.Components/docs/MGGRID_RENDERING.md create mode 100644 AyCode.Blazor.Components/docs/MGGRID_TOOLBAR.md delete mode 100644 docs/MGGRID.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e4bdc9a..7eb6fcb 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -19,13 +19,13 @@ 8. SignalR uses **AcBinaryHubProtocol** (custom binary) — not the default JSON protocol. 9. Grid filters use **AcExpressionNode** serialization — LINQ expressions are serialized and sent to server. -## Related Solutions -14. **AyCode.Core** solution (`../AyCode.Core/AyCode.Core.sln`) contains all core framework code (SignalR, serialization, data sources, base classes, interfaces). When a type is referenced in this Blazor solution but not defined here (e.g. `AcSignalRDataSource`, `AcBinaryHubProtocol`, `IId`, `AcObservableCollection`, `AcLoggerBase`), look it up in AyCode.Core source. -15. For AyCode.Core domain rules see: `../AyCode.Core/.github/copilot-instructions.md` - ## Conventions 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 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. +14. **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. + +## Related Solutions +15. **AyCode.Core** solution (`../AyCode.Core/AyCode.Core.sln`) contains all core framework code (SignalR, serialization, data sources, base classes, interfaces). When a type is referenced in this Blazor solution but not defined here (e.g. `AcSignalRDataSource`, `AcBinaryHubProtocol`, `IId`, `AcObservableCollection`, `AcLoggerBase`), look it up in AyCode.Core source. +16. For AyCode.Core domain rules see: `../AyCode.Core/.github/copilot-instructions.md` diff --git a/AyCode.Blazor.Components/AyCode.Blazor.Components.csproj b/AyCode.Blazor.Components/AyCode.Blazor.Components.csproj index 4fce58a..a437e1b 100644 --- a/AyCode.Blazor.Components/AyCode.Blazor.Components.csproj +++ b/AyCode.Blazor.Components/AyCode.Blazor.Components.csproj @@ -55,6 +55,10 @@ + + + + diff --git a/AyCode.Blazor.Components/Components/Grids/README.md b/AyCode.Blazor.Components/Components/Grids/README.md index c389ced..fde0082 100644 --- a/AyCode.Blazor.Components/Components/Grids/README.md +++ b/AyCode.Blazor.Components/Components/Grids/README.md @@ -1,6 +1,6 @@ # Grids -Core grid system built on DevExpress `DxGrid`. For the full technical reference see `AyCode.Blazor/docs/MGGRID.md`. +Core grid system built on DevExpress `DxGrid`. For the full technical reference see `AyCode.Blazor.Components/docs/MGGRID.md`. ## Key Files diff --git a/AyCode.Blazor.Components/README.md b/AyCode.Blazor.Components/README.md index 4748c9f..59b6bf4 100644 --- a/AyCode.Blazor.Components/README.md +++ b/AyCode.Blazor.Components/README.md @@ -2,6 +2,21 @@ Blazor Razor component library targeting .NET 10. Provides reusable DevExpress-based UI components, a SignalR-powered grid system, and LINQ expression serialization services. +## Documentation + +| Document | Topic | +|---|---| +| `MGGRID.md` | MgGrid system — overview, hierarchy, generic params, IMgGridBase interface | +| `MGGRID_PARAMETERS.md` | Component parameters, event callbacks, default grid settings | +| `MGGRID_CRUD.md` | Lifecycle, CRUD operations, edit flow, disposal | +| `MGGRID_LAYOUT.md` | Layout persistence (storage keys, tiers, operations) | +| `MGGRID_DETAIL.md` | Master-detail hierarchy | +| `MGGRID_RENDERING.md` | Fullscreen mode, rendering | +| `MGGRID_INFOPANEL.md` | MgGridInfoPanel, MgGridWithInfoPanel wrapper | +| `MGGRID_TOOLBAR.md` | MgGridToolbarTemplate (buttons, parameters, state) | +| `MGGRID_COLUMNS.md` | MgGridDataColumn (InfoPanel params, UrlLink) | +| `MGGRID_DATASOURCE.md` | MgGridSignalRDataSource (server-side data, local cache) | + ## Dependencies - **DevExpress.Blazor** 25.1.3, **DevExpress.Data** 25.1.3 diff --git a/AyCode.Blazor.Components/docs/MGGRID.md b/AyCode.Blazor.Components/docs/MGGRID.md new file mode 100644 index 0000000..436904b --- /dev/null +++ b/AyCode.Blazor.Components/docs/MGGRID.md @@ -0,0 +1,116 @@ +# MgGrid System + +> Comprehensive documentation for the **MgGrid** component family — the primary UI data grid pattern in the AyCode.Blazor framework. +> Source: `Components/Grids/` +> For SignalR transport: `AyCode.Services/docs/SIGNALR.md` (in AyCode.Core repo) +> For `AcSignalRDataSource`: `AyCode.Services.Server/docs/SIGNALR_DATASOURCE.md` (in AyCode.Core repo) + +## 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` + +## Detailed Documentation + +| File | Topics | +|---|---| +| `MGGRID_PARAMETERS.md` | Component parameters (required, CRUD tags, data & context, display), event callbacks, default grid settings | +| `MGGRID_CRUD.md` | Lifecycle, CRUD operations, ID generation, edit flow, disposal | +| `MGGRID_LAYOUT.md` | Layout persistence (storage keys, three tiers, operations, persisted state) | +| `MGGRID_DETAIL.md` | Master-detail hierarchy | +| `MGGRID_RENDERING.md` | Fullscreen mode, rendering | +| `MGGRID_INFOPANEL.md` | MgGridInfoPanel, MgGridWithInfoPanel wrapper, responsive layout, templates, editors | +| `MGGRID_TOOLBAR.md` | MgGridToolbarTemplate (buttons, parameters, state) | +| `MGGRID_COLUMNS.md` | MgGridDataColumn (InfoPanel params, UrlLink) | +| `MGGRID_DATASOURCE.md` | MgGridSignalRDataSource (server-side data, local cache, background refresh) | + +## Component Hierarchy + +``` +DxGrid (DevExpress) + └── MgGridBase (AyCode.Blazor — abstract) + └── [Project-specific adapter] ← consumer fixes TSignalRDataSource, TId, TLoggerClient + └── [Concrete entity grid] ← consumer sets CRUD tags in constructor +``` + +### Companion Components + +| Component | Purpose | Docs | +|---|---|---| +| **MgGridWithInfoPanel** | `DxSplitter` wrapper: grid + InfoPanel, fullscreen, splitter persistence | `MGGRID_INFOPANEL.md` | +| **MgGridToolbarBase** | `DxToolbar` base with `Grid` reference, `RefreshClick` callback | `MGGRID_TOOLBAR.md` | +| **MgGridToolbarTemplate** | Full toolbar: CRUD, navigation, layout menu, fullscreen, export | `MGGRID_TOOLBAR.md` | +| **MgGridDataColumn** | Extended `DxGridDataColumn` with InfoPanel params and `UrlLink` | `MGGRID_COLUMNS.md` | +| **MgGridInfoPanel** | Default InfoPanel: column-value pairs, edit mode, typed editors | `MGGRID_INFOPANEL.md` | +| **MgGridSignalRDataSource** | `GridCustomDataSource` wrapper: server-side filter/sort/page, local cache | `MGGRID_DATASOURCE.md` | + +## Generic Type Parameters + +```csharp +MgGridBase +``` + +| Parameter | Constraint | Purpose | +|---|---|---| +| `TSignalRDataSource` | `: AcSignalRDataSource<…>` | SignalR-backed data source (see `AyCode.Services.Server/docs/SIGNALR_DATASOURCE.md` in AyCode.Core repo) | +| `TDataItem` | `: class, IId` | 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 + : MgGridBase, TDataItem, int, MyLoggerClient> + where TDataItem : class, IId +{ + [Inject] public required MyLoggedInModel LoggedInModel { get; set; } + protected override int GetLayoutUserId() => LoggedInModel.UserId; +} + +// Concrete grid — only TDataItem remains open +public class GridOrderBase : MyProjectGridBase +{ + public GridOrderBase() + { + GetAllMessageTag = MySignalRTags.GetOrders; + AddMessageTag = MySignalRTags.AddOrder; + UpdateMessageTag = MySignalRTags.UpdateOrder; + } +} +``` + +## 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` | Check if manual save exists | + +## Event Args Classes + +| Class | Base | Extra Properties | +|---|---|---| +| `GridDataItemChangedEventArgs` | — | `Grid`, `DataItem`, `TrackingState`, `CancelStateChangeInvoke` | +| `GridDataItemChangingEventArgs` | `GridDataItemChangedEventArgs` | `IsCanceled` | diff --git a/AyCode.Blazor.Components/docs/MGGRID_COLUMNS.md b/AyCode.Blazor.Components/docs/MGGRID_COLUMNS.md new file mode 100644 index 0000000..4c17c07 --- /dev/null +++ b/AyCode.Blazor.Components/docs/MGGRID_COLUMNS.md @@ -0,0 +1,18 @@ +# MgGrid — Columns + +> Part of the MgGrid system. See `MGGRID.md` for overview and component hierarchy. + +## 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 ``. + +Uses compiled property accessors (`ConcurrentDictionary` cache) for performance. diff --git a/AyCode.Blazor.Components/docs/MGGRID_CRUD.md b/AyCode.Blazor.Components/docs/MGGRID_CRUD.md new file mode 100644 index 0000000..80768f4 --- /dev/null +++ b/AyCode.Blazor.Components/docs/MGGRID_CRUD.md @@ -0,0 +1,101 @@ +# MgGrid — Lifecycle & CRUD + +> Part of the MgGrid system. See `MGGRID.md` for overview and component hierarchy. + +## 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 MGGRID_PARAMETERS.md) + ├── 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`. + +## 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. diff --git a/AyCode.Blazor.Components/docs/MGGRID_DATASOURCE.md b/AyCode.Blazor.Components/docs/MGGRID_DATASOURCE.md new file mode 100644 index 0000000..f65fec0 --- /dev/null +++ b/AyCode.Blazor.Components/docs/MGGRID_DATASOURCE.md @@ -0,0 +1,13 @@ +# MgGrid — DataSource + +> Part of the MgGrid system. See `MGGRID.md` for overview and component hierarchy. +> For the underlying `AcSignalRDataSource`: `AyCode.Services.Server/docs/SIGNALR_DATASOURCE.md` (in AyCode.Core repo) + +## 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 diff --git a/AyCode.Blazor.Components/docs/MGGRID_DETAIL.md b/AyCode.Blazor.Components/docs/MGGRID_DETAIL.md new file mode 100644 index 0000000..d09667e --- /dev/null +++ b/AyCode.Blazor.Components/docs/MGGRID_DETAIL.md @@ -0,0 +1,25 @@ +# MgGrid — Master-Detail + +> Part of the MgGrid system. See `MGGRID.md` for overview and component hierarchy. + +## How It Works + +1. `MgGridBase.BuildRenderTree` wraps content in `CascadingValue` +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 + + @{ + var parent = (ParentEntity)context.DataItem; + + } + +``` + +When `ParentDataItem` is set and `KeyFieldNameToParentId` is provided, new items automatically get their parent FK set via reflection. diff --git a/AyCode.Blazor.Components/docs/MGGRID_INFOPANEL.md b/AyCode.Blazor.Components/docs/MGGRID_INFOPANEL.md new file mode 100644 index 0000000..f0f69f8 --- /dev/null +++ b/AyCode.Blazor.Components/docs/MGGRID_INFOPANEL.md @@ -0,0 +1,98 @@ +# MgGrid — InfoPanel + +> Part of the MgGrid system. See `MGGRID.md` for overview and component hierarchy. + +## MgGridWithInfoPanel Wrapper + +```razor + + + + + + @* Optional: custom InfoPanel — if omitted, default MgGridInfoPanel is used *@ + + +``` + +| 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` + +## 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` | +| `DateTime` / `DateTime?` | `DxDateEdit` / `DxDateEdit` | +| `DateOnly` / `DateOnly?` | `DxDateEdit` / `DxDateEdit` | +| `int` | `DxSpinEdit` | +| `decimal` | `DxSpinEdit` | +| `double` | `DxSpinEdit` | +| ComboBox (via `DxComboBoxSettings`) | `DxComboBox` | +| 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 diff --git a/AyCode.Blazor.Components/docs/MGGRID_LAYOUT.md b/AyCode.Blazor.Components/docs/MGGRID_LAYOUT.md new file mode 100644 index 0000000..fb020ef --- /dev/null +++ b/AyCode.Blazor.Components/docs/MGGRID_LAYOUT.md @@ -0,0 +1,46 @@ +# MgGrid — Layout Persistence + +> Part of the MgGrid system. See `MGGRID.md` for overview and component hierarchy. + +## 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. diff --git a/AyCode.Blazor.Components/docs/MGGRID_PARAMETERS.md b/AyCode.Blazor.Components/docs/MGGRID_PARAMETERS.md new file mode 100644 index 0000000..bfadda0 --- /dev/null +++ b/AyCode.Blazor.Components/docs/MGGRID_PARAMETERS.md @@ -0,0 +1,82 @@ +# MgGrid — Parameters & Events + +> Part of the MgGrid system. See `MGGRID.md` for 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.Server/docs/SIGNALR_DATASOURCE.md` (in AyCode.Core repo) for details. + +## Data & Context Parameters + +| Parameter | Type | Description | +|---|---|---| +| `DataSource` | `IList` | Bind with `AcObservableCollection` for external data. If not set, grid creates its own. | +| `ParentDataItem` | `IId?` | 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` | + +## 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`). diff --git a/AyCode.Blazor.Components/docs/MGGRID_RENDERING.md b/AyCode.Blazor.Components/docs/MGGRID_RENDERING.md new file mode 100644 index 0000000..bfb9b71 --- /dev/null +++ b/AyCode.Blazor.Components/docs/MGGRID_RENDERING.md @@ -0,0 +1,21 @@ +# MgGrid — Fullscreen & Rendering + +> Part of the MgGrid system. See `MGGRID.md` for overview and component hierarchy. + +## 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` — 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 diff --git a/AyCode.Blazor.Components/docs/MGGRID_TOOLBAR.md b/AyCode.Blazor.Components/docs/MGGRID_TOOLBAR.md new file mode 100644 index 0000000..86c979f --- /dev/null +++ b/AyCode.Blazor.Components/docs/MGGRID_TOOLBAR.md @@ -0,0 +1,39 @@ +# MgGrid — Toolbar + +> Part of the MgGrid system. See `MGGRID.md` for overview and component hierarchy. + +## 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` | diff --git a/Aycode.Blazor.sln b/Aycode.Blazor.sln index 554940c..5c3435f 100644 --- a/Aycode.Blazor.sln +++ b/Aycode.Blazor.sln @@ -28,7 +28,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{C3A1D7E2-B docs\ARCHITECTURE.md = docs\ARCHITECTURE.md docs\CONVENTIONS.md = docs\CONVENTIONS.md docs\GLOSSARY.md = docs\GLOSSARY.md - docs\MGGRID.md = docs\MGGRID.md EndProjectSection EndProject Global diff --git a/README.md b/README.md index 0cb9cad..2ca69c2 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Blazor Server + MAUI Hybrid UI framework built on DevExpress 25.1.3 components. ## LLM Context -Domain rules and UI pitfalls live in a single file: [`.github/copilot-instructions.md`](.github/copilot-instructions.md) +Domain rules and UI pitfalls live in a single file: `.github/copilot-instructions.md` | Tool | Auto-loaded | Action needed | |------|------------|---------------| @@ -12,22 +12,34 @@ Domain rules and UI pitfalls live in a single file: [`.github/copilot-instructio | Claude Code | ✅ `CLAUDE.md` → references above | None | | Cursor / Windsurf | ✅ `README.md` | Read `copilot-instructions.md` via @file | -Detailed docs: [`docs/`](docs/) — GLOSSARY.md, ARCHITECTURE.md +Solution-level docs in `docs/`: -Core framework rules: [`../AyCode.Core/.github/copilot-instructions.md`](../AyCode.Core/.github/copilot-instructions.md) +| Document | Topic | +|---|---| +| `GLOSSARY.md` | Blazor/MAUI terminology | +| `ARCHITECTURE.md` | Solution layers, dependency rules | +| `CONVENTIONS.md` | Coding conventions | + +Project-level docs: + +| Project | Documents | +|---|---| +| `AyCode.Blazor.Components/docs/` | `MGGRID.md` — MgGrid system (grid base, toolbar, InfoPanel, layout, CRUD) | + +Core framework rules: `../AyCode.Core/.github/copilot-instructions.md` ## Solution Structure -| Project | Purpose | README | -|---|---|---| -| [`AyCode.Blazor.Components`](AyCode.Blazor.Components/README.md) | DevExpress UI components, grids, SignalR data sources, expression helpers | [README](AyCode.Blazor.Components/README.md) | -| [`AyCode.Blazor.Models`](AyCode.Blazor.Models/README.md) | Shared view models for Blazor components | [README](AyCode.Blazor.Models/README.md) | -| [`AyCode.Blazor.Models.Server`](AyCode.Blazor.Models.Server/README.md) | Server-side model scaffolding | [README](AyCode.Blazor.Models.Server/README.md) | -| [`AyCode.Blazor.Controllers`](AyCode.Blazor.Controllers/README.md) | Controller scaffolding (minimal) | [README](AyCode.Blazor.Controllers/README.md) | -| [`AyCode.Maui.Core`](AyCode.Maui.Core/README.md) | MAUI cross-platform: Android, iOS, Windows | [README](AyCode.Maui.Core/README.md) | +| Project | Purpose | +|---|---| +| `AyCode.Blazor.Components` | DevExpress UI components, grids, SignalR data sources, expression helpers | +| `AyCode.Blazor.Models` | Shared view models for Blazor components | +| `AyCode.Blazor.Models.Server` | Server-side model scaffolding | +| `AyCode.Blazor.Controllers` | Controller scaffolding (minimal) | +| `AyCode.Maui.Core` | MAUI cross-platform: Android, iOS, Windows | ### Test Projects -| Project | Purpose | README | -|---|---|---| -| [`AyCode.Blazor.Components.Tests`](AyCode.Blazor.Components.Tests/README.md) | Grid and component tests | [README](AyCode.Blazor.Components.Tests/README.md) | +| Project | Purpose | +|---|---| +| `AyCode.Blazor.Components.Tests` | Grid and component tests | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 04dcd81..2d72b67 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -38,7 +38,7 @@ User ← DxGrid ← AcSignalRDataSource ← SignalR (AcBinary) ← Server Hub ## MgGrid Component System -The primary UI pattern for data screens. Full documentation: [`MGGRID.md`](MGGRID.md) +The primary UI pattern for data screens. Overview and index: `AyCode.Blazor.Components/docs/MGGRID.md` ``` DxGrid (DevExpress) diff --git a/docs/CONVENTIONS.md b/docs/CONVENTIONS.md index 985c943..d1993cc 100644 --- a/docs/CONVENTIONS.md +++ b/docs/CONVENTIONS.md @@ -1,6 +1,6 @@ # Conventions -For core framework conventions (Ac prefix, Session/Transaction pattern, etc.) see [`../../AyCode.Core/docs/CONVENTIONS.md`](../../AyCode.Core/docs/CONVENTIONS.md). +For core framework conventions (Ac prefix, Session/Transaction pattern, etc.) see `AyCode.Core/docs/CONVENTIONS.md`. ## Naming diff --git a/docs/GLOSSARY.md b/docs/GLOSSARY.md index 75b64e3..f212366 100644 --- a/docs/GLOSSARY.md +++ b/docs/GLOSSARY.md @@ -1,6 +1,6 @@ # Glossary -Blazor/MAUI UI terminology. For core framework terms see [`../../AyCode.Core/docs/GLOSSARY.md`](../../AyCode.Core/docs/GLOSSARY.md). +Blazor/MAUI UI terminology. For core framework terms see `AyCode.Core/docs/GLOSSARY.md`. ## UI Components @@ -8,12 +8,12 @@ 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. See `AyCode.Core/docs/SIGNALR_DATASOURCE.md`. | +| **AcSignalRDataSource** | Grid data source backed by SignalR. Handles load, CRUD, filtering, and change tracking. See `AyCode.Services.Server/docs/SIGNALR_DATASOURCE.md` in AyCode.Core repo. | | **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). +For full technical reference see `AyCode.Blazor.Components/docs/MGGRID.md`. | Term | Definition | |---|---| @@ -26,7 +26,7 @@ For full technical reference see [`MGGRID.md`](MGGRID.md). | **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`. | +| **SignalRCrudTags** | Bundle of 5 integer message tags (GetAll, GetItem, Add, Update, Remove) for one entity type. See `AyCode.Services.Server/docs/SIGNALR_DATASOURCE.md` in AyCode.Core repo. | | **IsMasterGrid** | `true` when `ParentDataItem == null` — top-level grid (not detail). | | **AutoSaveLayoutName** | Base name for localStorage layout keys. Default: `"Grid{TDataItem.Name}"`. | diff --git a/docs/MGGRID.md b/docs/MGGRID.md deleted file mode 100644 index 5101d77..0000000 --- a/docs/MGGRID.md +++ /dev/null @@ -1,525 +0,0 @@ -# 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 (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 -``` - -| Parameter | Constraint | Purpose | -|---|---|---| -| `TSignalRDataSource` | `: AcSignalRDataSource<…>` | SignalR-backed data source (see `AyCode.Core/docs/SIGNALR_DATASOURCE.md`) | -| `TDataItem` | `: class, IId` | 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 - : MgGridBase, TDataItem, int, MyLoggerClient> - where TDataItem : class, IId -{ - [Inject] public required MyLoggedInModel LoggedInModel { get; set; } - protected override int GetLayoutUserId() => LoggedInModel.UserId; -} - -// Concrete grid — only TDataItem remains open -public class GridOrderBase : MyProjectGridBase -{ - 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` | Bind with `AcObservableCollection` for external data. If not set, grid creates its own. | -| `ParentDataItem` | `IId?` | 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` -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 - - @{ - var parent = (ParentEntity)context.DataItem; - - } - -``` - -When `ParentDataItem` is set and `KeyFieldNameToParentId` is provided, new items automatically get their parent FK set via reflection. - -## MgGridWithInfoPanel Wrapper - -```razor - - - - - - @* Optional: custom InfoPanel — if omitted, default MgGridInfoPanel is used *@ - - -``` - -| 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` | -| `DateTime` / `DateTime?` | `DxDateEdit` / `DxDateEdit` | -| `DateOnly` / `DateOnly?` | `DxDateEdit` / `DxDateEdit` | -| `int` | `DxSpinEdit` | -| `decimal` | `DxSpinEdit` | -| `double` | `DxSpinEdit` | -| ComboBox (via `DxComboBoxSettings`) | `DxComboBox` | -| 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 ``. - -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` — 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` | Check if manual save exists | - -## Event Args Classes - -| Class | Base | Extra Properties | -|---|---|---| -| `GridDataItemChangedEventArgs` | — | `Grid`, `DataItem`, `TrackingState`, `CancelStateChangeInvoke` | -| `GridDataItemChangingEventArgs` | `GridDataItemChangedEventArgs` | `IsCanceled` |