Kandra.ClientLib.MudBlazor
Kandra.ClientLib.MudBlazor.Pages.Admin.Constants.ConstantEditDialog
Properties
| Property | Description |
|---|
EffectiveDate | The value's current/existing effective instant, already converted to this browser's local time by the shared UtcToLocalDateTimeJsonConverter (both SetAt and Date are genuine instants, so that conversion is correct here). Null only for a never-overridden constant (List page, no history row yet). |
Id | The row being edited, when LockEffectiveDate is true. |
LockEffectiveDate | True when editing one specific existing row's value in place - hides the effective-date/time controls (only EffectiveDate is shown, read-only) and saves via UpdateValueAsync using Id, not a re-derived instant. Requires Id. |
Methods
| Method | Description |
|---|
ComposeDateValueUtc | Mirrors ComputeEffectiveUtc's local-to-UTC conversion, for the business value when it's itself a date/time (unrelated to the row's own effective instant). |
Kandra.ClientLib.MudBlazor.Services.ChartOfAccountsTreeCache
Fetches the whole chart-of-accounts tree once per app session and memoizes it - both nested (for the tree page/dialog picker) and flattened (for the search selector's substring scan). Registered Singleton, not Scoped like IHierarchicalDictionaryLookupService's per-level lookups: the chart is code-defined and never changes after deploy, so one fetch per app session is correct rather than one per circuit.
Constructors
| Constructor | Description |
|---|
ChartOfAccountsTreeCache(IChartOfAccountsApiClient) | Fetches the whole chart-of-accounts tree once per app session and memoizes it - both nested (for the tree page/dialog picker) and flattened (for the search selector's substring scan). Registered Singleton, not Scoped like IHierarchicalDictionaryLookupService's per-level lookups: the chart is code-defined and never changes after deploy, so one fetch per app session is correct rather than one per circuit. |
Kandra.ClientLib.MudBlazor.Services.IFileSaveService
Methods
| Method | Description |
|---|
OpenInNewTab(Byte[], String) | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Accounting.AccountTransactionsView
Properties
| Property | Description |
|---|
Load | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Accounting.SubcontoValueList
Properties
| Property | Description |
|---|
Values | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.ChangeTracking.ChangeHistoryTable
Properties
| Property | Description |
|---|
Load | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.ChangeTracking.ChangeStateInfo
Properties
| Property | Description |
|---|
Load | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.DateRangeEditor
Properties
| Property | Description |
|---|
PickerRange | Mirrors DateRange as the MudBlazor-native type MudDateRangePicker itself binds to. |
Kandra.ClientLib.MudBlazor.Shared.Components.FilePickerField
Properties
| Property | Description |
|---|
Extensions | undocumented |
MaxFileSizeBytes | undocumented |
Methods
| Method | Description |
|---|
OnFilesChangedAsync(InputFileChangeEventArgs) | undocumented |
UploadPendingFileAsync | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.HierarchyBreadcrumbs
Properties
| Property | Description |
|---|
Items | undocumented |
NavigationPrefix | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.KandraLogo
Properties
| Property | Description |
|---|
OnDark | Force the white-contour version, for surfaces that stay dark in both themes. |
Kandra.ClientLib.MudBlazor.Shared.Components.Linking.DocumentLinksView
Properties
| Property | Description |
|---|
Load | undocumented |
Properties
| Property | Description |
|---|
IsActive | Null (default) = not a document list, the 4th submit/unsubmit button isn't rendered at all. |
Kandra.ClientLib.MudBlazor.Shared.Components.Registers.RegisterTransactionsView
Properties
| Property | Description |
|---|
Load | undocumented |
Methods
| Method | Description |
|---|
Text(RegisterFieldValueDto) | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.AccountDialogSelect
Properties
| Property | Description |
|---|
DialogTitle | undocumented |
NodeSelection | undocumented |
SelectedCode | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.AccountPickerDialog
Properties
| Property | Description |
|---|
NodeSelection | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.AccountSearchSelect
Properties
| Property | Description |
|---|
NodeSelection | undocumented |
SelectedCode | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.DictionaryDialogMultiSelect<T0, T1>
Properties
| Property | Description |
|---|
DialogTitle | undocumented |
QueryOverride | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.DictionaryDialogSelect<T0, T1>
Properties
| Property | Description |
|---|
DialogTitle | undocumented |
QueryOverride | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.DictionaryDropdownSelect<T0, T1>
Properties
| Property | Description |
|---|
QueryOverride | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.DictionaryMultiSelectPickerDialog<T0, T1>
Properties
| Property | Description |
|---|
InitialSelection | undocumented |
QueryOverride | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.DictionaryPickerDialog<T0, T1>
Properties
| Property | Description |
|---|
QueryOverride | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.DictionarySearchSelect<T0, T1>
Properties
| Property | Description |
|---|
QueryOverride | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.DocumentDialogMultiSelect<T0, T1>
Properties
| Property | Description |
|---|
DialogTitle | undocumented |
QueryOverride | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.DocumentDialogSelect<T0, T1>
Properties
| Property | Description |
|---|
DialogTitle | undocumented |
QueryOverride | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.DocumentMultiSelectPickerDialog<T0, T1>
Properties
| Property | Description |
|---|
InitialSelection | undocumented |
QueryOverride | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.DocumentPickerDialog<T0, T1>
Properties
| Property | Description |
|---|
QueryOverride | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.HierarchicalDictionaryDialogMultiSelect<T0, T1>
Properties
| Property | Description |
|---|
DialogTitle | undocumented |
NodeSelection | undocumented |
QueryOverride | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.HierarchicalDictionaryDialogSelect<T0, T1>
Properties
| Property | Description |
|---|
DialogTitle | undocumented |
NodeSelection | undocumented |
QueryOverride | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.HierarchicalDictionaryMultiSelectPickerDialog<T0, T1>
Properties
| Property | Description |
|---|
InitialSelection | undocumented |
NodeSelection | undocumented |
QueryOverride | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.HierarchicalDictionaryPickerDialog<T0, T1>
Properties
| Property | Description |
|---|
NodeSelection | undocumented |
QueryOverride | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.HierarchicalDictionarySearchSelect<T0, T1>
Properties
| Property | Description |
|---|
NodeSelection | undocumented |
QueryOverride | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Components.Selectors.HierarchicalNodeSelection
Fields
| Field | Description |
|---|
Any | undocumented |
Folders | undocumented |
Leaves | undocumented |
Kandra.ClientLib.MudBlazor.Shared.FormSaveContext
Cascaded to every field editor inside a generated Add/Update form's Form<TRecord>/ Form<TRecord,TValidator> shell (BlazorUiRenderer) - a control that needs to do work right before Save/Execute fires (today: FilePickerField uploading a picked-but-not- yet-uploaded local file and only then reporting the resulting reference id back through its own two-way binding) registers a delegate here instead of the page needing to know about it. ReadOnly mirrors whichever readOnly the owning page's Form call was opened with - a plain settable property refreshed on every render (matches every other MudBlazor field editor already picking this up via the ambient MudForm's own cascading state; this object plays the same role for a custom component that isn't MudBlazor-form-aware out of the box).
Kandra.ClientLib.MudBlazor.Shared.IHasFormSaveContext
Implemented by AddBase<TItem>/UpdateBase<TItem> - the shared ancestor of every generated Document/Dictionary Add/Update page - so BlazorUiRenderer has a generic way to reach the owning page's FormSaveContext and cascade it, and so those two base classes have a single, well-known place to call BeforeSaveAsync from before their own DtoServiceAddRecord/DtoServiceUpdateRecord.
Kandra.ClientLib.MudBlazor.Shared.LicenseLimitDialog
Properties
| Property | Description |
|---|
FallbackMessage | Fallback text (the raw server message) if LimitKind doesn't match a known key. |
LimitKind | The limitKind ProblemDetails extension value (e.g. "DocumentsPerMonth"). |
Kandra.ClientLib.MudBlazor.Shared.Pages.AddDocument<T0, T1>
Abstract purely to defer NavigationPrefix to the concrete hand-written leaf Add page - this class itself never sets a route.
Kandra.ClientLib.MudBlazor.Shared.Pages.DataProcessorPageBase<T0, T1, T2>
DataProcessor counterpart to DictionaryFormBase/ DocumentFormBase - but a DataProcessor has no List/Add/Update/View split (ExecuteFormTitleAttribute's own doc comment: "a single-page filter-and-run screen ... no add/edit/ list/view distinction"), so this one base class both is the routed page (unlike Dictionary/Document, where a separate outer Add/Update/View page embeds a shared Form component) and owns the whole AuthorizedPage/PageTitle/Execute/Result sequence a hand-written DataProcessor page (the old ImportNbuRates.razor, now under _HandWrittenReference/DataProcessor/) used to assemble itself. Kandra.Generators.Ui.Blazor's DataProcessorUiEmitter emits one sealed subclass per DataProcessor, overriding NavigationPrefix/Policy/Title plus RenderFields (the input Dto's own field editors, inside a validated Form<TInput,TValidator> - every DataProcessor Dto ships a mandatory Validator, root CLAUDE.md) and RenderResult (the ResultDtoType's [Caption]-carrying properties, rendered read-only once Execute succeeds - see UiFieldDiscovery.TryBuildResultFormModel for how that field set is discovered). Stays a plain .cs class, not .razor - same reason as DictionaryFormBase/DocumentFormBase: it overrides BuildRenderTree itself, which would collide with the Razor compiler's own generated override in a .razor file.
Properties
| Property | Description |
|---|
Policy | Authorization policy gating the whole page - matches the hand-written convention's own "Execute{Name}Behavior" string exactly (e.g. "ExecuteImportNbuRatesBehavior"), so the client-side gate never drifts from the server-side one DataProcessorAuthorizationPoint already enforces. |
Methods
| Method | Description |
|---|
RenderFields(IUiRenderer) | The input Dto's own field editors, in declared order - the one thing actually specific to a DataProcessor. |
RenderResult(IUiRenderer) | The result Dto's [Caption]-carrying properties, read-only - rendered only once Execute has produced a Result (see BuildRenderTree's own null check above). |
Kandra.ClientLib.MudBlazor.Shared.Pages.DictionaryFormBaseUnvalidated<T0>
No-validator fallback - see DictionaryFormBase's doc comment for why this should be unreachable for a real, correctly-configured entity.
Kandra.ClientLib.MudBlazor.Shared.Pages.DictionaryFormBase<T0, T1>
Everything about a generated {Dto}Form component that never varies per entity - the Record/Title/ButtonText/WaitingOperation/OnValidSubmit/ReadOnly parameter set, the Loc/RendererFactory injects, and the renderer setup (title, divider, opening the Form shell, Save/Cancel vs Back). The Kandra.Generators.Ui.Blazor-emitted subclass only has to override RenderFields with the field calls specific to that Dto - mirrors the existing hand-written-base-class pattern already used for List/Add/Update/View pages (ListDictionary and friends), just applied to Form too. This is the FluentValidation-bound overload - every form-bearing Dto has a mandatory Validator (root CLAUDE.md: "The engine fails DI validation at startup if one is missing"), so this is the one generated Form classes actually inherit from; DictionaryFormBaseUnvalidated is a defensive fallback for the (should-never-happen) case discovery doesn't resolve one. Stays a plain .cs class, not a .razor file like every other shared page base in this folder - it needs to override BuildRenderTree itself, and the Razor compiler always generates its own BuildRenderTree override for a .razor file (even one with completely empty markup), which collides with a hand-written one in the same partial class (CS0111). FlatDictionaryListPage doesn't have this problem (it never overrides BuildRenderTree, only ordinary methods), which is why that one is a .razor file. Implements IEditForm purely so FormComponent can instantiate the generated {Dto}Form subclass with its Title/ButtonText/Record/WaitingOperation/ OnValidSubmit/ReadOnly attribute names checked against the interface at compile time, instead of untyped string literals.
Properties
| Property | Description |
|---|
SaveContext | undocumented |
Methods
| Method | Description |
|---|
RenderFields(IUiRenderer) | The one thing that's actually specific to a Dto - its field editors, in declared order. |
Kandra.ClientLib.MudBlazor.Shared.Pages.DocumentFormBase<T0, T1>
Document counterpart to DictionaryFormBase - same Record/Title/ButtonText/ WaitingOperation/OnValidSubmit/ReadOnly parameter set and Loc/RendererFactory injects, same "only RenderFields is entity-specific" split. Two things a Document form has that a Dictionary form doesn't: SubmitButtonText and the Save-vs-Submit intent flow - matches the hand-written Waybill/Form.razor's own _intentSubmit field + HandleValidSubmit exactly (see that method's own doc comment on AddSaveSubmitButtons for why the confirmation dialog itself has to live here rather than in the framework-agnostic renderer). a read-only render skips the confirmation dialog entirely (there is no Save button to click) and emits no terminal button of its own - the enclosing view page (EmitViewTabs) supplies a single Back button above the tabs instead.
Properties
| Property | Description |
|---|
SaveContext | undocumented |
Methods
| Method | Description |
|---|
HandleValidSubmit | Mirrors the hand-written Waybill/Form.razor's own HandleValidSubmit exactly - a Save (not Submit) on an already-active document unsubmits it, which is surprising enough to confirm first; a Submit, or Saving a document that was never active, needs no confirmation. |
RenderFields(IUiRenderer, Boolean) | The one thing that's actually specific to a Dto - its field editors (and, if it has a Lines property, a TablePart call) - in declared order. readOnly mirrors this component's own ReadOnly (individual field editors ignore it - they already read the ambient MudForm ReadOnly cascading from Form - only the generated TablePart call needs it explicitly, to decide whether to show the Add/Delete-line buttons). Reused unchanged for a generated Document View page, which embeds this same component with ReadOnly forced true instead of re-emitting its own copy of these calls - see DocumentViewForm. |
Kandra.ClientLib.MudBlazor.Shared.Pages.DocumentListPage<T0, T1>
Abstract purely to defer NavigationPrefix and the six per-entity IDocumentListPage members (Columns/AddPolicy/AddCaption/EditPolicy/ DeletePolicy/SubmitPolicy) to the concrete hand-written/generated leaf List page - this class itself never sets a route or knows a Dto's columns/policies. Everything else the interface needs (paging, grid capture, server reload, date-range filter) is genuinely entity-agnostic and lives here, non-abstractly.
Kandra.ClientLib.MudBlazor.Shared.Pages.FlatDictionaryAddPage<T0, T1>
Abstract purely to defer NavigationPrefix to the concrete hand-written/generated leaf Add page - this class itself never sets a route.
Kandra.ClientLib.MudBlazor.Shared.Pages.FlatDictionaryListPage<T0, T1>
Abstract purely to defer NavigationPrefix and the five per-entity IFlatDictionaryListPage members (Columns/AddPolicy/AddCaption/EditPolicy/ DeletePolicy) to the concrete hand-written/generated leaf List page - this class itself never sets a route or knows a Dto's columns/policies. Everything else the interface needs (paging, grid capture, server reload) is genuinely entity-agnostic and lives here, non-abstractly.
Kandra.ClientLib.MudBlazor.Shared.Pages.FlatDictionaryUpdatePage<T0, T1>
Abstract purely to defer NavigationPrefix to the concrete hand-written/generated leaf Update page - this class itself never sets a route.
Kandra.ClientLib.MudBlazor.Shared.Pages.FlatDictionaryViewPage<T0, T1>
Abstract purely to defer NavigationPrefix to the concrete hand-written/generated leaf View page - this class itself never sets a route.
Kandra.ClientLib.MudBlazor.Shared.Pages.HierarchicalDictionaryAddPage<T0, T1>
Abstract purely to defer NavigationPrefix to the concrete hand-written/generated leaf Add page - this class itself never sets a route.
Kandra.ClientLib.MudBlazor.Shared.Pages.HierarchicalDictionaryListPage<T0, T1, T2, T3>
Abstract purely to defer NavigationPrefix and the nine per-entity IHierarchicalDictionaryListPage members (AddPolicy/AddLeafCaption/ AddFolderCaption/EditPolicy/DeletePolicy/BreadcrumbRootCaption/ExtraColumns/CodeCaption/NameCaption) to the concrete hand-written/generated leaf List page. CodeCaption/NameCaption default to "Code"/"Name" for every hierarchical dictionary that doesn't override them, but stay per-entity (not hardcoded here) so a concrete root Dto's own [Caption] override on Code/Name (see DictionaryDto.Code/Name being virtual) is actually honored — mirrors DocumentListPage.CodeCaption's identical reasoning for documents. Everything else the interface needs (breadcrumbs, folder navigation, grid capture, server reload) is genuinely entity-agnostic and lives here, non-abstractly.
Properties
| Property | Description |
|---|
UpTargetId | undocumented |
Methods
| Method | Description |
|---|
IsFolder(T0) | undocumented |
Kandra.ClientLib.MudBlazor.Shared.Pages.HierarchicalDictionaryUpdatePage<T0, T1>
Abstract purely to defer NavigationPrefix to the concrete hand-written/generated leaf Update page - this class itself never sets a route.
Kandra.ClientLib.MudBlazor.Shared.Pages.HierarchicalDictionaryViewPage<T0, T1>
Abstract purely to defer NavigationPrefix to the concrete hand-written/generated leaf View page - this class itself never sets a route.
Kandra.ClientLib.MudBlazor.Shared.Pages.ListDictionary<T0, T1>
Abstract purely to defer NavigationPrefix to the concrete hand-written/generated leaf List page - this class itself never sets a route.
Kandra.ClientLib.MudBlazor.Shared.Pages.ListDocument<T0, T1>
Abstract purely to defer NavigationPrefix to the concrete hand-written leaf List page - this class itself never sets a route.
Methods
| Method | Description |
|---|
OnRangeChanged | QueryDto.From/.To are read directly off DatesRange by DocumentListPage's own ServerReload (mirrors the hand-written per-Document List.razor pages' identical private method) - this only needs to trigger a reload once the date range itself has changed. |
Kandra.ClientLib.MudBlazor.Shared.Pages.ReportPageBase<T0, T1, T2>
Report counterpart to DataProcessorPageBase - a Report, like a DataProcessor, has no List/Add/Update/View split (one filter-and-run screen), so this base class both is the routed page and owns the whole PageTitle/filters/result sequence every hand-written report page used to assemble itself. Kandra.Generators.Ui.Blazor's ReportUiEmitter emits one sealed subclass per Report, overriding NavigationPrefix/Title plus RenderFilterFields (the filters Dto's own field editors, direct two-way-bound with no EditForm/validator wrapper - matches every hand-written report page, which never wraps its filters in one either; the FluentValidation validator each report Dto still carries is enforced server-side inside RunAsync, not client-side here), optionally RenderExtendedFilterFields/ HasExtendedFilters (the [ExtendedFilter]-marked subset), and RenderResult (the export buttons + result grid, both entirely report-specific - column list, aggregate wiring, and which export formats to offer are all only known at generator time). Stays a plain .cs class, not .razor - same reason as DictionaryFormBase/DocumentFormBase/ DataProcessorPageBase: it overrides BuildRenderTree itself, which would collide with the Razor compiler's own generated override in a .razor file.
Properties
| Property | Description |
|---|
HasExtendedFilters | True when this report has any [ExtendedFilter]-marked field - gates whether BuildRenderTree wraps RenderExtendedFilterFields in a collapsible panel at all. |
Methods
| Method | Description |
|---|
FiltersReady | Matches PriceListReport's existing "don't run until a required dropdown filter is picked" guard - true (always runnable) unless a generated subclass overrides it for a required-selection filter. |
RenderExtendedFilterFields(IUiRenderer) | The filters Dto's [ExtendedFilter]-marked field editors - only ever invoked when HasExtendedFilters is true, so a report with none can leave this at its no-op default. |
RenderFilterFields(IUiRenderer) | The filters Dto's own regular (non-extended) field editors, in declared order. |
RenderResult(IBlazorUiRenderer) | Export buttons + result grid - rendered only once Report is non-null (see BuildRenderTree's own null check above). |
Fields
| Field | Description |
|---|
GroupingState | Outermost-first grouping order for the result grid - see ReportGroupingState's own doc comment for why this needs to be an ordered set, not a single field. |
Kandra.ClientLib.MudBlazor.Shared.Pages.UpdateDocument<T0, T1>
Abstract purely to defer NavigationPrefix to the concrete hand-written leaf Update page - this class itself never sets a route.
Kandra.ClientLib.MudBlazor.Shared.Pages.ViewDocument<T0, T1>
Abstract purely to defer NavigationPrefix to the concrete hand-written leaf View page - this class itself never sets a route.
Kandra.ClientLib.MudBlazor.Shared.ThemeState
The effective light/dark state of the UI, published by the theme switcher in UserNav (whose MudThemeProvider is the one that actually drives the palette) so that components elsewhere in the tree - e.g. KandraLogo picking the white- or black-contour logo - can follow it without a cascading value from a component that is not their ancestor. Singleton, like PageHistoryState (one WASM app = one user).
Properties
| Property | Description |
|---|
IsDarkMode | Whether the UI currently renders with the dark palette. |
Methods
| Method | Description |
|---|
Set(Boolean) | Publishes the effective mode; no-op (and no event) when unchanged. |
Events
| Event | Description |
|---|
Changed | Raised after IsDarkMode changes. |
Kandra.ClientLib.MudBlazor.Ui.BlazorUiRenderer
The one real, compiled implementation behind every generated Blazor form/page - see IUiRenderer's doc comment for the overall shape. Not registered directly; obtained per-BuildRenderTree-call from BlazorUiRendererFactory. Internal state is a genuine push/pop stack of (RenderTreeBuilder, seq) frames, not a single shared field: every nested RenderFragment/RenderFragment<T> lambda (EditForm's ChildContent, MudCard's ChildContent, MudSelect's per-item ChildContent, MudDataGrid's Columns/CellTemplate, ...) is invoked later by that child component's own render pass with its own RenderTreeBuilder instance and its own zero-based sequence space - exactly why the text-based codegen this replaces invented fresh builder1/seq1, builder2/seq2, ... names per nesting level. Here that's a real stack instead: any method that opens a component whose content is a RenderFragment pushes a new frame when it hands Blazor that lambda, and pops it when the lambda body finishes - and because container methods (Form, AuthorizedPage, ListForm, ...) are fluent (they open, invoke the caller's body, then close), the push/pop is symmetric by construction; there is no Begin/End pair a caller could get out of sync.
Methods
| Method | Description |
|---|
EmitDocumentDateCodeStatusColumns<T0>(String, String, Boolean, Boolean, String, String) | The Date/Code/Status column trio every hand-written Document List page starts its grid with - Date/Code non-filterable/filterable PropertyColumns (matches every hand-written Document List exactly), Status a CheckCircle/RadioButtonUnchecked CellTemplate over IsActive (submitted vs. draft). |
EmitEditLink(String, Action) | Wraps renderContent in a MudLink pointing at the record's Edit (Update) page - the click target every clickable list cell (Code/Name/Date/etc. on Document lists, flat dictionaries, and hierarchical-dictionary leaves) shares. Color.Inherit/Underline.Hover so a plain data cell doesn't turn permanently blue-and-underlined like a normal link, matching how these grids read today - only distinguishing itself as clickable on hover. |
EmitFolderLeafColumn<T0>(String, Expression<Func<T0, String>>, Func<T0, String>, Func<T0, Boolean>, Action<Nullable<Guid>>, Boolean, Boolean, Boolean, String) | Code column gets the folder/person icon (includeIcon: true); Name only gets the icon-less MudLink - a folder row's MudLink navigates into the folder (navigateToFolder), a leaf row's wraps in EmitEditLink instead so clicking Code/Name opens the leaf's Edit page - matches HierarchicalDictionaryUiEmitter.EmitFolderLeafColumn exactly. Real PropertyColumn with a custom CellTemplate override, not a plain TemplateColumn - MudBlazor's DataGrid can only build a working filter predicate (Column.PropertyName, the operator dropdown, ...) off a genuine Property Expression, and a bare TemplateColumn with Filterable="true" but no Property renders a filter icon that doesn't actually work. PropertyColumn supports a fully custom CellTemplate too, so this loses nothing visually - confirmed against the hand-written Item/List.razor's own Code/Name columns, which use exactly this PropertyColumn+CellTemplate shape (and do have working filters) rather than TemplateColumn. property and valueSelector are the same lambda text passed twice (Expression for the filter binding, Func for reading the cell's display value) - same reasoning as every other field editor in this renderer: never call Expression.Compile() to read a value. |
EmitLinkedPropertyColumn<T0>(UiListColumnSpec<T0>, String, Func<T0, Boolean>) | A real PropertyColumn (keeps working filter/sort UI) whose cell content is wrapped in an Edit-page MudLink - the shared implementation behind ListForm's/ DocumentListForm's page.Columns loop and HierarchicalListForm's page.ExtraColumns loop. Compiles column's Property expression once per column (not per row/render) purely to read the display text for the link's content - everywhere else in this renderer takes a separate plain Func specifically to avoid Expression.Compile(), but no such Func exists on UiListColumnSpec today and a once-per-column compile is not the hot path that convention guards against. |
EmitTemplateColumn<T0>(String, Func<T0, String>, String, Func<T0, Boolean>) | A non-filterable/non-sortable grid column whose cell text comes from a plain delegate rather than a MudBlazor PropertyColumn Expression - used for anything an Expression<Func<TRecord, string?>> can't represent (a Guid FK resolved via bulk lookup, a hierarchical leaf/folder-only field pattern-matched off the shared root type, a non-string scalar rendered via ToString()). Shared by ListForm/DocumentListForm/HierarchicalListForm - all three already open this inside their own Columns RenderFragment scope. The cell content opens the Edit page (matches every other list column - see EmitEditLink), unless skipLinkWhen says otherwise - only HierarchicalListForm's ExtraTemplateColumns pass one, so a folder row's extra columns stay plain text (folder rows already navigate into the folder via their Code/Name cell, not to an Edit page). |
EmitViewTabs(Guid, String, String, String, Action, Func<Task<ChangeStateDto>>, Func<ChangeHistoryQueryDto, Task<PagedResult<ChangeHistoryEntryDto>>>, String, Func<Task<DocumentRegisterTransactionsDto>>, String, Func<Task<DocumentAccountTransactionsDto>>, String, Func<Task<DocumentLinksDto>>, IReadOnlyList<ValueTuple<String, String>>, String) | Shared Details/ChangeInfo/History MudTabs shape behind both DocumentViewForm and DictionaryViewForm - the two only differ in what goes inside the Details panel (a Document's embedded form takes a Print callback, a Dictionary's doesn't, and a hierarchical Dictionary picks Leaf/Folder by runtime type), which renderFormBody captures. ViewId is emitted here unconditionally - it's the one field neither a Document's nor a Dictionary's field-editor list ever renders itself. The Back button is emitted once, below the tabs (as a sibling, not inside any one tab panel) - matching the bottom-of-form placement every hand-written View page uses (e.g. Pages/Identity/Roles/View.razor's own trailing <BackViewButton />) and full-width like every other terminal button (AddExecuteButton/ AddPrintButton). It used to live inside the Details tab's own form (via AddBackButton), which meant switching to ChangeInfo/History left the page with no way back at all. |
GridItem(Int32, Action<IUiRenderer>) | MudItem xs="12" with an md breakpoint derived from widthPercent (MudBlazor's 12-column grid - e.g. 25% -> md="3", matching the hand-written Line.razor's own MudItem md values exactly), clamped to [1,12]. 100% (or any value >=100) omits the md override entirely - full width at every breakpoint, the same behavior a field had before GridItem existed. |
TablePart<T0>(String, String, ICollection<T0>, Func<T0>, Action<IUiRenderer, T0>, Boolean, Func<T0, Task>) | Matches the hand-written Lines.razor/Line.razor shape exactly: MudStack + caption MudText + MudTable Items="lines" with a two-column RowTemplate (a MudForm ReadOnly="readOnly" wrapping rowBody's own field editors, then a delete MudIconButton) + an Add MudButton below, both buttons skipped when readOnly. No HeaderContent - both cells' headers would be empty anyway (the row's own field Labels already caption each control; a header row here would just be dead vertical space, matching neither hand-written column ("Line"/"Actions", genuinely captioned) - so MudTable renders no <thead> at all rather than an empty one. |
Kandra.ClientLib.MudBlazor.Ui.IBlazorUiRenderer
Blazor/MudBlazor implementation surface, extending the framework-agnostic IUiRenderer with the page-shell primitives every generated List/Add/Update/View page needs. Kept out of the root interface because these shapes are inherently web-grid-shaped (MudDataGrid's GridState/GridData server-reload contract) - a future Avalonia host would want an ItemsControl/TreeDataGrid-shaped equivalent built on its own virtualization and column model, not this one.
Properties
| Property | Description |
|---|
RawBuilder | Low-level escape hatch onto whatever fluent frame is currently open - the current RenderTreeBuilder plus its own next-sequence-number counter. No longer needed by generated code itself (FormComponent covers the one case that used to require it), but still used internally by BlazorUiRenderer's own AuthorizedContent-nested helpers (e.g. add buttons inside ListForm/HierarchicalListForm) and kept public as a genuine fallback for anything the fluent vocabulary doesn't cover yet. |
Methods
| Method | Description |
|---|
AuthorizedContent(String, Action<IBlazorUiRenderer>) | A nested AuthorizeView with no NotAuthorized branch, for gating a single piece of content (e.g. an Add button) inside an already-authorized page - today's FlatDictionaryUiEmitter.EmitNestedAuthorizeView. A null/empty policy skips the wrapper entirely and always renders body. |
AuthorizedPage(String, String, String, Action<IBlazorUiRenderer>) | The AuthorizeView(Policy)+CascadingValue<string>(NavigationPrefix) wrapper every List/Add/Update/View page starts with (today's FlatDictionaryUiEmitter.EmitAuthorizedShell) - one fluent method reused by every page kind instead of duplicating the wrapper per page type. |
DictionaryViewForm(Guid, String, String, String, Action<IBlazorUiRenderer>, Func<Task<ChangeStateDto>>, Func<ChangeHistoryQueryDto, Task<PagedResult<ChangeHistoryEntryDto>>>) | Dictionary View page body - the same Details/ChangeInfo/History MudTabs shape as DocumentViewForm, generalized over renderForm instead of a single TForm/TRecord pair: a flat dictionary's View has exactly one Form component to embed, but a hierarchical dictionary's View picks between its Leaf/Folder Form by the loaded record's runtime type (see HierarchicalDictionaryUiEmitter.EmitView) - a callback handles both shapes uniformly without this method needing its own folder/leaf type parameters. The callback is expected to call FormComponent with title: "" (this method already renders the page-level title itself, via the caller's own preceding PageTitle call - same "empty Title skips the embedded form's own title+divider" convention DocumentViewForm relies on, see Kandra.ClientLib.MudBlazor.Shared.Pages.DictionaryFormBase{TRecord,TValidator}) and readOnly: true. Caller is expected to have already wrapped this in AuthorizedPage and emitted its own PageTitle, same as every other page-body method here. |
DocumentFormComponent<T0, T1>(T1, String, String, String, Boolean, EventCallback, Boolean, Func<Task>) | Same as FormComponent, additionally setting submitButtonText (SubmitButtonText) and onPrint (OnPrint, null for Add - nothing to print yet) - the Document-only attributes a generated {Dto}Form built on DocumentFormBase needs that a Dictionary form doesn't. |
DocumentListForm<T0, T1>(IDocumentListPage<T0, T1>) | Document List page body - date-range filter, a hard-coded read-only Date/Code/Status column trio (Status = the CheckCircle/RadioButtonUnchecked submitted/draft icon every hand-written Document List page already shows) ahead of page's own columns, then a grid whose row actions include Submit/Unsubmit alongside Delete - everything ListForm's body has, plus what a Document list adds. Caller is expected to have already wrapped this in AuthorizedPage, same as ListForm. |
DocumentViewForm<T0, T1>(T1, Boolean, String, String, String, Func<Task<ChangeStateDto>>, Func<ChangeHistoryQueryDto, Task<PagedResult<ChangeHistoryEntryDto>>>, Func<Task>, String, Func<Task<DocumentRegisterTransactionsDto>>, String, Func<Task<DocumentAccountTransactionsDto>>, String, Func<Task<DocumentLinksDto>>, IReadOnlyList<ValueTuple<String, String>>, String) | Document View page body - the Details/ChangeInfo/History MudTabs every hand-written Document View page already has (ChangeStateInfo/ChangeHistoryTable are already entity-kind-agnostic delegate-driven components, reused unchanged). Details opens with a ViewId (the one field the shared TForm never renders), then embeds TForm itself - the same generated {Dto}Form component Add/Update already use - via DocumentFormComponent with readOnly: true, rather than re-emitting a second copy of the same field editors/TablePart call. A Print icon button follows (skipped when onPrint is null - a Document with no print form); no separate Back button is needed here - the embedded TForm's own read-only branch already renders one (BuildRenderTree). Caller is expected to have already wrapped this in AuthorizedPage and emitted its own PageTitle, same as every other page-body method here. |
ExtendedFiltersPanel(String, Action<IUiRenderer>) | A collapsed-by-default panel wrapping body's fields - what a Report filters Dto's [ExtendedFilter]-marked fields render inside (matches every hand-written report page's own MudExpansionPanels/MudExpansionPanel wrapper). |
FormComponent<T0, T1>(T1, String, String, Boolean, EventCallback, Boolean) | Instantiates a shared, per-Dto generated child Form component (OpenComponent<TForm>(...) + its Title/ButtonText/Record/WaitingOperation/OnValidSubmit/ReadOnly attributes) inside an AuthorizedPage body. TForm is constrained to IEditForm so every attribute name is written via nameof(...) against that interface - compiler-checked - rather than bare string literals. The concrete Form type still can't be referenced generically anywhere the generator itself doesn't already know it by name (it doesn't exist until Kandra.Generators.Ui.Blazor creates it), so this is the one place generated code still names a concrete component type directly - everything about instantiating and wiring it up now lives here instead of in hand-emitted RenderTreeBuilder text. |
HierarchicalListForm<T0, T1, T2, T3>(IHierarchicalDictionaryListPage<T0, T1, T2, T3>) | Hierarchical/tree dictionary List page body - breadcrumbs, tree popover, up-button, dual add-buttons (leaf/folder), and a grid whose Code/Name columns render a folder icon+link or a leaf icon+text depending on IsFolderSelector (today's HierarchicalDictionaryUiEmitter.EmitFolderLeafColumn). Replaces HierarchicalDictionaryUiEmitter.EmitList's inner content. page bundles every value the body needs instead of a flat parameter list; the generated/hand-written List page itself is expected to implement it and pass this. |
ListForm<T0, T1>(IFlatDictionaryListPage<T0, T1>) | Flat dictionary List page body (Add button + MudDataGrid + pager) - replaces FlatDictionaryUiEmitter.EmitList's inner content. Caller is expected to have already wrapped this in AuthorizedPage. page bundles every value the body needs - see IFlatDictionaryListPage - instead of a flat parameter list; the generated/ hand-written List page itself is expected to implement it and pass this. |
LoadingIndicator | Full-page MudProgressCircular for the gap between a page existing and its Record resolving - e.g. a hierarchical Add/Update/View page before its async GetNew/Get call determines the concrete Leaf/Folder type. Flat dictionary pages never need this: their generated Add/Update/View always assign Record a synchronous placeholder (new {Dto}()) in OnInitialized before the async load happens, so Record is never null there - hierarchical's abstract root Dto genuinely can't be instantiated until the concrete Leaf/Folder type is known, which the synchronous shortcut can't do. |
ReportColumn<T0, T1>(String, Expression<Func<T0, T1>>, Boolean, Boolean, Boolean, Nullable<SortDirection>, AggregateDefinition<T0>, Action<Boolean>, String, RenderFragment<CellContext<T0>>) | One report result grid column. TValue is resolved per-field by the generator (string/decimal/DateTime/bool/enum), same "one generic call per typed field, T picked at emit time" idiom AddNumericEditor and friends already use. format is the column's own [Format] string (e.g. "N3", "d") - the same one Kandra.Printing.PrintValueFormatter applies for the print/export version of this column, so the grid cell renders identically instead of a raw ToString() (a decimal with no format prints every fractional digit). aggregate is a fully-built MudBlazor.AggregateDefinition<TRecord> - the generator constructs it inline (including any Custom-aggregate static call, itself passed format too - see ICustomAggregate), so this method stays a thin pass-through, not a Kind/CustomType interpreter. cellTemplate overrides the default value rendering (used only for a [Format("A|B")] bool column today) - format is ignored by MudBlazor whenever cellTemplate is set, since the template fully owns cell rendering. |
ReportResultGrid<T0>(IEnumerable<T0>, Boolean, Action<IBlazorUiRenderer>) | Opens a client-side (Items=, not ServerData=) MudDataGrid<TRecord> - Groupable="true" Filterable="true", matching every hand-written report grid - invokes columnsBody to add ReportColumn calls, then closes. A genuinely new grid mode: every other generated grid (ListForm/ DocumentListForm) is server-paged; a report grid loads its whole (already server-filtered) result set once via IReportService.RunAsync and then sorts/filters/groups entirely client-side over that in-memory collection. |
Kandra.ClientLib.MudBlazor.Ui.IBlazorUiRendererFactory
A generated component's BuildRenderTree doesn't own a renderer instance across renders (its lifetime doesn't match one render pass, and mutable per-render state - the frame stack - can't be shared across concurrent component instances of the same generated type). Instead the component injects this factory and asks for a fresh renderer bound to that call's RenderTreeBuilder at the top of every BuildRenderTree. owner is the generated component itself - event callbacks are created against it (EventCallback.Factory.Create(owner, ...), matching every this receiver in today's hand-emitted codegen) so Blazor's runtime auto-triggers StateHasChanged on the right component after the callback runs; the renderer itself is not a component and can't be the receiver.
Kandra.ClientLib.MudBlazor.Ui.IDataProcessorPage
DataProcessor counterpart to IAddPage - a generated DataProcessor page is a single routed screen (no List/Add/Update/View split, see DataProcessorPageBase's own doc comment), so this is the only page-contract interface it needs.
Kandra.ClientLib.MudBlazor.Ui.IDictionaryAddPage<T0>
Record is exposed get-only from the interface's perspective - every current renderer call site only reads it, never assigns through the interface.
Kandra.ClientLib.MudBlazor.Ui.IDictionaryListPage
Pure marker today - no dictionary-only List behavior exists beyond what IListPage already declares.
The Document-only extras IEditForm doesn't carry - a separate interface rather than adding these to the base one, since a Dictionary form (no submit/unsubmit lifecycle, no print form) has no meaningful value for either. Implemented by DocumentFormBase, referenced by DocumentFormComponent.
Properties
| Property | Description |
|---|
OnPrint | Null (Add - nothing to print yet) skips the Print button entirely; Update/View both supply their own PrintRecord (now a single shared method on UpdateDocument{TItem,TQuery}/ ViewDocument{TItem,TQuery} respectively, not per-Dto generated code - see their own doc comments). Rendered by DocumentFormBase immediately before the terminal Save/Submit/Cancel or Back button, so its position is unified across both Update and View without either page needing to say so itself. |
Kandra.ClientLib.MudBlazor.Ui.IDocumentListPage
SubmitRecord/UnsubmitRecord are declared on ListBase for every List page but only ever meaningfully overridden (via DtoServiceSubmitRecord/DtoServiceUnsubmitRecord) by ListDocument - dictionaries leave them as no-ops, so these are the first genuinely document-specific members in this hierarchy.
Kandra.ClientLib.MudBlazor.Ui.IDocumentListPage<T0, T1>
Everything DocumentListForm needs, bundled behind one interface instead of a flat parameter list - mirrors IFlatDictionaryListPage exactly for the paging/grid/policy members, plus what a Document list adds on top: SubmitPolicy (gates the submit/unsubmit buttons IDocumentListPage already declares) and DatesRange/OnRangeChanged for the date-range filter every Document list shows (matches ListDocument<TItem,TQuery>'s existing DatesRange field). Implemented by DocumentListPage<TItem,TQuery> (the paging/grid members, non-abstract) and by each generated/ hand-written leaf {Dto}List (the per-entity members, since the base stays abstract for them).
Properties
| Property | Description |
|---|
DatesRange | Same nullable DateRangeDto type as ListDocument<TItem,TQuery>.DatesRange and DateRangeEditor.Value - null means "no range selected" (List pages always populate a default last-month-to-now range on init, so this is effectively never null in practice). |
TemplateColumns | Columns that can't be expressed as a UiListColumnSpec Expression - see TemplateColumns's doc comment for the full rationale (same shape, reused unchanged for Documents). |
Implemented by DictionaryFormBase<TRecord,TValidator>/DictionaryFormBaseUnvalidated<TRecord> (Kandra.ClientLib.MudBlazor.Shared.Pages) purely so IBlazorUiRenderer.FormComponent can constrain its TForm type parameter and reference these exact property names via nameof(...) - checked by the compiler against this interface - instead of "Title"/"Record"/... string literals with nothing backing them.
Kandra.ClientLib.MudBlazor.Ui.IFlatDictionaryListPage<T0, T1>
Everything ListForm needs, bundled behind one interface instead of a flat parameter list - implemented by FlatDictionaryListPage<TItem,TQuery> (the paging/grid members, non-abstract) and by each generated/hand-written leaf {Dto}List (the five per-entity members, since the base stays abstract for them).
Properties
| Property | Description |
|---|
TemplateColumns | Columns that can't be expressed as a UiListColumnSpec Expression (a Guid FK resolved via bulk lookup, a non-string scalar) - defaults to empty on FlatDictionaryListPage<TItem,TQuery>, only overridden by entities that actually have one. |
Kandra.ClientLib.MudBlazor.Ui.IHierarchicalDictionaryListPage<T0, T1, T2, T3>
Everything HierarchicalListForm needs, bundled behind one interface instead of a flat parameter list - implemented by HierarchicalDictionaryListPage<TRootDto,TFolderDto,TLeafDto,TQuery> (the breadcrumb/navigation/ grid members, non-abstract) and by each generated/hand-written leaf {Dto}List (the seven per-entity members, since the base stays abstract for them). TRoot is constrained to IDictionaryDto (not just IEntityDto) purely so NameSelector can be implemented once, non-abstractly, on the base - every concrete root hierarchical dictionary Dto already extends HierarchicalDictionaryItemDto : DictionaryDto : IDictionaryDto.
Properties
| Property | Description |
|---|
ExtraTemplateColumns | Columns that can't be expressed as a UiListColumnSpec Expression (a Guid FK resolved via bulk lookup, a non-string scalar, or any field only declared on the leaf/folder subclass rather than the shared root type) - defaults to empty on HierarchicalDictionaryListPage<...>, only overridden by entities that actually have one. |
Kandra.ClientLib.MudBlazor.Ui.IListPage
Implemented independently by both ListBase<TItem> (covers ListDictionary/FlatDictionaryListPage/ ListDocument) and HierarchicalDictionaryListPage<...> - the two do not share a common class ancestor (HierarchicalDictionaryListPage inherits RenderedPage directly, not ListBase, per its own doc comment), so this is a structural/interface contract, not a class-hierarchy one. Deliberately does not extend IWaitingOperationPage - that flag is declared on ListBase but never assigned or consumed by any List page today.
Kandra.ClientLib.MudBlazor.Ui.INavigationPrefixPage
Read-only navigation-prefix contract. Every List/Add/Update/View page (hand-written or generated, flat or hierarchical) supplies its own route stem as an expression-bodied override (public override string NavigationPrefix => "/dictionary/branch";) instead of assigning a mutable field/property in OnInitialized - the value is never reassigned after construction anywhere in the codebase, including on HierarchicalDictionaryListPage, whose folder-scoped paging only ever changes the {FolderId} route segment appended after this value, never this value itself.
Kandra.ClientLib.MudBlazor.Ui.IWaitingOperationPage
A live view-model flag toggled around each async load/save call (see AddBase.CreateRecord, UpdateBase.UpdateRecord, ViewBase.OnParametersSetAsync) - unlike INavigationPrefixPage this keeps its setter, since the page itself mutates it during its own lifecycle rather than declaring it once.
Kandra.ClientLib.MudBlazor.Ui.MaterialIconResolver
Resolves an Interface node's plain-string Icon (e.g. "Description") to the actual SVG-path constant it names, by reflecting over MudBlazor.Icons.Material.Filled - the same field a hand-written page would reference directly as Icons.Material.Filled.Description.
Kandra.ClientLib.MudBlazor.Ui.UiListColumnSpec<T0>
One MudDataGrid column - Property feeds MudBlazor's own PropertyColumn<TRecord,object?>, which is what drives its built-in filter-expression building; a plain delegate wouldn't work there. The first real consumer of UiFieldModel's existing but currently-unused ListFormColumn/ListFormColumnSortable/ListFormColumnFilterable fields (Kandra.Generators.Shared/UiFieldDiscovery.cs).
Constructors
| Constructor | Description |
|---|
UiListColumnSpec(String, Expression<Func<T0, String>>, Boolean, Boolean) | One MudDataGrid column - Property feeds MudBlazor's own PropertyColumn<TRecord,object?>, which is what drives its built-in filter-expression building; a plain delegate wouldn't work there. The first real consumer of UiFieldModel's existing but currently-unused ListFormColumn/ListFormColumnSortable/ListFormColumnFilterable fields (Kandra.Generators.Shared/UiFieldDiscovery.cs). |
Kandra.ClientLib.MudBlazor.Ui.UiListTemplateColumnSpec<T0>
A list column whose display text can't be expressed as a UiListColumnSpec Expression - a Guid FK resolved through a bulk-fetched lookup dictionary (expression trees can't represent a dictionary lookup with a fallback), a hierarchical leaf/folder-only field (not a member of the shared root type, needs an is pattern-match), or any non-string scalar rendered via ToString(). Always non-filterable/non-sortable - rendered via MudBlazor's TemplateColumn, matching the established hand-written convention (see KandraWms.Client.MudBlazor/Pages/Dictionary/Item/List.razor's Category/ UnitOfMeasure columns). ValueSelector is a plain delegate, not an expression tree, so pattern-matching and dictionary lookups both work fine here.
Constructors
| Constructor | Description |
|---|
UiListTemplateColumnSpec(String, Func<T0, String>) | A list column whose display text can't be expressed as a UiListColumnSpec Expression - a Guid FK resolved through a bulk-fetched lookup dictionary (expression trees can't represent a dictionary lookup with a fallback), a hierarchical leaf/folder-only field (not a member of the shared root type, needs an is pattern-match), or any non-string scalar rendered via ToString(). Always non-filterable/non-sortable - rendered via MudBlazor's TemplateColumn, matching the established hand-written convention (see KandraWms.Client.MudBlazor/Pages/Dictionary/Item/List.razor's Category/ UnitOfMeasure columns). ValueSelector is a plain delegate, not an expression tree, so pattern-matching and dictionary lookups both work fine here. |