@jigx/core-sdk 1.0.0 → 1.2.0-rc
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/README.md +2 -0
- package/dist/action/ja.generate-pdf.d.ts +17 -1
- package/dist/action/ja.generate-pdf.d.ts.map +1 -1
- package/dist/action/ja.generate-pdf.js +4 -1
- package/dist/action/ja.in-background.d.ts +3 -2
- package/dist/action/ja.in-background.d.ts.map +1 -1
- package/dist/action/ja.in-background.js +1 -1
- package/dist/assets/example-extraction-cache.json +3 -3
- package/dist/assets/extracted-core-sdk-examples.yaml +18 -0
- package/dist/assets/extracted-core-sdk-types.yaml +58 -0
- package/dist/assets/type-extraction-cache.json +3 -3
- package/docs/array-fields.md +371 -0
- package/docs/conditional-logic.md +178 -0
- package/docs/convention-naming.md +102 -0
- package/docs/date-field.md +92 -0
- package/docs/dropdown-fields.md +879 -0
- package/docs/field-state.md +131 -0
- package/docs/field-types-overview.md +132 -0
- package/docs/formatting.md +421 -0
- package/docs/icons.md +142 -0
- package/docs/index.md +23 -0
- package/docs/jsonata-expressions.md +200 -0
- package/docs/media-fields.md +107 -0
- package/docs/overview.md +467 -0
- package/docs/pattern-build-deploy.md +91 -0
- package/docs/pattern-datasources.md +459 -0
- package/docs/pattern-forms.md +528 -0
- package/docs/pattern-global-actions.md +92 -0
- package/docs/pattern-javascript-functions.md +452 -0
- package/docs/pattern-navigation.md +304 -0
- package/docs/pattern-pdf-generation.md +391 -0
- package/docs/pattern-rest-acumatica.md +660 -0
- package/docs/pattern-sync-progress.md +96 -0
- package/docs/pattern-sync.md +653 -0
- package/docs/pattern-tabs-form.md +293 -0
- package/docs/recipe-index.md +64 -0
- package/docs/runtime-variables.md +127 -0
- package/docs/sections.md +81 -0
- package/docs/validation-patterns.md +150 -0
- package/package.json +5 -4
- package/CHANGELOG.md +0 -95
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# Pattern: Tabbed Forms
|
|
2
|
+
|
|
3
|
+
**Tab-specific nuances only.** For generic form patterns (required fields, `isDisabled`,
|
|
4
|
+
save action ordering, global actions, edit-existing-record), see
|
|
5
|
+
[pattern-forms.md](pattern-forms.md).
|
|
6
|
+
|
|
7
|
+
## Tabs screen with header
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
const tabs = app.addScreen.tabs({
|
|
11
|
+
screenId: SCREEN.CUSTOMER_CREATE,
|
|
12
|
+
title: 'New Customer',
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
tabs.with({ state: { showDiscardAlert: { initialValue: true } } })
|
|
16
|
+
|
|
17
|
+
const header = tabs.header()
|
|
18
|
+
header.with({ height: 'tiny' })
|
|
19
|
+
header.addControl.image({
|
|
20
|
+
instanceId: 'header-image',
|
|
21
|
+
source: { uri: 'https://images.jigx.com/customers/sales/sales_header.png' },
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Registering tabs with inputs
|
|
26
|
+
|
|
27
|
+
Use `.input()` to pass state from the tabs screen to child screens:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
const generalTab = tabs.addInstance.tab(SCREEN.CUSTOMER_GENERAL, TAB_INSTANCE.GENERAL)
|
|
31
|
+
generalTab.button({ instanceId: 'general-btn', title: 'General', icon: 'contact' })
|
|
32
|
+
generalTab.input('showDiscardAlert', '=@ctx.jig.state.showDiscardAlert')
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Conditional embedded jigs
|
|
36
|
+
|
|
37
|
+
When one tab or detail screen needs to host a variant-specific sub-flow, use
|
|
38
|
+
`component.jig` inside a `jig.default` screen. Render one embedded jig per variant and
|
|
39
|
+
guard each with `when` rather than trying to make `jigId` dynamic.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
screen.addControl.jig({
|
|
43
|
+
instanceId: 'amarr-builder',
|
|
44
|
+
jigId: SCREEN.DOOR_AMARR,
|
|
45
|
+
when: '=@ctx.components.manufacturer.state.value = "AMARR"',
|
|
46
|
+
inputs: {
|
|
47
|
+
doorId: '=@ctx.jig.inputs.doorId',
|
|
48
|
+
lineNumber: '=@ctx.jig.inputs.lineNumber',
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The wrapper owns variant selection. The embedded jig owns its form, validation, save
|
|
54
|
+
action, and item-code construction. Pass stable ids as inputs; do not rely on the
|
|
55
|
+
embedded jig instance id as a local row id.
|
|
56
|
+
|
|
57
|
+
For manufacturer-specific attribute builders, prefer not to use `component.jig` at
|
|
58
|
+
all. Navigate from a chooser/list screen to the concrete manufacturer jig and put the
|
|
59
|
+
save action on that concrete jig. Use wrapper-owned saves only when the embedded
|
|
60
|
+
content is simple enough that cross-jig validation has been proven in the target
|
|
61
|
+
runtime.
|
|
62
|
+
|
|
63
|
+
If the UX requires one parent-level save button, keep the wrapper as the save owner
|
|
64
|
+
instead. In that variant:
|
|
65
|
+
|
|
66
|
+
1. The wrapper owns the bottom-panel action list.
|
|
67
|
+
2. Each embedded jig is field-only. The wrapper reads the active embedded jig's
|
|
68
|
+
component state in the save action, e.g.
|
|
69
|
+
`@ctx.jigs.amarr-builder.components.size.state.selected.valueID`.
|
|
70
|
+
3. The wrapper save action has one conditional `executeEntity` branch per embedded
|
|
71
|
+
variant, guarded by the wrapper selection (`when: =@ctx.components.manufacturer.state.value = "AMARR"`).
|
|
72
|
+
4. Each branch also checks the active embedded jig's form validity before saving,
|
|
73
|
+
e.g. `@ctx.jigs.amarr-builder.components.door-details-form.state.isValid`.
|
|
74
|
+
5. The wrapper save button's disabled expression should depend only on wrapper-owned
|
|
75
|
+
state, such as the selected manufacturer. Do not depend on embedded jig outputs
|
|
76
|
+
for the button style, because parent bottom-panel `isDisabled` can remain stale
|
|
77
|
+
after fields change inside the embedded jig.
|
|
78
|
+
6. Shared wrapper datasources needed by the save, such as the existing local row and
|
|
79
|
+
PDF stale marker row, must live on the wrapper screen because the parent action
|
|
80
|
+
executes in wrapper context.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const amarr = app.addScreen.default({
|
|
84
|
+
screenId: SCREEN.DOOR_AMARR,
|
|
85
|
+
title: 'Amarr Door Builder',
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const form = amarr.addControl.form({ instanceId: 'door-details-form' })
|
|
89
|
+
|
|
90
|
+
screen.addControl.jig({
|
|
91
|
+
instanceId: 'amarr-builder',
|
|
92
|
+
jigId: SCREEN.DOOR_AMARR,
|
|
93
|
+
when: '=@ctx.components.manufacturer.state.value = "AMARR"',
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
screen.bottomPanel().add.list({
|
|
97
|
+
title: 'Save Door',
|
|
98
|
+
isDisabled: '=@ctx.components.manufacturer.state.value ? false:true',
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Use the embedded component state inside the action list at tap time. Add an invalid
|
|
103
|
+
alert before the save branches, guard the state changes and saves with the embedded
|
|
104
|
+
form validity, and read field values from the embedded component state.
|
|
105
|
+
|
|
106
|
+
Do not rely on `@ctx.jigs.<embedded>.outputs` for wrapper-owned save actions unless
|
|
107
|
+
you have verified the current runtime exposes those outputs in the action context.
|
|
108
|
+
In the DoorPro flow, debug logs showed `@ctx.jigs.amarr-builder.outputs` as `null`
|
|
109
|
+
inside the wrapper action, while direct embedded component state remained the safer
|
|
110
|
+
pattern for wrapper-owned saves.
|
|
111
|
+
|
|
112
|
+
If the product requirement is a visually disabled button until every embedded field
|
|
113
|
+
is valid, put the save button inside the same jig as the form state instead of on
|
|
114
|
+
the wrapper.
|
|
115
|
+
|
|
116
|
+
When the parent owns the save button, read embedded component state in the save
|
|
117
|
+
action itself:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
saveActions.showAlert({
|
|
121
|
+
instanceId: 'missing-required-fields',
|
|
122
|
+
when: '=@ctx.components.manufacturer.state.value = "AMARR" ? (@ctx.jigs.amarr-builder.components.door-details-form.state.isValid = true ? false:true) : true',
|
|
123
|
+
title: 'Door Not Complete',
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
saveActions
|
|
127
|
+
.executeEntity({
|
|
128
|
+
when: '=@ctx.components.manufacturer.state.value = "AMARR" and @ctx.jigs.amarr-builder.components.door-details-form.state.isValid = true',
|
|
129
|
+
})
|
|
130
|
+
.dynamicData('default/doorDetails', 'save')
|
|
131
|
+
.data({
|
|
132
|
+
sizeValueID: '=@ctx.jigs.amarr-builder.components.size.state.selected.valueID',
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
For wrapper-owned saves, propagate the wrapper upload/discard state into the embedded
|
|
137
|
+
jigs as an input and bind each embedded form's `isDiscardChangesAlertEnabled` to that
|
|
138
|
+
input. The save action still follows the generic ordering: set wrapper `upload`,
|
|
139
|
+
save, reset wrapper `upload`, then navigate.
|
|
140
|
+
|
|
141
|
+
Do not put critical save actions on a section only to work around embedded jig bottom
|
|
142
|
+
panels not rendering. Use either embedded-owned saves when each embedded jig is a
|
|
143
|
+
standalone interaction, or wrapper-owned saves when the parent screen should expose a
|
|
144
|
+
single consistent save action.
|
|
145
|
+
|
|
146
|
+
## Local correlation id for new tabbed aggregates
|
|
147
|
+
|
|
148
|
+
When a tabs flow creates a new local aggregate, the preferred pattern is:
|
|
149
|
+
|
|
150
|
+
1. Navigate to the new parent screen with a generated jig `instanceId`
|
|
151
|
+
2. Generate one local correlation id and use it for both:
|
|
152
|
+
- the parent screen `instanceId`
|
|
153
|
+
- the parent input that child tabs read as the aggregate key
|
|
154
|
+
3. Pass that same id down to child tabs and child create screens as the parent key
|
|
155
|
+
4. Save the parent row with that id in the local table
|
|
156
|
+
5. Save child rows with `parentId` set to that same local correlation id
|
|
157
|
+
|
|
158
|
+
This is the stable local key for the whole aggregate until remote sync happens.
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const createActions = list.bottomPanel().add.list({
|
|
164
|
+
title: 'New Service Order',
|
|
165
|
+
isPrimary: true,
|
|
166
|
+
concurrency: 'sequential',
|
|
167
|
+
}).actions
|
|
168
|
+
|
|
169
|
+
createActions.setScreenState({
|
|
170
|
+
instanceId: 'set-new-id',
|
|
171
|
+
keys: { newParentId: '=$uuid()' },
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
createActions.goto({
|
|
175
|
+
linkTo: SCREEN.SERVICE_ORDER_CREATE,
|
|
176
|
+
behaviour: 'new',
|
|
177
|
+
instanceId: '=@ctx.jig.state.newParentId',
|
|
178
|
+
parameters: {
|
|
179
|
+
serviceOrderId: '=@ctx.jig.state.newParentId',
|
|
180
|
+
},
|
|
181
|
+
})
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Then inside the tabs flow:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
detailsTab.input('serviceOrderId', '=@ctx.jig.inputs.serviceOrderId')
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
And child rows:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
.data({
|
|
194
|
+
id: '=@ctx.jig.inputs.detailId',
|
|
195
|
+
parentId: '=@ctx.jig.inputs.serviceOrderId',
|
|
196
|
+
})
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
For child create screens, generate the new child id at the `go-to` call and pass it in as an input or parameter. Do not rely on the child jig `instanceId` as the row id.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
addDetail.goto({
|
|
203
|
+
linkTo: SCREEN.SERVICE_ORDER_DETAIL_CREATE,
|
|
204
|
+
behaviour: 'new',
|
|
205
|
+
parameters: {
|
|
206
|
+
serviceOrderId: '=@ctx.jig.inputs.serviceOrderId',
|
|
207
|
+
detailId: '=$uuid()',
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Then the child save can always use the passed `detailId` directly.
|
|
213
|
+
|
|
214
|
+
Important distinction:
|
|
215
|
+
|
|
216
|
+
- the parent input is the authoritative local correlator child tabs should use
|
|
217
|
+
- the parent screen `instanceId` should match that correlator for new flows
|
|
218
|
+
- existing records opened from a list should pass their persisted row id in inputs
|
|
219
|
+
- child tables should keep `parentId` pointing at the stable local parent id
|
|
220
|
+
- if a remote API later needs a different id (for example Acumatica `id` / `NoteID` for file uploads),
|
|
221
|
+
store that in a separate remote-target field and update it during sync operations
|
|
222
|
+
|
|
223
|
+
Do not mix "input id" and fallback `@ctx.jig.instanceId` logic in the same tabbed aggregate flow.
|
|
224
|
+
Use one stable local correlation id end to end.
|
|
225
|
+
|
|
226
|
+
Do not introduce refresh-token workarounds when the real issue is parent-child key consistency.
|
|
227
|
+
If details, photos, or contacts are not showing after save, check the local correlation id flow first.
|
|
228
|
+
|
|
229
|
+
## Tab content screen with form
|
|
230
|
+
|
|
231
|
+
Each tab screen declares the input and wraps fields in a form. See
|
|
232
|
+
[pattern-forms.md](pattern-forms.md) for the generic form rules (required fields,
|
|
233
|
+
button validation, etc.).
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
const screen = app.addScreen.default({ screenId: SCREEN.CUSTOMER_GENERAL, title: 'General' })
|
|
237
|
+
screen.addInput({ name: 'showDiscardAlert', type: 'boolean' })
|
|
238
|
+
|
|
239
|
+
const form = screen.addControl.form({
|
|
240
|
+
instanceId: 'general-form',
|
|
241
|
+
isDiscardChangesAlertEnabled: '=@ctx.jig.inputs.showDiscardAlert',
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
form.addControl.textField({ instanceId: 'CustomerName', label: 'Customer Name', isRequired: true })
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Cross-jig form validation
|
|
248
|
+
|
|
249
|
+
The one thing unique to tabs: combine `isValid` across all child forms from the
|
|
250
|
+
parent tabs screen:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
const IS_VALID = [
|
|
254
|
+
'=@ctx.jigs.general-instance.components.general-form.state.isValid',
|
|
255
|
+
'and @ctx.jigs.billing-instance.components.billing-form.state.isValid',
|
|
256
|
+
'and @ctx.jigs.shipping-instance.components.shipping-form.state.isValid',
|
|
257
|
+
].join(' ')
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Use as `isDisabled` (inverted) on the save button — see
|
|
261
|
+
[pattern-forms.md](pattern-forms.md#save-button--isdisabled-never-when) for why
|
|
262
|
+
`isDisabled` not `when`.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
tabs.bottomPanel().add.executeAction({
|
|
266
|
+
title: 'Save',
|
|
267
|
+
action: 'act-customer-save',
|
|
268
|
+
isDisabled: `=(${IS_VALID}) = true ? false:true`,
|
|
269
|
+
})
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Discard alert across tabs
|
|
273
|
+
|
|
274
|
+
The discard alert pattern in [pattern-forms.md](pattern-forms.md#save-action-list--correct-ordering)
|
|
275
|
+
uses a single-screen local state key (`upload`). For tabs, the state lives on the
|
|
276
|
+
parent tabs screen and propagates to child tabs via inputs:
|
|
277
|
+
|
|
278
|
+
1. **Tabs screen state**: `upload: { initialValue: null }`
|
|
279
|
+
2. **Pass to child tabs** via `.input('upload', '=@ctx.jig.state.upload')`
|
|
280
|
+
3. **Child screens declare** `addInput({ name: 'upload', type: 'boolean' })`
|
|
281
|
+
4. **Form binds**: `isDiscardChangesAlertEnabled: '=@ctx.jig.inputs.upload = true ? false:true'`
|
|
282
|
+
5. **Global action's first step**: `setScreenState({ keys: { upload: true } })` —
|
|
283
|
+
this targets the tabs screen's state (the *calling* screen), which propagates to
|
|
284
|
+
child forms via the input binding
|
|
285
|
+
6. **Global action's last steps**:
|
|
286
|
+
- save-and-close flows: `resetScreenState` → `goBack`
|
|
287
|
+
- save-in-place flows: `resetScreenState`
|
|
288
|
+
See [pattern-forms.md](pattern-forms.md#save-action-list--correct-ordering).
|
|
289
|
+
|
|
290
|
+
**The nuance vs single-screen**: in the tabs pattern, the state lives on the PARENT
|
|
291
|
+
tabs screen, not the child form screens. `setScreenState` targets the calling jig,
|
|
292
|
+
which in this flow is the tabs screen (where the save button lives). Because child
|
|
293
|
+
tabs read the state via `.input()`, they pick up the change on the next render.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Recipe: Core SDK App with Acumatica Integration
|
|
2
|
+
|
|
3
|
+
Build a multi-screen Jigx app using Core SDK with local data, tabbed forms, REST integration, and Acumatica-compatible nested JSON.
|
|
4
|
+
|
|
5
|
+
Reference implementation: `workspace/acumatica-apps/create-customer/`
|
|
6
|
+
|
|
7
|
+
## Pattern files
|
|
8
|
+
|
|
9
|
+
| Pattern | Description |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| [convention-naming.md](convention-naming.md) | File, component, field, and constant naming conventions |
|
|
12
|
+
| [pattern-datasources.md](pattern-datasources.md) | SELECT id/data, jsonProperties, isDocument, config table, schemas folder |
|
|
13
|
+
| [pattern-forms.md](pattern-forms.md) | **Generic form patterns** — required fields, save button isDisabled, save action ordering, global action extraction, edit-existing-record |
|
|
14
|
+
| [pattern-tabs-form.md](pattern-tabs-form.md) | Tab-specific additions — cross-jig validation, tab inputs for discard-alert propagation |
|
|
15
|
+
| [pattern-global-actions.md](pattern-global-actions.md) | Parameter snapshotting, local save action, JSONata save expression, TypeScript helpers |
|
|
16
|
+
| [pattern-rest-acumatica.md](pattern-rest-acumatica.md) | REST functions, Acumatica conventions, functionCall, accessToken, remote sync flag |
|
|
17
|
+
| [pattern-navigation.md](pattern-navigation.md) | behaviour: 'new', $uuid(), bottom panel order, list-detail-child flow, screen-scoped datasources |
|
|
18
|
+
| [pattern-sync.md](pattern-sync.md) | Initial sync, continuation, diff sync, sync scopes, OData vs REST |
|
|
19
|
+
| [pattern-sync-progress.md](pattern-sync-progress.md) | Sync progress bar UI ideas — weights, step text, show/hide, cards |
|
|
20
|
+
| [pattern-javascript-functions.md](pattern-javascript-functions.md) | `app.script()` — extend JSONata with custom JS functions (math, formatting, HTML templates, validation, transformation) |
|
|
21
|
+
| [pattern-pdf-generation.md](pattern-pdf-generation.md) | `generate-pdf` action + `app.script()` + `jig.document` screen — generate, persist, preview, share PDFs |
|
|
22
|
+
| [pattern-build-deploy.md](pattern-build-deploy.md) | Build order, typecheck, JSON/YAML generation, deploy script, Jigx API |
|
|
23
|
+
|
|
24
|
+
## Project structure
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
src/
|
|
28
|
+
├── app.ts # Assembly + build order
|
|
29
|
+
├── common.ts # Screen/action/function/datasource/entity constants
|
|
30
|
+
├── database.ts # Table definitions (config, customers, contacts)
|
|
31
|
+
├── datasources.ts # App-scoped datasources
|
|
32
|
+
├── schemas/
|
|
33
|
+
│ ├── customers.yaml # Schema for default/customers table
|
|
34
|
+
│ └── contacts.yaml # Schema for default/contacts table
|
|
35
|
+
├── functions/
|
|
36
|
+
│ └── rest-put-customer.ts # REST PUT function for Acumatica
|
|
37
|
+
├── actions/
|
|
38
|
+
│ ├── act-customer-save.ts # Save customer locally
|
|
39
|
+
│ └── act-customer-save-remote.ts # Save customer to Acumatica via REST
|
|
40
|
+
└── screens/
|
|
41
|
+
├── jig-customer-list.ts # List screen with header sync action
|
|
42
|
+
├── jig-customer-general.ts # Tab 1 content (default screen with form)
|
|
43
|
+
├── jig-customer-billing.ts # Tab 2 content
|
|
44
|
+
├── jig-customer-shipping.ts # Tab 3 content
|
|
45
|
+
├── jig-customer-create.ts # Tabs screen (orchestrator)
|
|
46
|
+
├── jig-customer-view.ts # Detail view
|
|
47
|
+
└── jig-contact-create.ts # Child entity form
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Schema-driven field design
|
|
51
|
+
|
|
52
|
+
Start with the external API schema (e.g., `acumatica schemas/Customers.yaml`) to drive field creation. The schema documents:
|
|
53
|
+
|
|
54
|
+
- Field paths (`data.CustomerName.value`, `data.MainContact.Email.value`)
|
|
55
|
+
- Nested objects (`MainContact`, `BillingContact`, `ShippingContact`, `PrimaryContact`)
|
|
56
|
+
- Field types and enumerations
|
|
57
|
+
- Relationships to other entities
|
|
58
|
+
|
|
59
|
+
Use the schema to:
|
|
60
|
+
|
|
61
|
+
1. Name form field `instanceId` values to match schema field names
|
|
62
|
+
2. Structure the save JSONata expression to match the schema's nesting
|
|
63
|
+
3. Identify required fields from the UI design (orange-highlighted labels in mockups)
|
|
64
|
+
4. Map sections to schema objects (Account Address → top-level, Primary Contact → `PrimaryContact`, etc.)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
## Import Statement
|
|
2
|
+
|
|
3
|
+
```typescript
|
|
4
|
+
import { Auth, System, JsonataBuilder } from '@jigx/expert-sdk'
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
## System Variables
|
|
8
|
+
|
|
9
|
+
### Device & Environment
|
|
10
|
+
|
|
11
|
+
| Variable | Type | Description |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `System.deviceType` | string | Device type ('ios', 'android') |
|
|
14
|
+
| `System.isOnline` | boolean | Network connected |
|
|
15
|
+
| `System.isOffline` | boolean | Network disconnected |
|
|
16
|
+
| `System.isPortrait` | boolean | Portrait orientation |
|
|
17
|
+
| `System.locale` | string | User's locale setting |
|
|
18
|
+
| `System.appVersion` | string | Current app version |
|
|
19
|
+
|
|
20
|
+
### Location
|
|
21
|
+
|
|
22
|
+
| Variable | Type | Description |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| `System.isLocationSharingEnabled` | boolean | Location permission granted |
|
|
25
|
+
| `System.geolocation.coords.latitude` | number | GPS latitude |
|
|
26
|
+
| `System.geolocation.coords.longitude` | number | GPS longitude |
|
|
27
|
+
| `System.geolocation.coords.accuracy` | number | GPS accuracy (meters) |
|
|
28
|
+
| `System.geolocation.coords.altitude` | number | GPS altitude |
|
|
29
|
+
| `System.geolocation.coords.speed` | number | Movement speed |
|
|
30
|
+
| `System.geolocation.coords.heading` | number | Movement direction |
|
|
31
|
+
| `System.geolocation.timestamp` | number | Location timestamp |
|
|
32
|
+
|
|
33
|
+
### Timezone
|
|
34
|
+
|
|
35
|
+
| Variable | Type | Description |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| `System.timezone.name` | string | Timezone name (e.g., 'America/New_York') |
|
|
38
|
+
| `System.timezone.offset` | number | UTC offset in minutes |
|
|
39
|
+
|
|
40
|
+
## User Variables (Auth.user)
|
|
41
|
+
|
|
42
|
+
| Variable | Type | Description |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| `Auth.user.id` | string | User's unique identifier |
|
|
45
|
+
| `Auth.user.displayName` | string | User's display name |
|
|
46
|
+
| `Auth.user.email` | string | User's email address |
|
|
47
|
+
| `Auth.user.phone` | string | User's phone number |
|
|
48
|
+
| `Auth.user.avatarUrl` | string | User's avatar image URL |
|
|
49
|
+
| `Auth.user.isVerified` | boolean | Account verification status |
|
|
50
|
+
|
|
51
|
+
## Usage Examples
|
|
52
|
+
|
|
53
|
+
### Pre-fill with User Data
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
step.addText({
|
|
57
|
+
name: 'name',
|
|
58
|
+
label: 'Name',
|
|
59
|
+
value: new JsonataBuilder('$userName', { userName: Auth.user.displayName }),
|
|
60
|
+
})
|
|
61
|
+
step.addEmail({
|
|
62
|
+
name: 'email',
|
|
63
|
+
label: 'Email',
|
|
64
|
+
value: new JsonataBuilder('$userEmail', { userEmail: Auth.user.email }),
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Conditional Based on Network
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
step.addText({
|
|
72
|
+
name: 'status',
|
|
73
|
+
label: 'Status',
|
|
74
|
+
value: new JsonataBuilder('$online ? "Connected" : "Offline"', { online: System.isOnline }),
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Device-Specific Logic
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
step.addDropdown({
|
|
82
|
+
name: 'selection',
|
|
83
|
+
label: 'Selection',
|
|
84
|
+
data: [
|
|
85
|
+
{ label: 'Option A', value: 'a' },
|
|
86
|
+
{ label: 'Option B', value: 'b' },
|
|
87
|
+
],
|
|
88
|
+
helperText: new JsonataBuilder('$device = "ios" ? "Tap to select" : "Touch to select"', {
|
|
89
|
+
device: System.deviceType,
|
|
90
|
+
}),
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Location-Based Features
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
step.addLocation({ name: 'location', label: 'Current Location' })
|
|
98
|
+
// Location field auto-configures with System.geolocation
|
|
99
|
+
// Or manual access:
|
|
100
|
+
step.addText({
|
|
101
|
+
name: 'coords',
|
|
102
|
+
label: 'Coordinates',
|
|
103
|
+
value: new JsonataBuilder('$lat & "," & $lng', {
|
|
104
|
+
lat: System.geolocation.coords.latitude,
|
|
105
|
+
lng: System.geolocation.coords.longitude,
|
|
106
|
+
}),
|
|
107
|
+
})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Timezone Display
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
step.addText({
|
|
114
|
+
name: 'timezone',
|
|
115
|
+
label: 'Timezone',
|
|
116
|
+
helperText: new JsonataBuilder('"Your timezone: " & $tz', { tz: System.timezone.name }),
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Availability
|
|
121
|
+
|
|
122
|
+
System and Auth variables are accessible in all `JsonataBuilder` contexts:
|
|
123
|
+
|
|
124
|
+
- Field properties (value, helperText, isVisible, etc.)
|
|
125
|
+
- Step properties (title, description, isVisible)
|
|
126
|
+
- Submission item (submissionItemTitle, submissionItemSubtitle)
|
|
127
|
+
- Form metadata (description)
|
package/docs/sections.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
## Creating Sections
|
|
2
|
+
|
|
3
|
+
```typescript
|
|
4
|
+
// Section for contact info
|
|
5
|
+
const contactSection = step.addSection({ title: 'Contact Information' })
|
|
6
|
+
contactSection.addEmail({ name: 'email', label: 'Email' })
|
|
7
|
+
contactSection.addPhone({ name: 'phone', label: 'Phone' })
|
|
8
|
+
// Section for address
|
|
9
|
+
const addressSection = step.addSection({ title: 'Address' })
|
|
10
|
+
addressSection.addText({ name: 'street', label: 'Street' })
|
|
11
|
+
addressSection.addText({ name: 'city', label: 'City' })
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**Important:** Add fields to the section, not the step directly:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// Correct
|
|
18
|
+
section.addText({ name: 'field', label: 'Field' })
|
|
19
|
+
|
|
20
|
+
// Wrong - adds to step, not section
|
|
21
|
+
step.addText({ name: 'field', label: 'Field' })
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Section Properties
|
|
25
|
+
|
|
26
|
+
Pass in constructor or use `.with()`:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
const toggleField = step.addCheckbox({ name: 'show-section', label: 'Show Issues' })
|
|
30
|
+
const section = step.addSection({
|
|
31
|
+
title: 'Issues & Evidence',
|
|
32
|
+
subtitle: 'Describe any problems encountered',
|
|
33
|
+
})
|
|
34
|
+
// Or update later
|
|
35
|
+
section.with({
|
|
36
|
+
title: 'Updated Title',
|
|
37
|
+
isVisible: new JsonataBuilder('$show = true', { show: toggleField.state.value }),
|
|
38
|
+
})
|
|
39
|
+
section.addText({ name: 'description', label: 'Description' })
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Best Practices
|
|
43
|
+
|
|
44
|
+
**Section titles:**
|
|
45
|
+
|
|
46
|
+
- Keep concise (under 20 characters)
|
|
47
|
+
- Use clear, descriptive names
|
|
48
|
+
- Examples: "Contact Info", "Preferences", "Billing Details"
|
|
49
|
+
|
|
50
|
+
**Grouping logic:**
|
|
51
|
+
|
|
52
|
+
- Contact info: email, phone, address
|
|
53
|
+
- Personal: name, date of birth, gender
|
|
54
|
+
- Professional: company, title, department
|
|
55
|
+
- Preferences: notification settings, language
|
|
56
|
+
|
|
57
|
+
## Example: Multi-Section Step
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
form.addStep({ instanceId: 'profile', icon: 'single-neutral' }, (step) => {
|
|
61
|
+
// Personal section
|
|
62
|
+
const personal = step.addSection({ title: 'Personal' })
|
|
63
|
+
personal.addText({ name: 'full-name', label: 'Full Name' })
|
|
64
|
+
personal.addDate({ name: 'dob', label: 'Date of Birth' })
|
|
65
|
+
// Contact section
|
|
66
|
+
const contact = step.addSection({ title: 'Contact' })
|
|
67
|
+
contact.addEmail({ name: 'email', label: 'Email' })
|
|
68
|
+
contact.addPhone({ name: 'phone', label: 'Phone' })
|
|
69
|
+
// Preferences section
|
|
70
|
+
const prefs = step.addSection({ title: 'Preferences' })
|
|
71
|
+
prefs.addCheckbox({ name: 'newsletter', label: 'Subscribe to newsletter' })
|
|
72
|
+
prefs.addDropdown({
|
|
73
|
+
name: 'language',
|
|
74
|
+
label: 'Language',
|
|
75
|
+
data: [
|
|
76
|
+
{ label: 'English', value: 'en' },
|
|
77
|
+
{ label: 'Spanish', value: 'es' },
|
|
78
|
+
],
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
```
|