cabloy 5.1.174 → 5.1.175
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cabloy-version +1 -1
- package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +4 -0
- package/CHANGELOG.md +18 -0
- package/package.json +2 -2
- package/repo-docs/.vitepress/config.mjs +7 -0
- package/repo-docs/frontend/a-model-under-the-hood.md +11 -0
- package/repo-docs/frontend/behavior-guide.md +73 -0
- package/repo-docs/frontend/column-configuration-guide.md +254 -0
- package/repo-docs/frontend/component-guide.md +2 -29
- package/repo-docs/frontend/controller-boundary-guide.md +229 -0
- package/repo-docs/frontend/introduction.md +4 -0
- package/repo-docs/frontend/page-route-guide.md +6 -0
- package/repo-docs/frontend/resource-picker-guide.md +223 -0
- package/repo-docs/frontend/rest-resource-source-reading-map.md +37 -21
- package/repo-docs/frontend/rest-resource-under-the-hood.md +50 -30
- package/repo-docs/frontend/routed-dialog-guide.md +471 -0
- package/repo-docs/frontend/router-stack-guide.md +4 -2
- package/repo-docs/frontend/router-view-hosts-guide.md +3 -2
- package/repo-docs/frontend/ssr-client-only.md +6 -0
- package/repo-docs/frontend/table-guide.md +2 -1
- package/repo-docs/frontend/table-resource-crud-cookbook.md +1 -0
- package/repo-docs/frontend/use-state-data-best-practices.md +26 -0
- package/repo-e2e/specs/cabloy-basic.spec.ts +11 -0
- package/repo-e2e/specs/controller-boundary.spec.ts +1 -2
- package/test-results/.last-run.json +1 -1
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/utils/util.ts +1 -0
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-layoutprofile/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-layoutprofile/test/layoutprofile.test.ts +2 -3
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/service/executor.ts +6 -1
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/package.original.json +1 -1
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/src/suite/a-commerce/modules/commerce-catalog/src/page/product/controller.tsx +4 -3
- package/zova/src/suite/a-commerce/modules/commerce-member/src/page/address/controller.tsx +4 -3
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionAdjustStock/controller.tsx +3 -2
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionRefund/controller.tsx +16 -20
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionShip/controller.tsx +3 -2
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/cart/controller.tsx +6 -5
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +5 -4
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/order/controller.tsx +4 -4
- package/zova/src/suite/a-demo/modules/demo-basic/src/page/controllerBoundary/controller.tsx +2 -2
- package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionDeleteForce.tsx +4 -4
- package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx +4 -4
- package/zova/src/suite/cabloy-basic/modules/basic-app/src/monkey.ts +11 -0
- package/zova/src/suite/cabloy-basic/modules/basic-app/src/types/appModal.ts +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-app/test/lib/routedDialogContext.test.ts +60 -0
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionUpdate.tsx +4 -4
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/actionCreate/controller.tsx +4 -4
- package/zova/src/suite/cabloy-basic/modules/basic-metrics/src/page/dashboard/controller.tsx +8 -2
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/bean/tableCell.actionUpdate.tsx +4 -4
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/actionCreate/controller.tsx +4 -4
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/actionDeleteBulk/controller.tsx +4 -4
- package/zova/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/component/paymentNextAction/controller.tsx +6 -5
- package/zova/src/suite-vendor/a-pay/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-router/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/monkey.ts +17 -3
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +1 -2
- package/zova/src/suite-vendor/a-zova/package.json +3 -4
- package/zova/pnpm-lock.yaml +0 -15375
- package/zova/src/suite-vendor/a-zova/modules/a-boundary/LICENSE +0 -21
- package/zova/src/suite-vendor/a-zova/modules/a-boundary/package.json +0 -52
- package/zova/src/suite-vendor/a-zova/modules/a-boundary/src/.metadata/index.ts +0 -26
- package/zova/src/suite-vendor/a-zova/modules/a-boundary/src/.metadata/this.ts +0 -2
- package/zova/src/suite-vendor/a-zova/modules/a-boundary/src/index.ts +0 -1
- package/zova/src/suite-vendor/a-zova/modules/a-boundary/tsconfig.build.json +0 -13
- package/zova/src/suite-vendor/a-zova/modules/a-boundary/tsconfig.json +0 -5
package/.cabloy-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.1.
|
|
1
|
+
5.1.175
|
|
@@ -183,6 +183,7 @@ Check whether the feature needs:
|
|
|
183
183
|
- SSR hydration-equivalence review: classify state as SSR-required or intentionally deferred; keep server HTML and the hydration-time client render equivalent; defer private, cookie-unavailable, or browser-only query/load/render branches to an explicit post-hydration, admission, mounted, or interaction boundary
|
|
184
184
|
- distinguish `$useStateData(...)` query ownership from readiness waits: `disableSuspenseOnInit` only skips its init-time suspense kick and does not prevent query creation or fetches; choose `$QueryEnsureLoaded(...)` or freshness helpers only at the later boundary that needs them
|
|
185
185
|
- verify that render-driving UI reads model/query-owned reactive state (`query.data` or a model-derived surface); keep awaited `refetch()` results local to one-shot interaction/orchestration and never as a parallel ongoing controller/render state copy
|
|
186
|
+
- refetch error-boundary ownership: when an interaction boundary such as `ZButton onPerform` should own generic query-refetch failure, return or await `query.refetch({ throwOnError: true })`; when local/domain-specific UI owns recovery, retain `result.error`, `query.error`, or a local catch instead; `bypassPersister` controls per-fetch persistence only and can be combined with either deliberate error route
|
|
186
187
|
- reverse fullstack handoff when newly added frontend resources will later be consumed by backend metadata or backend tooling
|
|
187
188
|
|
|
188
189
|
If the frontend change introduces resources such as a custom form-field renderer, table-cell renderer, or other generated metadata that backend `ZovaRender.field(...)` / `ZovaRender.cell(...)` will consume, do not treat the task as frontend-only cleanup.
|
|
@@ -203,6 +204,7 @@ Check whether the feature needs:
|
|
|
203
204
|
- generic component conversion
|
|
204
205
|
- style/theme/icon updates
|
|
205
206
|
- wrapper usage review
|
|
207
|
+
- async interaction ownership: for a button-only action, return or await the complete action through `ZButton onPerform`, choose one error presentation owner rather than combining its generic alert with local query/error UI, and do not mirror the same lifecycle with button-local `loading` / `disabled` state; retain explicit state only for independently initiated or broader shared work
|
|
206
208
|
- async-loading or controllerRef implications
|
|
207
209
|
|
|
208
210
|
### Verification
|
|
@@ -216,6 +218,7 @@ Check whether the feature needs:
|
|
|
216
218
|
- SSR or route-path verification
|
|
217
219
|
- hydration-time initial-render equivalence when SSR, private state, browser-only state, or async model state changes
|
|
218
220
|
- edition-specific flavor, SSR site baseline, and project-asset verification
|
|
221
|
+
- interaction failure-path verification when `ZButton onPerform` owns a query action: a failed refetch reaches exactly the intended `onError`, generic alert, or local error UI, and button loading resets
|
|
219
222
|
|
|
220
223
|
### SSR theme review reminder
|
|
221
224
|
|
|
@@ -251,6 +254,7 @@ Especially relevant pages include:
|
|
|
251
254
|
- `repo-docs/frontend/route-alias-guide.md`
|
|
252
255
|
- `repo-docs/frontend/navigation-guards-guide.md`
|
|
253
256
|
- `repo-docs/frontend/component-guide.md`
|
|
257
|
+
- `repo-docs/frontend/behavior-guide.md` for `ZButton` / Behavior action loading and error-boundary ownership
|
|
254
258
|
- `repo-docs/frontend/form-layout-guide.md` for schema-driven field placement, Grid/flow selection, groups, tabs, or embedded filter actions
|
|
255
259
|
- `repo-docs/frontend/component-props-guide.md`
|
|
256
260
|
- `repo-docs/frontend/component-v-model-guide.md`
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.1.175
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Add `$routedDialog` support for route-driven dialogs.
|
|
8
|
+
- Remove the `a-boundary` component.
|
|
9
|
+
- Update core functionality and related integrations.
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
- Fix end-to-end test failures.
|
|
14
|
+
|
|
15
|
+
### Improvements
|
|
16
|
+
|
|
17
|
+
- Refactor `ZButton`.
|
|
18
|
+
- Update table cell force-delete actions and related test coverage.
|
|
19
|
+
- Document controller load boundaries, column layouts, resource pickers, routed dialogs, error handling, and `ZButton` usage.
|
|
20
|
+
|
|
3
21
|
## 5.1.174
|
|
4
22
|
|
|
5
23
|
### Improvements
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cabloy",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.175",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "A Node.js fullstack framework",
|
|
6
6
|
"keywords": [
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@cabloy/dotenv": "^1.2.8",
|
|
79
|
-
"@cabloy/lint": "^5.1.
|
|
79
|
+
"@cabloy/lint": "^5.1.35",
|
|
80
80
|
"@cabloy/process-helper": "^3.1.3",
|
|
81
81
|
"@playwright/test": "^1.62.1",
|
|
82
82
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -503,6 +503,7 @@ export default defineConfig({
|
|
|
503
503
|
{ text: 'A-Router Guide', link: '/frontend/a-router-guide' },
|
|
504
504
|
{ text: 'Zova Router Under the Hood', link: '/frontend/zova-router-under-the-hood' },
|
|
505
505
|
{ text: 'Router View Hosts Guide', link: '/frontend/router-view-hosts-guide' },
|
|
506
|
+
{ text: 'Routed Dialog Guide', link: '/frontend/routed-dialog-guide' },
|
|
506
507
|
{ text: 'Router Tabs Introduction', link: '/frontend/router-tabs-introduction' },
|
|
507
508
|
{ text: 'Router Tabs Overview', link: '/frontend/router-tabs-overview' },
|
|
508
509
|
{ text: 'Router Tabs Mechanism', link: '/frontend/router-tabs-mechanism' },
|
|
@@ -529,7 +530,12 @@ export default defineConfig({
|
|
|
529
530
|
text: 'Components & UI',
|
|
530
531
|
items: [
|
|
531
532
|
{ text: 'Component Guide', link: '/frontend/component-guide' },
|
|
533
|
+
{
|
|
534
|
+
text: 'Controller Load Boundary Guide',
|
|
535
|
+
link: '/frontend/controller-boundary-guide',
|
|
536
|
+
},
|
|
532
537
|
{ text: 'Form Guide', link: '/frontend/form-guide' },
|
|
538
|
+
{ text: 'Resource Picker Guide', link: '/frontend/resource-picker-guide' },
|
|
533
539
|
{ text: 'Form Layout Guide', link: '/frontend/form-layout-guide' },
|
|
534
540
|
{ text: 'Image Guide', link: '/frontend/image-guide' },
|
|
535
541
|
{ text: 'File Guide', link: '/frontend/file-guide' },
|
|
@@ -555,6 +561,7 @@ export default defineConfig({
|
|
|
555
561
|
link: '/frontend/permission-formscene-action-visibility-guide',
|
|
556
562
|
},
|
|
557
563
|
{ text: 'Table Guide', link: '/frontend/table-guide' },
|
|
564
|
+
{ text: 'Column Configuration Guide', link: '/frontend/column-configuration-guide' },
|
|
558
565
|
{ text: 'TableCell Authoring Cookbook', link: '/frontend/table-cell-cookbook' },
|
|
559
566
|
{
|
|
560
567
|
text: 'Table + Resource CRUD Cookbook',
|
|
@@ -225,6 +225,17 @@ This option is not a force-new-request primitive. The model wrapper preserves Ta
|
|
|
225
225
|
|
|
226
226
|
The current implementation transports the marker through the installed TanStack Query observer's internal fetch-options forwarding. `bypassPersister` is therefore a Zova model option, not a native public TanStack `RefetchOptions` field; the model wrapper keeps that internal detail out of the normal authoring surface.
|
|
227
227
|
|
|
228
|
+
`QueryRefetchOptions` extends TanStack `RefetchOptions` with only `bypassPersister`. The wrapper removes that Zova-specific option and forwards the remaining native options, including `throwOnError`. When one interaction needs both policies, combine them explicitly:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
await query.refetch({
|
|
232
|
+
bypassPersister: true,
|
|
233
|
+
throwOnError: true,
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The options remain independent: `bypassPersister` selects the persistence path for this fetch, while `throwOnError` controls whether a fetch failure rejects the awaited interaction. Neither changes query ownership, cancellation, or in-flight deduplication semantics. For choosing between an action-level failure boundary and local query-error UI, see [`$useStateData` Best Practices](/frontend/use-state-data-best-practices#choose-the-refetch-error-boundary).
|
|
238
|
+
|
|
228
239
|
## State helper families as one runtime family
|
|
229
240
|
|
|
230
241
|
The state helper layer lives mainly in:
|
|
@@ -337,6 +337,79 @@ Representative usage:
|
|
|
337
337
|
|
|
338
338
|
This is a clean example of attaching a behavior to a native element through a generated `bs-*` attribute.
|
|
339
339
|
|
|
340
|
+
### Perform a standalone asynchronous button action
|
|
341
|
+
|
|
342
|
+
Cabloy Basic's `ZButton` attaches the `basic-button:perform` behavior to its native button. `BehaviorPerform` owns the button-local lifecycle for an action passed through `onPerform`:
|
|
343
|
+
|
|
344
|
+
- loading presentation and `aria-busy`
|
|
345
|
+
- temporary disabled state and duplicate-click suppression
|
|
346
|
+
- the ordinary action-error alert, unless `onError` handles the error
|
|
347
|
+
- cleanup in `finally` after the action completes or fails
|
|
348
|
+
|
|
349
|
+
Pass the complete interaction promise through `onPerform`. For a button-only action, do not mirror the same invocation with controller-local busy state or `loading` / `disabled` props:
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
// Correct: BehaviorPerform awaits the complete save action.
|
|
353
|
+
<ZButton onPerform={() => this.save()}>{this.scope.locale.Save()}</ZButton>
|
|
354
|
+
|
|
355
|
+
// Correct: the loading boundary includes every step of this interaction.
|
|
356
|
+
<ZButton
|
|
357
|
+
onPerform={async () => {
|
|
358
|
+
await this.save();
|
|
359
|
+
await this.$router.push(nextPath);
|
|
360
|
+
}}
|
|
361
|
+
>
|
|
362
|
+
{this.scope.locale.Save()}
|
|
363
|
+
</ZButton>
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Returning the complete promise is not enough when an API reports its failure as a result value. When a `ZButton` owns the generic error path for a query refetch, await a refetch that rejects on failure:
|
|
367
|
+
|
|
368
|
+
```tsx
|
|
369
|
+
<ZButton
|
|
370
|
+
onPerform={async () => {
|
|
371
|
+
await query.refetch({ throwOnError: true });
|
|
372
|
+
}}
|
|
373
|
+
>
|
|
374
|
+
{this.scope.locale.Refresh()}
|
|
375
|
+
</ZButton>
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
The rejection reaches `BehaviorPerform`, which calls `onError` or its default action-error alert and then clears loading in `finally`. `bypassPersister` controls persistence behavior only; it does not make `refetch()` reject. Catch the failure locally only when that layer intentionally owns recovery or error presentation. For the query-state ownership decision and avoiding duplicate local and generic feedback, see [`$useStateData` Best Practices](/frontend/use-state-data-best-practices#choose-the-refetch-error-boundary).
|
|
379
|
+
|
|
380
|
+
Do not detach asynchronous work from the callback. In the following shape, the callback returns `void`, so the behavior clears its button-local loading state before `save()` finishes and cannot handle its rejection:
|
|
381
|
+
|
|
382
|
+
```tsx
|
|
383
|
+
// Incorrect: the save promise is outside BehaviorPerform's boundary.
|
|
384
|
+
<ZButton
|
|
385
|
+
onPerform={() => {
|
|
386
|
+
void this.save();
|
|
387
|
+
}}
|
|
388
|
+
>
|
|
389
|
+
{this.scope.locale.Save()}
|
|
390
|
+
</ZButton>
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
`loading` and `disabled` remain valid when they represent pending work owned outside this button's `onPerform`, such as an operation started by another control or a native-form/Enter-key submission path. In that case, the button must reflect a broader shared lifecycle:
|
|
394
|
+
|
|
395
|
+
```tsx
|
|
396
|
+
<ZButton
|
|
397
|
+
type="submit"
|
|
398
|
+
loading={this.formRef.formState.isSubmitting}
|
|
399
|
+
disabled={this.formRef.formState.isSubmitting}
|
|
400
|
+
onClick={event => {
|
|
401
|
+
event.preventDefault();
|
|
402
|
+
}}
|
|
403
|
+
onPerform={() => this.submitFromButton()}
|
|
404
|
+
>
|
|
405
|
+
{this.scope.locale.Save()}
|
|
406
|
+
</ZButton>
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
A genuine `ZForm` submission still owns form-wide validation, submission state, and form error handling even when one visible trigger is a `ZButton`. Use form state when another submit path can bypass the button; otherwise let `BehaviorPerform` be the sole owner of the button-local lifecycle.
|
|
410
|
+
|
|
411
|
+
The source contract is implemented by `basic-button`'s `ControllerButton` and `BehaviorPerform`. For form ownership and submission paths, see the [Form Guide](/frontend/form-guide).
|
|
412
|
+
|
|
340
413
|
### Form-field behavior composition
|
|
341
414
|
|
|
342
415
|
The form-field controller composes behaviors from two sources:
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# Column Configuration Guide
|
|
2
|
+
|
|
3
|
+
Column Configuration lets authenticated users save a personal table layout on a resource list page. They can change the visibility, width, and order of eligible columns; the layout is restored when they revisit the same page.
|
|
4
|
+
|
|
5
|
+
This guide covers Column Configuration on standard resource list pages in both Cabloy Basic and Cabloy Start. It complements the [Table Guide](/frontend/table-guide), which explains the general `ZTable`, schema-metadata, and `tableCell` authoring surfaces. This page focuses on user layouts and their persistence.
|
|
6
|
+
|
|
7
|
+
## What Column Configuration does
|
|
8
|
+
|
|
9
|
+
After opening **Column Configuration** from the toolbar, a user can:
|
|
10
|
+
|
|
11
|
+
| Capability | Description |
|
|
12
|
+
| ---------------------- | ----------------------------------------------------------------------------------- |
|
|
13
|
+
| Show or hide columns | Only columns eligible in the current table schema can be changed |
|
|
14
|
+
| Set a numeric width | The width must be an integer from `1` through `2000` |
|
|
15
|
+
| Use automatic width | Set the width to `auto` and let the table handle it from content and runtime layout |
|
|
16
|
+
| Reorder columns | Reorder only within the same fixed region |
|
|
17
|
+
| Save a personal layout | Save it for the current authenticated user and page path |
|
|
18
|
+
| Reset the layout | Remove the personal layout and restore current schema defaults |
|
|
19
|
+
|
|
20
|
+
Column Configuration does not change backend DTOs, database fields, API permissions, or the schema itself. It is a personal presentation preference over already authorized data; it must not be used as an authorization or data-isolation mechanism.
|
|
21
|
+
|
|
22
|
+
### Fixed regions define the reorder boundary
|
|
23
|
+
|
|
24
|
+
A schema can pin a column to the `left`, `center`, or `right` region. The Move Up and Move Down controls in Column Configuration work only within the same region:
|
|
25
|
+
|
|
26
|
+
- A left-fixed column can move only among left-fixed columns.
|
|
27
|
+
- A center column can move only among center columns.
|
|
28
|
+
- A right-fixed column can move only among right-fixed columns.
|
|
29
|
+
|
|
30
|
+
A user cannot change a column's fixed region through Column Configuration. The row-selection column is also a runtime-generated column and is never saved in a personal layout.
|
|
31
|
+
|
|
32
|
+
## User workflow
|
|
33
|
+
|
|
34
|
+
1. Sign in and open a resource list page.
|
|
35
|
+
2. Click the settings icon for **Column Configuration** in the bulk toolbar.
|
|
36
|
+
3. In the dialog, as needed:
|
|
37
|
+
- select or clear a column to control its visibility;
|
|
38
|
+
- enter a numeric width or choose automatic width;
|
|
39
|
+
- use the Move Up and Move Down controls to reorder columns in the same fixed region.
|
|
40
|
+
4. Click **Save** to persist the personal layout and refresh the current table.
|
|
41
|
+
5. To return to defaults, click **Reset**, then click **Save**.
|
|
42
|
+
|
|
43
|
+
Unauthenticated users cannot load, save, or reset personal column layouts. Even when the toolbar action uses `permission: { public: true }`, that setting controls whether the action is discoverable; it does not allow anonymous reads or writes of personal layouts.
|
|
44
|
+
|
|
45
|
+
## Basic and Start integration
|
|
46
|
+
|
|
47
|
+
Both editions share the same layout contract and persistence model. Page-block names, action resource keys, and UI components are edition-specific.
|
|
48
|
+
|
|
49
|
+
| Edition | Toolbar block | Column-configuration action | UI layer |
|
|
50
|
+
| ------------ | ----------------------------- | -------------------------------- | ---------------------- |
|
|
51
|
+
| Cabloy Basic | `basic-page:blockToolbarBulk` | `basic-table:actionColumnConfig` | DaisyUI + Tailwind CSS |
|
|
52
|
+
| Cabloy Start | `start-page:blockToolbarBulk` | `start-table:actionColumnConfig` | Vuetify |
|
|
53
|
+
|
|
54
|
+
Current CRUD generator templates add the edition-specific Column Configuration action. Add the action explicitly when authoring a custom or existing resource page.
|
|
55
|
+
|
|
56
|
+
### Cabloy Basic
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
ZovaRender.block('basic-page:blockToolbarBulk', {
|
|
60
|
+
actions: [
|
|
61
|
+
ZovaRender.tableActionBulk('basic-table:actionColumnConfig', {
|
|
62
|
+
permission: { public: true },
|
|
63
|
+
placement: 'end',
|
|
64
|
+
}),
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Place this action in a standard resource list page hosted by `basic-page:blockPage`, together with `basic-page:blockTable`.
|
|
70
|
+
|
|
71
|
+
### Cabloy Start
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
ZovaRender.block('start-page:blockToolbarBulk', {
|
|
75
|
+
actions: [
|
|
76
|
+
ZovaRender.tableActionBulk('start-table:actionColumnConfig', {
|
|
77
|
+
permission: { public: true },
|
|
78
|
+
placement: 'end',
|
|
79
|
+
}),
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Place this action in a standard resource list page hosted by `start-page:blockPage`, together with `start-page:blockTable`.
|
|
85
|
+
|
|
86
|
+
> [!TIP]
|
|
87
|
+
> Do not copy Basic `basic-*` action or block names into a Start page, or the reverse. The editions share the layout protocol but register different page and table resources.
|
|
88
|
+
|
|
89
|
+
## Schema defaults and personal layouts
|
|
90
|
+
|
|
91
|
+
Column Configuration has two layers: the schema defines the default table columns, and a personal layout overrides visibility, width, and order within permitted boundaries.
|
|
92
|
+
|
|
93
|
+
### 1. Define default columns in the schema
|
|
94
|
+
|
|
95
|
+
Use `ZovaRender.order(...)`, `ZovaRender.visible(...)`, and `ZovaRender.column(...)` on row-DTO or entity fields:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
@Api.field(
|
|
99
|
+
v.title($locale('StudentName')),
|
|
100
|
+
ZovaRender.order(1),
|
|
101
|
+
ZovaRender.column({
|
|
102
|
+
align: 'left',
|
|
103
|
+
width: 240,
|
|
104
|
+
fixed: 'left',
|
|
105
|
+
enableSorting: true,
|
|
106
|
+
sortDescFirst: true,
|
|
107
|
+
}),
|
|
108
|
+
v.string(),
|
|
109
|
+
)
|
|
110
|
+
name: string;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The three metadata categories have distinct responsibilities:
|
|
114
|
+
|
|
115
|
+
| Metadata | Responsibility |
|
|
116
|
+
| --------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
117
|
+
| `ZovaRender.visible(false)` | Sets schema/scene visibility. A hidden field does not become a configurable table column. |
|
|
118
|
+
| `ZovaRender.order(...)` | Defines the schema default order. It is normally clearer than setting `order` inside `ZovaRender.column(...)`. |
|
|
119
|
+
| `ZovaRender.column(...)` | Defines physical-column capabilities: `align`, `width`, `fixed`, and sorting capabilities. |
|
|
120
|
+
| `ZovaRender.cell(...)` | Selects a cell-render resource; it does not persist a layout. |
|
|
121
|
+
|
|
122
|
+
The `width` and `fixed` values in `ZovaRender.column(...)` are schema defaults. `fixed` also defines the region in which a user may reorder the column. For a standard resource list page, the backend row schema is the contract truth for column metadata; do not maintain a parallel frontend list of default columns.
|
|
123
|
+
|
|
124
|
+
### 2. Personal layouts override only permitted parts
|
|
125
|
+
|
|
126
|
+
A saved personal layout can change:
|
|
127
|
+
|
|
128
|
+
- whether an eligible table-schema column is visible;
|
|
129
|
+
- its numeric or automatic width;
|
|
130
|
+
- its order within the same `left`, `center`, or `right` region.
|
|
131
|
+
|
|
132
|
+
The schema remains authoritative:
|
|
133
|
+
|
|
134
|
+
- A schema-hidden field cannot be restored by a personal layout.
|
|
135
|
+
- A personal layout cannot change the schema-defined fixed region.
|
|
136
|
+
- An old column that is absent from the current schema is not rendered.
|
|
137
|
+
- The row-selection column is not included in the personal layout.
|
|
138
|
+
|
|
139
|
+
## How layouts are saved and restored
|
|
140
|
+
|
|
141
|
+
A standard resource page uses the current route `path` as its `layoutKey`. A layout profile is scoped as follows:
|
|
142
|
+
|
|
143
|
+
```text
|
|
144
|
+
current authenticated user + current page route path
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Consequently, one user can save different layouts for different resource pages, and different users do not overwrite one another's layout on the same page.
|
|
148
|
+
|
|
149
|
+
The `a-layoutprofile` frontend model handles `load`, `save`, and `reset`. A standard `blockPage` first loads the current profile and passes it into the table. After a save or reset succeeds, the page refreshes table metadata so that visibility, order, width, and pinned-column state take effect immediately.
|
|
150
|
+
|
|
151
|
+
A simplified layout profile has this form:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"version": 1,
|
|
156
|
+
"schemaFingerprint": "optional-fingerprint",
|
|
157
|
+
"columns": [
|
|
158
|
+
{ "key": "name", "visible": true, "width": 240 },
|
|
159
|
+
{ "key": "mobile", "visible": false, "width": "auto" }
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
| Field | Rule |
|
|
165
|
+
| ------------------- | --------------------------------------------------- |
|
|
166
|
+
| `version` | Must currently be `1` |
|
|
167
|
+
| `schemaFingerprint` | Optional schema fingerprint, at most 255 characters |
|
|
168
|
+
| `columns` | At most 200 column entries |
|
|
169
|
+
| `columns[].key` | Schema column key, from 1 through 100 characters |
|
|
170
|
+
| `columns[].visible` | Boolean |
|
|
171
|
+
| `columns[].width` | `auto` or an integer from `1` through `2000` |
|
|
172
|
+
|
|
173
|
+
The standard API requires an authenticated user: load, save, and reset all use the current user as the layout owner. Public SSR pre-hydration also does not read this private user state in advance, so a server-side render does not treat a user layout as established first-render truth.
|
|
174
|
+
|
|
175
|
+
## Compatibility when the schema changes
|
|
176
|
+
|
|
177
|
+
Before using a profile, the table reconciles it with the current schema instead of trusting its saved JSON directly:
|
|
178
|
+
|
|
179
|
+
1. It discards empty keys, invalid widths, invalid visibility values, later duplicate entries, and other malformed profile entries.
|
|
180
|
+
2. It discards old columns that no longer exist in the current schema.
|
|
181
|
+
3. It regroups columns into the schema-defined left, center, and right fixed regions; a persisted order is retained only inside its original region.
|
|
182
|
+
4. It appends new schema columns to their region, using the schema width or `auto` when the schema does not define a width.
|
|
183
|
+
5. It excludes the runtime row-selection column.
|
|
184
|
+
|
|
185
|
+
Therefore, users normally do not need to clean up an old profile manually after fields are added, removed, or repinned. To return entirely to the current schema default layout, use **Reset** in Column Configuration.
|
|
186
|
+
|
|
187
|
+
## Standard resource-page runtime path
|
|
188
|
+
|
|
189
|
+
This is the simplified flow when a standard resource page saves a column configuration:
|
|
190
|
+
|
|
191
|
+
```text
|
|
192
|
+
tableActionBulk in the DTO
|
|
193
|
+
↓
|
|
194
|
+
edition-specific blockToolbarBulk
|
|
195
|
+
↓
|
|
196
|
+
actionColumnConfig dialog
|
|
197
|
+
↓
|
|
198
|
+
blockPage (route.path → layoutKey)
|
|
199
|
+
↓
|
|
200
|
+
a-layoutprofile load / save / reset
|
|
201
|
+
↓
|
|
202
|
+
reconcileTableLayout(...)
|
|
203
|
+
↓
|
|
204
|
+
effective ZTable layout
|
|
205
|
+
↓
|
|
206
|
+
column visibility / order / width / pinned-column rendering
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
In that flow:
|
|
210
|
+
|
|
211
|
+
- `blockPage` supplies a Column Configuration draft only after the table reference, table-column metadata, layout-profile model, and authenticated state are ready.
|
|
212
|
+
- `actionColumnConfig` edits the draft and reconciles it again before saving.
|
|
213
|
+
- `ZTable` derives column visibility, order, and numeric widths from its effective layout.
|
|
214
|
+
- Pinned-column state still comes from the schema's `rest.fixed`, not from the profile.
|
|
215
|
+
|
|
216
|
+
`training-student:student` is a complete Basic reference: its list DTO uses `basic-table:actionColumnConfig`, and its field schema defines defaults with `ZovaRender.visible(...)`, `ZovaRender.order(...)`, and `ZovaRender.column(...)`.
|
|
217
|
+
|
|
218
|
+
## When using `ZTable` directly
|
|
219
|
+
|
|
220
|
+
A direct `ZTable` consumer can pass a `layout`:
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
<ZTable data={this.students} schema={this.schemaRow} layout={this.layout} />
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The table still reconciles that layout with the current schema and applies visibility, order, width, and schema-defined fixed regions. However, direct `ZTable` use does not automatically provide a personal-profile loader, saver, resetter, or Column Configuration toolbar.
|
|
227
|
+
|
|
228
|
+
When a page is not a standard resource list page, its owner must decide:
|
|
229
|
+
|
|
230
|
+
1. where the layout comes from;
|
|
231
|
+
2. how it is loaded, saved, and reset for the relevant user and page scope;
|
|
232
|
+
3. how the page keeps its state consistent after a layout changes.
|
|
233
|
+
|
|
234
|
+
For ordinary resource CRUD lists, prefer the existing `basic-page` or `start-page` blockPage/blockTable flow instead of rebuilding the persistence path.
|
|
235
|
+
|
|
236
|
+
## Troubleshooting
|
|
237
|
+
|
|
238
|
+
| Symptom | What to check |
|
|
239
|
+
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
240
|
+
| The toolbar has no Column Configuration button | Confirm that the page has the action for the active edition, uses the correct `blockToolbarBulk`, and does not filter the action through toolbar permission rules. |
|
|
241
|
+
| Clicking it does not open a dialog | Confirm that the page uses standard `blockPage` and `blockTable`, and that table metadata and authenticated state are ready. |
|
|
242
|
+
| The layout is not restored after save | Confirm that the same user visits the same route path, Save was clicked, and no frontend or API request failed. |
|
|
243
|
+
| A new column does not follow the old profile's order | This is expected: new columns are appended to their fixed region with current schema defaults. |
|
|
244
|
+
| A column cannot move to another side | This is the schema `fixed`-region constraint. Change the schema, not the user profile. |
|
|
245
|
+
| A hidden system field should appear in Column Configuration | Do not use a profile for this. Check whether the field uses `ZovaRender.visible(false)`. |
|
|
246
|
+
| A direct `ZTable` has no save behavior | The `layout` prop is only a layout input; its owner is responsible for persistence. |
|
|
247
|
+
|
|
248
|
+
## Related documentation
|
|
249
|
+
|
|
250
|
+
- [Table Guide](/frontend/table-guide): table public APIs, schema column metadata, and `tableCell`.
|
|
251
|
+
- [Table + Resource CRUD Cookbook](/frontend/table-resource-crud-cookbook): how a standard resource list combines filter, toolbar, table, and pager blocks.
|
|
252
|
+
- [Resource List Page Deep Dive](/frontend/resource-list-page-deep-dive): the route-to-`blockPage`/`blockTable`/`ZTable` runtime path.
|
|
253
|
+
- [Zova Table Under the Hood](/frontend/zova-table-under-the-hood): schema metadata, table metadata, and render runtime.
|
|
254
|
+
- [Zova Table Source Reading Map](/frontend/zova-table-source-reading-map): source-reading order for deeper implementation analysis.
|
|
@@ -80,36 +80,9 @@ This matters because the wrapper is not just a naming convenience. It is part of
|
|
|
80
80
|
|
|
81
81
|
## Controller load boundaries
|
|
82
82
|
|
|
83
|
-
A component Controller, optional Style bean, and optional Render bean load in that order. While that
|
|
83
|
+
A component or page Controller, optional Style bean, and optional Render bean load in that order. While that initialization chain is pending, Zova can render a loading fallback; an unhandled initialization failure can render an error fallback.
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
@Controller()
|
|
87
|
-
class ControllerReport extends BeanControllerBase {
|
|
88
|
-
static $componentOptions: IComponentOptions = {
|
|
89
|
-
boundary: {
|
|
90
|
-
loading: { delay: 500 },
|
|
91
|
-
renderMode: 'inline',
|
|
92
|
-
retry: true,
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
`retry` is disabled by default. Enable it only when creating a fresh Controller → Style → Render graph and running the Controller initialization again is safe. In particular, do not opt in for initialization that can repeat a non-idempotent external effect without its own idempotency or recovery contract.
|
|
99
|
-
|
|
100
|
-
Error rendering keeps Controller ownership first. A Controller override may receive the retry action as its trailing argument; otherwise the application boundary renderer receives it:
|
|
101
|
-
|
|
102
|
-
```ts
|
|
103
|
-
protected renderError(error, renderMode, retry?) {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
config.boundary.renderError(ctx, error, renderMode, retry?);
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
The callback is optional because retry may be disabled or unavailable. Existing error renderers that accept only `error` or `error, renderMode` remain valid. When a Controller provides its own error renderer, Zova does not wrap that output with an application retry control; the Controller decides whether to present the action.
|
|
111
|
-
|
|
112
|
-
For SSR, a server-selected error fallback remains the client’s first hydration result. Retry becomes interactive only after the component has mounted, so the server HTML and hydration-time client output stay equivalent. A retry then creates a fresh client-side load graph; it does not rerun the failed Controller instance in place.
|
|
85
|
+
This is a Controller-load boundary, not a generic render, event-handler, or post-initialization async-error boundary. For options, renderer precedence, retry safety, SSR hydration, and the Cabloy Basic / Cabloy Start presentation split, see [Controller Load Boundary Guide](/frontend/controller-boundary-guide).
|
|
113
86
|
|
|
114
87
|
## Reference the component instance
|
|
115
88
|
|