cabloy 5.1.174 → 5.1.176
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/scheduled_tasks.lock +1 -1
- package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +4 -0
- package/CHANGELOG.md +25 -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-docs/fullstack/a-pay-payment-suite.md +31 -17
- 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/pnpm-lock.yaml +45 -38
- 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/package.json +3 -1
- 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-commerce/modules/commerce-trade/src/page/payment/controller.tsx +1 -1
- 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-vendor/a-zova/modules/a-boundary → suite/cabloy-basic/modules/basic-pay}/package.json +13 -5
- package/zova/src/{suite-vendor/a-pay/modules/a-pay → suite/cabloy-basic/modules/basic-pay}/src/.metadata/component/paymentNextAction.ts +2 -2
- package/zova/src/suite/cabloy-basic/modules/basic-pay/src/.metadata/index.ts +68 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pay/src/.metadata/this.ts +2 -0
- package/zova/src/{suite-vendor/a-pay/modules/a-pay → suite/cabloy-basic/modules/basic-pay}/src/component/paymentNextAction/controller.tsx +13 -24
- 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/cabloy-basic/package.json +1 -0
- 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/.metadata/index.ts +4 -46
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts +13 -0
- 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/src/suite-vendor/a-zova/modules/a-boundary/LICENSE +0 -21
- 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 → suite/cabloy-basic/modules/basic-pay}/src/index.ts +0 -0
- /package/zova/src/{suite-vendor/a-zova/modules/a-boundary → suite/cabloy-basic/modules/basic-pay}/tsconfig.build.json +0 -0
- /package/zova/src/{suite-vendor/a-zova/modules/a-boundary → suite/cabloy-basic/modules/basic-pay}/tsconfig.json +0 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Controller Load Boundary Guide
|
|
2
|
+
|
|
3
|
+
A Zova Controller-load boundary renders a loading or error fallback while a page or component controller host initializes. It is available in both **Cabloy Basic** and **Cabloy Start**.
|
|
4
|
+
|
|
5
|
+
The runtime contract is shared by the editions. Their default fallback presentation is deliberately different: Basic uses DaisyUI + Tailwind CSS, while Start uses Vuetify. See [Cabloy Editions](/editions/overview) before copying UI-specific renderer code between editions.
|
|
6
|
+
|
|
7
|
+
## What this boundary owns
|
|
8
|
+
|
|
9
|
+
A generated Zova page or component host loads these beans in order:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
Controller → optional Style bean → optional Render bean
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
While that chain is pending, the host can render a loading fallback. If an initialization error is not handled by the application error path, the host can render an error fallback instead.
|
|
16
|
+
|
|
17
|
+
This is a **Controller-load** boundary, not a general-purpose Vue error boundary. In particular, it does not automatically turn these failures into its fallback UI:
|
|
18
|
+
|
|
19
|
+
- an exception from ordinary `render()` work after the Controller has loaded;
|
|
20
|
+
- an event-handler or action failure;
|
|
21
|
+
- a query, mutation, or data-fetch failure that occurs after initialization;
|
|
22
|
+
- browser-only work that should have been deferred from SSR.
|
|
23
|
+
|
|
24
|
+
Choose the owner that matches the concern: use [Behavior Guide](/frontend/behavior-guide) for composable render-time interception, [$useStateData Best Practices](/frontend/use-state-data-best-practices) for model-owned async state, and [SSR ClientOnly](/frontend/ssr-client-only) for browser-only rendering. Read [Reading Zova for Vue Developers](/frontend/reading-zova-for-vue-developers) first if the Controller / Render / Style bean model is unfamiliar.
|
|
25
|
+
|
|
26
|
+
## Configure one Controller host
|
|
27
|
+
|
|
28
|
+
Declare boundary options on the page or component Controller:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { BeanControllerBase, IComponentOptions } from 'zova';
|
|
32
|
+
import { Controller } from 'zova-module-a-bean';
|
|
33
|
+
|
|
34
|
+
@Controller()
|
|
35
|
+
export class ControllerReport extends BeanControllerBase {
|
|
36
|
+
static $componentOptions: IComponentOptions = {
|
|
37
|
+
boundary: {
|
|
38
|
+
loading: { delay: 500 },
|
|
39
|
+
renderMode: 'inline',
|
|
40
|
+
retry: true,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The options have the following roles:
|
|
47
|
+
|
|
48
|
+
| Option | Meaning |
|
|
49
|
+
| ------------------------ | ------------------------------------------------------------------------------------------------- |
|
|
50
|
+
| `boundary.loading.delay` | Milliseconds to wait before exposing the loading fallback for this Controller host. |
|
|
51
|
+
| `boundary.renderMode` | Fallback structure hint: `'block'` or `'inline'`. |
|
|
52
|
+
| `boundary.retry` | Opts this Controller host into a retry action after an error fallback. It is disabled by default. |
|
|
53
|
+
|
|
54
|
+
### Resolution and defaults
|
|
55
|
+
|
|
56
|
+
| Concern | Controller option | Global application configuration | Runtime behavior |
|
|
57
|
+
| ------------------ | ---------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
58
|
+
| Loading delay | `boundary.loading.delay` | `config.boundary.loading?.delay` | The Controller value wins. An invalid, zero, or negative resolved value displays loading immediately. |
|
|
59
|
+
| Render mode | `boundary.renderMode` | — | Defaults to `'block'`; only the explicit value `'inline'` selects inline mode. |
|
|
60
|
+
| Retry availability | `boundary.retry` | — | Disabled by default; see [Retry safely](#retry-safely). |
|
|
61
|
+
| Loading renderer | `renderLoading(renderMode)` | `config.boundary.renderLoading(ctx, renderMode)` | Controller renderer wins, then application renderer, then no loading VNode. |
|
|
62
|
+
| Error renderer | `renderError(error, renderMode, retry?)` | `config.boundary.renderError(ctx, error, renderMode, retry?)` | Controller renderer wins, then application renderer, then normalized error text. |
|
|
63
|
+
|
|
64
|
+
`renderMode` and `retry` are Controller-level options. They are not global `config.boundary` switches.
|
|
65
|
+
|
|
66
|
+
## Choose block or inline structure
|
|
67
|
+
|
|
68
|
+
Use the default `block` mode when the fallback is allowed to own a block-level region. Use `inline` when the fallback must remain valid phrasing content, for example inside a paragraph:
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
<p>
|
|
72
|
+
Import status: <ZImportStatus />
|
|
73
|
+
</p>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
static $componentOptions: IComponentOptions = {
|
|
78
|
+
boundary: {
|
|
79
|
+
renderMode: 'inline',
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`inline` is an HTML-structure requirement, not merely a visual choice. An application renderer receives the selected mode and must preserve suitable markup: current Basic and Start renderers use a `span` host for inline fallback output. Do not use a block-oriented renderer that inserts a `div` into phrasing-only content.
|
|
85
|
+
|
|
86
|
+
## Provide application defaults
|
|
87
|
+
|
|
88
|
+
Application configuration supplies the shared renderer contract:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import type { ZovaContext } from 'zova';
|
|
92
|
+
import type { VNodeChild } from 'vue';
|
|
93
|
+
|
|
94
|
+
config.boundary = {
|
|
95
|
+
loading: {
|
|
96
|
+
delay: 500,
|
|
97
|
+
},
|
|
98
|
+
renderLoading(ctx: ZovaContext, renderMode: 'block' | 'inline'): VNodeChild {
|
|
99
|
+
return null;
|
|
100
|
+
},
|
|
101
|
+
renderError(
|
|
102
|
+
ctx: ZovaContext,
|
|
103
|
+
error: unknown,
|
|
104
|
+
renderMode: 'block' | 'inline',
|
|
105
|
+
retry?: () => Promise<void>,
|
|
106
|
+
): VNodeChild {
|
|
107
|
+
return null;
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The renderer decides the actual VNode, accessibility semantics, localization, and presentation. Keep customer-facing error text safe: an error fallback is not a substitute for error reporting, monitoring, or a deliberate disclosure policy.
|
|
113
|
+
|
|
114
|
+
The `retry` callback is optional. It is absent when retry is disabled and remains unavailable until an enabled failed boundary has completed client hydration. A global renderer should render its retry control only when this callback is present.
|
|
115
|
+
|
|
116
|
+
## Override rendering for one Controller
|
|
117
|
+
|
|
118
|
+
A Controller can own its boundary presentation without changing application defaults:
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import type { VNodeChild } from 'vue';
|
|
122
|
+
import type { TypeComponentBoundaryRenderMode } from 'zova';
|
|
123
|
+
|
|
124
|
+
protected renderLoading(renderMode: TypeComponentBoundaryRenderMode): VNodeChild {
|
|
125
|
+
return <span role="status">Loading report ({renderMode})</span>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
protected renderError(
|
|
129
|
+
error: unknown,
|
|
130
|
+
renderMode: TypeComponentBoundaryRenderMode,
|
|
131
|
+
retry?: () => Promise<void>,
|
|
132
|
+
): VNodeChild {
|
|
133
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
134
|
+
return (
|
|
135
|
+
<span role="alert">
|
|
136
|
+
Report failed in {renderMode} mode: {message}
|
|
137
|
+
{retry && <button onClick={() => void retry()}>Retry</button>}
|
|
138
|
+
</span>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
A Controller renderer always takes precedence over the global renderer. Once it provides `renderError`, it owns the whole fallback output; Zova does not append the application retry button around it.
|
|
144
|
+
|
|
145
|
+
The trailing callback was added compatibly. Existing error renderers that accept only `error`, or `error, renderMode`, remain valid. Add the optional third argument only when the Controller needs to surface retry itself.
|
|
146
|
+
|
|
147
|
+
## Retry safely
|
|
148
|
+
|
|
149
|
+
`retry: true` is intentionally opt-in. A retry does **not** call `__init__()` again on the failed Controller instance. Instead, Zova clears the prior load attempt, disposes the beans it owns, and creates a fresh Controller → Style → Render graph.
|
|
150
|
+
|
|
151
|
+
Enable retry only when every relevant initialization effect is safe to perform again. Typical safe cases include a local read whose repeated execution is harmless, or a request with a defined idempotency/recovery contract. Do not enable it for initialization that can repeat a non-idempotent external effect without such a contract.
|
|
152
|
+
|
|
153
|
+
The runtime also protects the interaction boundary:
|
|
154
|
+
|
|
155
|
+
- only one retry attempt is active at a time; repeated clicks share that attempt;
|
|
156
|
+
- retry is available only when `retry: true`, the host is still active, and the current state is an error;
|
|
157
|
+
- it becomes interactive after client hydration, not in server HTML;
|
|
158
|
+
- a successful retry restores the fresh Controller graph and normal ready rendering.
|
|
159
|
+
|
|
160
|
+
## Error handling and SSR
|
|
161
|
+
|
|
162
|
+
### Error routing
|
|
163
|
+
|
|
164
|
+
A Controller-load failure is reported through the application error handler before Zova decides to show a local fallback. An application handler can mark the load error as handled; in that case, the Controller-load error fallback is not rendered.
|
|
165
|
+
|
|
166
|
+
Treat a visible fallback and operational reporting as separate responsibilities. The default Basic renderer displays an error message, but production applications should decide which message is appropriate for the current audience and preserve diagnostic information through their normal error-handling path.
|
|
167
|
+
|
|
168
|
+
### SSR and hydration
|
|
169
|
+
|
|
170
|
+
For SSR, the sequence is designed to keep server HTML and the browser's first hydration output equivalent:
|
|
171
|
+
|
|
172
|
+
1. server prefetch runs the Controller-load chain;
|
|
173
|
+
2. an unhandled server fallback is captured as deferred SSR state;
|
|
174
|
+
3. the browser replays that fallback during its initial hydration render, even if a new browser attempt could now succeed;
|
|
175
|
+
4. after hydration, an enabled retry control can start a fresh client-side load graph.
|
|
176
|
+
|
|
177
|
+
Do not render a retry affordance unconditionally in server output. The optional callback is the signal that it is interactive and available. Also distinguish a Controller-load fallback from SSR-wide handled control flow such as redirects or authentication handling; those can be handled without selecting this local fallback.
|
|
178
|
+
|
|
179
|
+
For the larger render and handoff model, read [SSR Architecture Overview](/frontend/ssr-architecture-overview) and use [SSR Review Checklist](/frontend/ssr-review-checklist) when changing SSR-sensitive UI.
|
|
180
|
+
|
|
181
|
+
## Shared contract, edition-specific defaults
|
|
182
|
+
|
|
183
|
+
The Controller options, renderer signatures, renderer precedence, retry rules, and SSR behavior above are shared Zova behavior. The default application renderers are edition-specific implementation choices.
|
|
184
|
+
|
|
185
|
+
| Surface | Cabloy Basic | Cabloy Start |
|
|
186
|
+
| ----------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------- |
|
|
187
|
+
| Global loading delay | `500ms` | `500ms` |
|
|
188
|
+
| Block loading fallback | Centered DaisyUI/Tailwind spinner in a `div` with `role="status"` | Centered Vuetify `VProgressCircular` in a flex `div` with `role="status"` |
|
|
189
|
+
| Inline loading fallback | DaisyUI/Tailwind spinner in a `span` host | `VProgressCircular` in a `span` host |
|
|
190
|
+
| Block error fallback | DaisyUI `alert alert-error` presentation | Tonal Vuetify `VAlert` |
|
|
191
|
+
| Inline error fallback | Inline error `span` presentation | Inline Vuetify-oriented error and action presentation in a `span` host |
|
|
192
|
+
| Retry affordance | DaisyUI ghost button | Small text-variant `VBtn` |
|
|
193
|
+
|
|
194
|
+
DaisyUI classes, Vuetify component names, spacing, and colors are not part of the shared Zova API. Keep shared Controller code UI-neutral where possible, then configure edition-local global renderers for the desired visual system.
|
|
195
|
+
|
|
196
|
+
## Validate the behavior in Cabloy Basic
|
|
197
|
+
|
|
198
|
+
Cabloy Basic includes a manual demonstration at:
|
|
199
|
+
|
|
200
|
+
```text
|
|
201
|
+
/demo/basic/controllerBoundary
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Its source is in `zova/src/suite/a-demo/modules/demo-basic/src/page/controllerBoundary/controller.tsx`, with separate normal, inline, and Controller-override probes. The focused browser specification is `repo-e2e/specs/controller-boundary.spec.ts`.
|
|
205
|
+
|
|
206
|
+
Together, those probes verify that:
|
|
207
|
+
|
|
208
|
+
- fast initialization finishes without exposing delayed loading;
|
|
209
|
+
- slow initialization exposes loading, then ready content;
|
|
210
|
+
- an error fallback can expose and recover through retry;
|
|
211
|
+
- inline fallback markup remains valid in phrasing content;
|
|
212
|
+
- Controller overrides receive the render mode and retain legacy error-renderer compatibility;
|
|
213
|
+
- a server-selected fallback survives as the initial hydration result, then can recover through a post-hydration retry.
|
|
214
|
+
|
|
215
|
+
The demo and this focused E2E specification are Basic-specific evidence. Cabloy Start shares the Controller-load contract but uses its own Vuetify default presentation and should validate its own controller hosts accordingly.
|
|
216
|
+
|
|
217
|
+
## Implementation checklist
|
|
218
|
+
|
|
219
|
+
Before enabling a Controller-load boundary, check that:
|
|
220
|
+
|
|
221
|
+
- [ ] the concern is initialization of a Controller, Style, or Render bean rather than later render/action/query work;
|
|
222
|
+
- [ ] `inline` is selected whenever the fallback sits in phrasing-only content;
|
|
223
|
+
- [ ] the application renderer handles both `block` and `inline` output safely and accessibly;
|
|
224
|
+
- [ ] retry is enabled only for a replay-safe fresh load graph;
|
|
225
|
+
- [ ] customer-facing error text is safe and operational details remain in the normal error path;
|
|
226
|
+
- [ ] SSR fallback output remains compatible with hydration and relies on the optional retry callback;
|
|
227
|
+
- [ ] UI-specific fallback code matches the active edition.
|
|
228
|
+
|
|
229
|
+
Read [Component Guide](/frontend/component-guide) for the broader component model and [Cabloy Editions](/editions/overview) when choosing Basic or Start UI conventions.
|
|
@@ -65,6 +65,7 @@ Use this path when the task is page-oriented, route-oriented, or the first time
|
|
|
65
65
|
- [Navigation Guards Guide](/frontend/navigation-guards-guide)
|
|
66
66
|
- [Zova Router Under the Hood](/frontend/zova-router-under-the-hood)
|
|
67
67
|
- [Router View Hosts Guide](/frontend/router-view-hosts-guide)
|
|
68
|
+
- [Routed Dialog Guide](/frontend/routed-dialog-guide)
|
|
68
69
|
- [Router Tabs Introduction](/frontend/router-tabs-introduction)
|
|
69
70
|
- [Router Tabs Overview](/frontend/router-tabs-overview)
|
|
70
71
|
- [Router Tabs Mechanism](/frontend/router-tabs-mechanism)
|
|
@@ -79,13 +80,16 @@ Use this path when the task is page-oriented, route-oriented, or the first time
|
|
|
79
80
|
Use this path when the task is about UI composition, component contracts, form or table architecture, form or table internals, form or table source reading, or theme work:
|
|
80
81
|
|
|
81
82
|
- [Component Guide](/frontend/component-guide)
|
|
83
|
+
- [Controller Load Boundary Guide](/frontend/controller-boundary-guide)
|
|
82
84
|
- [Form Guide](/frontend/form-guide)
|
|
85
|
+
- [Resource Picker Guide](/frontend/resource-picker-guide)
|
|
83
86
|
- [Semantic Presentation Contract](/fullstack/semantic-presentation-contract)
|
|
84
87
|
- [Form Layout Guide](/frontend/form-layout-guide)
|
|
85
88
|
- [Image Guide](/frontend/image-guide)
|
|
86
89
|
- [Zova Form Under the Hood](/frontend/zova-form-under-the-hood)
|
|
87
90
|
- [Zova Form Source Reading Map](/frontend/zova-form-source-reading-map)
|
|
88
91
|
- [Table Guide](/frontend/table-guide)
|
|
92
|
+
- [Column Configuration Guide](/frontend/column-configuration-guide)
|
|
89
93
|
- [TableCell Authoring Cookbook](/frontend/table-cell-cookbook)
|
|
90
94
|
- [Table + Resource CRUD Cookbook](/frontend/table-resource-crud-cookbook)
|
|
91
95
|
- [Zova Table Under the Hood](/frontend/zova-table-under-the-hood)
|
|
@@ -72,6 +72,12 @@ Choose `meta.ssrProfile` from the page's rendering contract. Use `session` when
|
|
|
72
72
|
|
|
73
73
|
This is one reason route records matter so much: they are not just URL declarations. They are an application-behavior surface.
|
|
74
74
|
|
|
75
|
+
## Route records inside routed dialogs
|
|
76
|
+
|
|
77
|
+
An ordinary page route can also be the target of a [Routed Dialog](/frontend/routed-dialog-guide). The route still follows the rules on this page: dynamic params require `name`, static routes normally remain unnamed, and generated `$params` / `$query` schemas continue to apply.
|
|
78
|
+
|
|
79
|
+
The difference is hosting rather than route declaration. A routed dialog runs the target through its own local memory-history router, so navigation inside the dialog does not change the main browser URL or route. Do not infer that every routed-dialog target is a suitable direct browser entry: a feature-specific target may require a page-host contract supplied only by its routed-dialog workflow.
|
|
80
|
+
|
|
75
81
|
## Route -> shell -> routed page
|
|
76
82
|
|
|
77
83
|
A useful frontend mental model is:
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Resource Picker Guide
|
|
2
|
+
|
|
3
|
+
Use a Resource Picker when a form field stores the identity of an existing resource, such as a Student, Product, or Role. The picker is schema-driven: declare the field renderer in backend entity or DTO metadata, and the active Cabloy edition renders the appropriate frontend control.
|
|
4
|
+
|
|
5
|
+
Use this page together with:
|
|
6
|
+
|
|
7
|
+
- [Form Guide](/frontend/form-guide)
|
|
8
|
+
- [API Schema Guide](/frontend/api-schema-guide)
|
|
9
|
+
- [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern)
|
|
10
|
+
- [Routed Dialog Guide](/frontend/routed-dialog-guide)
|
|
11
|
+
- [TableCell Authoring Cookbook](/frontend/table-cell-cookbook)
|
|
12
|
+
|
|
13
|
+
> [!TIP]
|
|
14
|
+
> A Resource Picker is a resource-field renderer, not a standalone client-side component API. The backend contract remains the source of truth for the field, its target resource, its selectable rows, and its display data.
|
|
15
|
+
|
|
16
|
+
## What a Resource Picker stores
|
|
17
|
+
|
|
18
|
+
A picker field persists an identity, not the whole selected row:
|
|
19
|
+
|
|
20
|
+
- `single` selection stores one ID;
|
|
21
|
+
- `multiple` selection stores an ID array;
|
|
22
|
+
- related objects are response-only display projections used to show labels.
|
|
23
|
+
|
|
24
|
+
For example, a Training Record can persist `studentId` while its select and view responses include a narrow `student` relation with the Student ID and display name. Do not replace the ID write contract with a submitted Student object merely because the UI displays a Student label.
|
|
25
|
+
|
|
26
|
+
## Prerequisites
|
|
27
|
+
|
|
28
|
+
Before adding a picker, verify the target resource has all of the following:
|
|
29
|
+
|
|
30
|
+
1. A valid resource name and a selectable resource API that the current user may access.
|
|
31
|
+
2. Select results with stable, nonempty row IDs.
|
|
32
|
+
3. A label field such as `name`, or a configured alternative such as `title` or `code`.
|
|
33
|
+
4. For `routedDialog` mode, select-schema blocks that produce the target resource's normal list experience, such as filter, table, and pager blocks.
|
|
34
|
+
5. A response-only relation projection for labels when the editing, readonly, or list experience needs to display the selected resource after loading.
|
|
35
|
+
|
|
36
|
+
The target resource's server-side authorization, instance scope, filtering, and validation remain authoritative. A client-side `query`, `actionPath`, or picker field does not authorize a user to select a row.
|
|
37
|
+
|
|
38
|
+
## Declare the field renderer
|
|
39
|
+
|
|
40
|
+
Declare the field renderer on the persisted identity field with `ZovaRender.field(...)`. Add a matching cell renderer when a resource list should display the relation label instead of a raw ID.
|
|
41
|
+
|
|
42
|
+
### Cabloy Basic
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
@Api.field(
|
|
46
|
+
v.title($locale('Student')),
|
|
47
|
+
ZovaRender.field('basic-resource:formFieldResourcePicker', {
|
|
48
|
+
resource: $resourceName('training-student:student'),
|
|
49
|
+
pickerMode: 'routedDialog',
|
|
50
|
+
selectionMode: 'single',
|
|
51
|
+
}),
|
|
52
|
+
ZovaRender.cell('basic-resource:resourcePicker', {
|
|
53
|
+
resource: $resourceName('training-student:student'),
|
|
54
|
+
}),
|
|
55
|
+
v.tableIdentity(),
|
|
56
|
+
)
|
|
57
|
+
studentId: TableIdentity;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Cabloy Start
|
|
61
|
+
|
|
62
|
+
Use the same business contract, but use Start's renderer identities:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
@Api.field(
|
|
66
|
+
v.title($locale('Student')),
|
|
67
|
+
ZovaRender.field('start-resource:formFieldResourcePicker', {
|
|
68
|
+
resource: $resourceName('training-student:student'),
|
|
69
|
+
pickerMode: 'routedDialog',
|
|
70
|
+
selectionMode: 'single',
|
|
71
|
+
}),
|
|
72
|
+
ZovaRender.cell('start-resource:resourcePicker', {
|
|
73
|
+
resource: $resourceName('training-student:student'),
|
|
74
|
+
}),
|
|
75
|
+
v.tableIdentity(),
|
|
76
|
+
)
|
|
77
|
+
studentId: TableIdentity;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The two renderer names are edition-specific. Do not use `basic-resource:*` names in Start, or `start-resource:*` names in Basic.
|
|
81
|
+
|
|
82
|
+
> [!IMPORTANT]
|
|
83
|
+
> The `resourcePicker` **cell** renderer is presentation-only. It reads related display data already returned in the list row; it does not open a picker and it does not fetch a missing label.
|
|
84
|
+
|
|
85
|
+
## Field options
|
|
86
|
+
|
|
87
|
+
The form-field renderer supports the following public metadata options.
|
|
88
|
+
|
|
89
|
+
| Option | Applies to | Meaning |
|
|
90
|
+
| --------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| `resource` | both modes | Required target resource name. The type currently marks it optional, but the renderer throws if it is missing. |
|
|
92
|
+
| `actionPath` | both modes | Optional target-resource select action. Use it when the resource exposes a dedicated selection endpoint. |
|
|
93
|
+
| `query` | both modes | Optional fixed list query. In dialog mode it is supplied to the picker list; it does not replace server-side scope or authorization. |
|
|
94
|
+
| `relationName` | labels | Response relation used to present selected labels. When omitted, the renderer can derive a relation name from a field ending in `Id`; specify it explicitly for exceptions. |
|
|
95
|
+
| `selectOptions` | inline select and labels | Edition-native select options, including `itemValue` and `itemTitle`. The default mapping is `id` and `name`; use another `itemTitle`, such as `title` or `code`, when appropriate. |
|
|
96
|
+
| `pickerMode` | interaction | `'select'` by default, or `'routedDialog'` for the hosted resource-list picker. |
|
|
97
|
+
| `selectionMode` | routed dialog | `'single'` by default, or `'multiple'`. |
|
|
98
|
+
| `selectionMax` | routed dialog | Maximum selected identities. Single mode is always limited to one. Multiple mode currently defaults to 100 when no valid nonnegative integer limit is supplied. |
|
|
99
|
+
|
|
100
|
+
Cabloy Start also provides Vuetify presentation options for this field, including `display`, `chipGroupOptions`, and `chipOptions`. Use them only in Start-specific metadata; they are not shared Basic options.
|
|
101
|
+
|
|
102
|
+
## Choose an interaction mode
|
|
103
|
+
|
|
104
|
+
### Inline select: the default
|
|
105
|
+
|
|
106
|
+
When `pickerMode` is omitted, the field loads the target `ModelResource` through its select query and renders an inline select. Use this for a small, readily available option set.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
ZovaRender.field('basic-resource:formFieldResourcePicker', {
|
|
110
|
+
resource: $resourceName('commerce-catalog:category'),
|
|
111
|
+
selectOptions: {
|
|
112
|
+
itemValue: 'id',
|
|
113
|
+
itemTitle: 'name',
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`actionPath` and `query` affect the target select request. `selectionMode` and `selectionMax` are routed-dialog controls; do not use them to assume an inline select will enforce a dialog-style selection policy.
|
|
119
|
+
|
|
120
|
+
### Routed dialog: searchable resource-list selection
|
|
121
|
+
|
|
122
|
+
Use `pickerMode: 'routedDialog'` when the user needs the target resource's table-based selection experience, including its existing filters, pager, list actions, fixed query, or controlled multi-selection.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
ZovaRender.field('basic-resource:formFieldResourcePicker', {
|
|
126
|
+
resource: $resourceName('training-student:student'),
|
|
127
|
+
pickerMode: 'routedDialog',
|
|
128
|
+
selectionMode: 'single',
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The routed picker reuses the target resource's schema-authored select blocks instead of maintaining a second hard-coded list. It opens the named `rest-resource:resourcePicker` route inside a routed-dialog host.
|
|
133
|
+
|
|
134
|
+
> [!WARNING]
|
|
135
|
+
> `rest-resource:resourcePicker` is not a standalone browser page or a direct navigation API. Its page controller requires a host-scoped picker contract supplied by the field/dialog workflow. Do not link users directly to `/rest/resource/:resource/picker`, treat it as bookmarkable state, or build application behavior around entering that route without its host.
|
|
136
|
+
|
|
137
|
+
## Selection result and limits
|
|
138
|
+
|
|
139
|
+
When the user confirms a routed picker:
|
|
140
|
+
|
|
141
|
+
- `single` writes the first selected ID;
|
|
142
|
+
- `multiple` writes a capped array of selected IDs;
|
|
143
|
+
- null, undefined, and empty IDs are discarded;
|
|
144
|
+
- duplicate IDs are collapsed while preserving the original ID value type;
|
|
145
|
+
- the dialog requires a valid nonempty selection to enable confirmation.
|
|
146
|
+
|
|
147
|
+
Single mode always has an effective maximum of one. For multiple mode, a missing or invalid limit currently becomes 100. A maximum of `0` prevents confirmation; it is not a useful way to request a deliberate empty result.
|
|
148
|
+
|
|
149
|
+
The picker keeps selection across list pages, filters, and sorting within the dialog. Its returned row snapshots are useful presentation data, but the saved field value remains IDs.
|
|
150
|
+
|
|
151
|
+
## Persisted IDs and display relations
|
|
152
|
+
|
|
153
|
+
A good picker contract separates writing from presentation:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
// Write data
|
|
157
|
+
studentId: TableIdentity;
|
|
158
|
+
|
|
159
|
+
// Response-only display projection
|
|
160
|
+
student?: {
|
|
161
|
+
id: TableIdentity;
|
|
162
|
+
name: string;
|
|
163
|
+
};
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The exact DTO form may differ, but the rule stays the same:
|
|
167
|
+
|
|
168
|
+
- send and persist `studentId`;
|
|
169
|
+
- return a narrow `student` relation for labels;
|
|
170
|
+
- use `relationName` if the relation does not follow the normal `...Id` naming convention;
|
|
171
|
+
- align the relation's title property with `selectOptions.itemTitle`.
|
|
172
|
+
|
|
173
|
+
After routed selection, the field can synchronize selected display data and may hydrate a missing label through the target resource. That behavior improves the immediate UI, but it is not a replacement for a correct view/select response projection. Readonly fields and list cells should receive the labels they need from the server response.
|
|
174
|
+
|
|
175
|
+
## Readonly fields and table cells
|
|
176
|
+
|
|
177
|
+
A Resource Picker field is interactive only in an editable form. In readonly mode it displays a relation-backed label instead of a chooser.
|
|
178
|
+
|
|
179
|
+
The paired `resourcePicker` table cell is also noninteractive. It formats relation data already attached to the table row. In particular:
|
|
180
|
+
|
|
181
|
+
- it does not fetch a related row;
|
|
182
|
+
- it does not open a dialog;
|
|
183
|
+
- missing display projection data can leave the cell without a useful label.
|
|
184
|
+
|
|
185
|
+
Use a relation projection deliberately rather than relying on a raw foreign-key ID to become readable presentation data.
|
|
186
|
+
|
|
187
|
+
## Routed-dialog lifecycle and SSR
|
|
188
|
+
|
|
189
|
+
A routed picker starts with the field's current IDs as its selection. On confirmation, it updates the field; on Cancel or an ordinary close, its result is `undefined` and the form keeps its prior value.
|
|
190
|
+
|
|
191
|
+
The routed dialog has its own in-memory route history. Opening, navigating inside, or closing it does not change the outer browser URL. It therefore is not appropriate for a flow that must be bookmarkable or survive a browser reload.
|
|
192
|
+
|
|
193
|
+
Routed dialogs are client-only. Open them from an interaction such as a button click, or defer lifecycle-driven behavior to an explicit post-hydration client boundary. Do not open a routed dialog from SSR execution merely because the surrounding page supports SSR. For the general lifecycle and presentation contract, see [Routed Dialog Guide](/frontend/routed-dialog-guide).
|
|
194
|
+
|
|
195
|
+
## Basic and Start UI differences
|
|
196
|
+
|
|
197
|
+
The resource/data contract is shared, but the visual implementation is deliberately edition-specific.
|
|
198
|
+
|
|
199
|
+
| Surface | Cabloy Basic | Cabloy Start |
|
|
200
|
+
| ------------------------------------ | ---------------------------------------------------- | -------------------------------------------- |
|
|
201
|
+
| Inline field | `ZSelect` with Basic's DaisyUI-oriented presentation | Vuetify `VSelect` |
|
|
202
|
+
| Optional selected-value presentation | Standard Basic field presentation | Can use Start's Vuetify chip-display options |
|
|
203
|
+
| Routed picker trigger and actions | Basic/DaisyUI-oriented controls | Vuetify controls |
|
|
204
|
+
| Routed single selection | Radio control | Checkbox selection surface |
|
|
205
|
+
| Renderer identities | `basic-resource:*` | `start-resource:*` |
|
|
206
|
+
|
|
207
|
+
Treat these as UI differences only. They do not change the core persistence rule, resource-owner data ownership, or server authorization boundary.
|
|
208
|
+
|
|
209
|
+
## Authoring checklist
|
|
210
|
+
|
|
211
|
+
Before shipping a Resource Picker, check the following:
|
|
212
|
+
|
|
213
|
+
- [ ] The field persists a stable identity or identity array.
|
|
214
|
+
- [ ] `resource` names the intended target resource.
|
|
215
|
+
- [ ] The target select API returns stable IDs and the configured title property.
|
|
216
|
+
- [ ] The target resource's select schema contains the list blocks needed by dialog mode.
|
|
217
|
+
- [ ] `actionPath` and `query` express only the desired selection view; server-side scope and authorization still enforce access.
|
|
218
|
+
- [ ] The response DTO supplies a narrow relation projection for editable, readonly, and table-label presentation.
|
|
219
|
+
- [ ] `relationName` and `selectOptions.itemTitle` match the returned projection when defaults do not.
|
|
220
|
+
- [ ] The field and table-cell renderer names match the active edition.
|
|
221
|
+
- [ ] The selected IDs, labels, cancellation behavior, and authorization rules are covered by a targeted test or end-to-end scenario.
|
|
222
|
+
|
|
223
|
+
The Training Record → Student picker is covered by existing end-to-end scenarios in both Cabloy Basic and Cabloy Start. Use that flow as the reference specimen when verifying an equivalent picker in your own resource.
|
|
@@ -53,9 +53,9 @@ It is also a **route-to-page-shell-to-block-to-model bridge** for resource-drive
|
|
|
53
53
|
|
|
54
54
|
If you only remember one mental model, remember this one:
|
|
55
55
|
|
|
56
|
-
1. `routes.ts`
|
|
56
|
+
1. `routes.ts` declares three ordinary resource workspace routes and one host-dependent picker route
|
|
57
57
|
2. generated `ZPage*` wrappers bind those route entries to page controllers
|
|
58
|
-
3. the page controllers resolve the current `resource`, `id`, and `formScene
|
|
58
|
+
3. the ordinary page controllers resolve the current `resource`, `id`, and `formScene`, while the picker controller also requires its host-scoped picker contract
|
|
59
59
|
4. the page controllers load top-level schema metadata and render `rest.blocks`
|
|
60
60
|
5. those rendered blocks usually enter generic Basic runtimes such as `basic-page:blockPage` or `basic-pageentry:blockPageEntry`
|
|
61
61
|
6. those downstream runtimes resolve the same selector-backed `ModelResource` instance again
|
|
@@ -65,7 +65,7 @@ A compact relationship map is:
|
|
|
65
65
|
|
|
66
66
|
```text
|
|
67
67
|
routes.ts
|
|
68
|
-
└─ ZPageResource / ZPageEntry / ZPageEntryCreate
|
|
68
|
+
└─ ZPageResource / ZPageEntry / ZPageEntryCreate / ZPageResourcePicker
|
|
69
69
|
└─ page controller shell
|
|
70
70
|
└─ schema rest.blocks
|
|
71
71
|
└─ generic Basic blocks
|
|
@@ -73,6 +73,8 @@ routes.ts
|
|
|
73
73
|
└─ OpenAPI bootstrap / fetch / mutation / invalidation
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
The picker route is intentionally different from an ordinary workspace page: it consumes an injected host contract and is normally entered through a routed dialog. For its public authoring contract, see [Resource Picker Guide](/frontend/resource-picker-guide).
|
|
77
|
+
|
|
76
78
|
That is why `rest-resource` should not be read as “one page that does CRUD directly”.
|
|
77
79
|
|
|
78
80
|
The Zova-native meaning is:
|
|
@@ -89,14 +91,16 @@ When you want the shortest correct reading order, use this sequence:
|
|
|
89
91
|
2. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/.metadata/page/resource.ts`
|
|
90
92
|
3. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/.metadata/page/entry.ts`
|
|
91
93
|
4. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/.metadata/page/entryCreate.ts`
|
|
92
|
-
5. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/
|
|
93
|
-
6. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/
|
|
94
|
-
7. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/
|
|
95
|
-
8. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/
|
|
96
|
-
9. `zova/src/suite/cabloy
|
|
97
|
-
10. `zova/src/suite/cabloy
|
|
98
|
-
11. `zova/src/suite/cabloy-basic/modules/basic-
|
|
99
|
-
12. `zova/src/suite
|
|
94
|
+
5. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/.metadata/page/resourcePicker.ts`
|
|
95
|
+
6. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controller.tsx`
|
|
96
|
+
7. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entry/controller.tsx`
|
|
97
|
+
8. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entryCreate/controller.tsx`
|
|
98
|
+
9. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resourcePicker/controller.tsx`
|
|
99
|
+
10. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts`
|
|
100
|
+
11. `zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx`
|
|
101
|
+
12. `zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx`
|
|
102
|
+
13. `zova/src/suite/cabloy-basic/modules/basic-commands/src/bean/command.delete.tsx`
|
|
103
|
+
14. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/.metadata/index.ts`
|
|
100
104
|
|
|
101
105
|
A compact role map is:
|
|
102
106
|
|
|
@@ -105,6 +109,7 @@ A compact role map is:
|
|
|
105
109
|
- `page/resource/controller.tsx` shows the list-page shell
|
|
106
110
|
- `page/entry/controller.tsx` shows the entry-page shell
|
|
107
111
|
- `page/entryCreate/controller.tsx` shows virtual create-page reuse
|
|
112
|
+
- `page/resourcePicker/controller.tsx` shows the host-dependent picker shell
|
|
108
113
|
- `model/resource.ts` shows the resource-owner core
|
|
109
114
|
- `basic-page:blockPage` shows the deeper list runtime
|
|
110
115
|
- `basic-pageentry:blockPageEntry` shows the deeper form runtime
|
|
@@ -117,31 +122,30 @@ Start with:
|
|
|
117
122
|
|
|
118
123
|
- `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/routes.ts`
|
|
119
124
|
|
|
120
|
-
This file declares
|
|
125
|
+
This file declares four routes:
|
|
121
126
|
|
|
122
127
|
- `:resource`
|
|
123
128
|
- `:resource/create`
|
|
124
129
|
- `:resource/:id/:formScene?`
|
|
130
|
+
- `:resource/picker`
|
|
125
131
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
It is not tied to one concrete resource such as Student or Product.
|
|
132
|
+
The first three are ordinary resource workspace routes. The fourth is the host-dependent Resource Picker route; it is normally rendered by a routed-dialog workflow, not entered as a direct browser destination. See [Resource Picker Guide](/frontend/resource-picker-guide) for the public authoring contract.
|
|
129
133
|
|
|
130
|
-
|
|
134
|
+
The module is not tied to one concrete resource such as Student or Product. Its runtime identity comes from `route.params.resource`.
|
|
131
135
|
|
|
132
136
|
### Why `tabKey` matters
|
|
133
137
|
|
|
134
|
-
|
|
138
|
+
The list, create, and entry routes use the same `tabKey(route)` shape:
|
|
135
139
|
|
|
136
140
|
```typescript
|
|
137
141
|
`/rest/resource/${encodeURIComponent(route.params.resource)}`;
|
|
138
142
|
```
|
|
139
143
|
|
|
140
|
-
This means
|
|
144
|
+
This means their workspace identity is resource-level rather than row-level. The picker route deliberately does not use that shared workspace identity: its controller requires the host-scoped picker contract that its routed-dialog caller provides.
|
|
141
145
|
|
|
142
146
|
If your next question becomes specifically how the entry route becomes a working page through `basic-pageentry`, continue with [Resource Entry Page Deep Dive](/frontend/resource-entry-page-deep-dive).
|
|
143
147
|
|
|
144
|
-
|
|
148
|
+
The ordinary workspace views are grouped under one resource-oriented tab boundary:
|
|
145
149
|
|
|
146
150
|
- resource list
|
|
147
151
|
- create entry
|
|
@@ -172,6 +176,7 @@ Instead, each route enters the normal Zova page-controller path through:
|
|
|
172
176
|
- `createZovaComponentPage(ControllerPageResource, ...)`
|
|
173
177
|
- `createZovaComponentPage(ControllerPageEntry, ...)`
|
|
174
178
|
- `createZovaComponentPage(ControllerPageEntryCreate, ...)`
|
|
179
|
+
- `createZovaComponentPage(ControllerPageResourcePicker, ...)`
|
|
175
180
|
|
|
176
181
|
These files also expose the typed Zod-based params schema for each page.
|
|
177
182
|
|
|
@@ -184,8 +189,8 @@ After the page wrappers, read:
|
|
|
184
189
|
This generated file is mostly not “business logic”, but it is still the best summary of the module’s registered surfaces:
|
|
185
190
|
|
|
186
191
|
- `rest-resource.model.resource` → model bean full name
|
|
187
|
-
- `rest-resource.controller.pageResource` / `pageEntry` / `pageEntryCreate` → controller registrations
|
|
188
|
-
- page-path and page-name typing for `/rest/resource
|
|
192
|
+
- `rest-resource.controller.pageResource` / `pageEntry` / `pageEntryCreate` / `pageResourcePicker` → controller registrations
|
|
193
|
+
- page-path and page-name typing for `/rest/resource/...`, including the hosted picker route
|
|
189
194
|
- module scope typing for `rest-resource`
|
|
190
195
|
|
|
191
196
|
Use this file as the **registry map**, not as the first place to learn the runtime behavior.
|
|
@@ -266,6 +271,16 @@ Instead, the create route reuses the entry shell and lets route params plus form
|
|
|
266
271
|
|
|
267
272
|
This is a good example of Zova keeping page identity separate from duplicated controller logic.
|
|
268
273
|
|
|
274
|
+
### 3.4 Resource Picker shell is host-dependent
|
|
275
|
+
|
|
276
|
+
Read:
|
|
277
|
+
|
|
278
|
+
- `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resourcePicker/controller.tsx`
|
|
279
|
+
|
|
280
|
+
This controller resolves the selector-backed target resource and its select schema, then renders the schema's list blocks through a picker page host. Unlike the ordinary list and entry shells, it requires an injected host contract whose resource must match the route parameter. The host adapts the resource's existing list blocks for selection and supplies the confirm/cancel workflow.
|
|
281
|
+
|
|
282
|
+
That makes the picker route an internal routed-dialog workflow page, not a direct browser-entry CRUD page. For the supported field metadata and result contract, continue with [Resource Picker Guide](/frontend/resource-picker-guide).
|
|
283
|
+
|
|
269
284
|
## 4. `ModelResource` is the owner core, not only a helper
|
|
270
285
|
|
|
271
286
|
Read:
|
|
@@ -384,6 +399,7 @@ These are the main runtime logic files:
|
|
|
384
399
|
- `src/page/resource/controller.tsx`
|
|
385
400
|
- `src/page/entry/controller.tsx`
|
|
386
401
|
- `src/page/entryCreate/controller.tsx`
|
|
402
|
+
- `src/page/resourcePicker/controller.tsx`
|
|
387
403
|
- `src/model/resource.ts`
|
|
388
404
|
|
|
389
405
|
These are where the real behavior decisions live.
|