@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
|
@@ -2617,6 +2617,8 @@ types:
|
|
|
2617
2617
|
fileName: Expression|string
|
|
2618
2618
|
/** HTML content to convert to PDF */
|
|
2619
2619
|
html: Expression|string
|
|
2620
|
+
/** Optional page size, orientation, and margins. Defaults to letter portrait with no margins. */
|
|
2621
|
+
page?: PdfPageOptions
|
|
2620
2622
|
}
|
|
2621
2623
|
GoBackActionInput:
|
|
2622
2624
|
domain: action
|
|
@@ -3707,11 +3709,13 @@ types:
|
|
|
3707
3709
|
* @param options.fileName - PDF file name
|
|
3708
3710
|
* @param options.html - HTML content
|
|
3709
3711
|
* @param options.instanceId - Instance ID for tracking
|
|
3712
|
+
* @param options.page - Optional page size, orientation, and margins
|
|
3710
3713
|
*/
|
|
3711
3714
|
generatePdf(options: {
|
|
3712
3715
|
fileName: Expression|string
|
|
3713
3716
|
html: Expression|string
|
|
3714
3717
|
instanceId?: KegInstanceId|string
|
|
3718
|
+
page?: PdfPageOptions
|
|
3715
3719
|
}): JaGeneratePdfBuilder
|
|
3716
3720
|
/**
|
|
3717
3721
|
* Create an open-app-settings action builder
|
|
@@ -9529,6 +9533,60 @@ types:
|
|
|
9529
9533
|
type PathsData = Expression|string | {
|
|
9530
9534
|
data: Expression|string
|
|
9531
9535
|
}
|
|
9536
|
+
PdfPageMargin:
|
|
9537
|
+
domain: action
|
|
9538
|
+
code: |-
|
|
9539
|
+
/**
|
|
9540
|
+
* PDF page margin specification.
|
|
9541
|
+
*
|
|
9542
|
+
* - `'none'` — zero margins on all sides (default).
|
|
9543
|
+
* - `number` — uniform margin in PDF points on all sides (72pt = 1in).
|
|
9544
|
+
* - object — independent per-side margins in PDF points; omitted sides default to 0.
|
|
9545
|
+
*/
|
|
9546
|
+
type PdfPageMargin = 'none' | number | {
|
|
9547
|
+
top?: number
|
|
9548
|
+
right?: number
|
|
9549
|
+
bottom?: number
|
|
9550
|
+
left?: number
|
|
9551
|
+
}
|
|
9552
|
+
PdfPageOptions:
|
|
9553
|
+
domain: action
|
|
9554
|
+
code: |-
|
|
9555
|
+
/**
|
|
9556
|
+
* PDF page configuration.
|
|
9557
|
+
*
|
|
9558
|
+
* The runtime constructs the physical page bounds from these values before the
|
|
9559
|
+
* HTML is rendered. CSS `@page` rules do not control the PDF page size — to
|
|
9560
|
+
* avoid overflow, match the body dimensions to the configured size in points.
|
|
9561
|
+
*
|
|
9562
|
+
* When `width` and `height` are both provided they override `size`/`orientation`.
|
|
9563
|
+
*
|
|
9564
|
+
* @see PdfPageSize
|
|
9565
|
+
* @see PdfPageOrientation
|
|
9566
|
+
* @see PdfPageMargin
|
|
9567
|
+
*/
|
|
9568
|
+
interface PdfPageOptions {
|
|
9569
|
+
/** Named paper size. Default: `'letter'`. */
|
|
9570
|
+
size?: PdfPageSize
|
|
9571
|
+
/** Page orientation. Default: `'portrait'`. */
|
|
9572
|
+
orientation?: PdfPageOrientation
|
|
9573
|
+
/** Custom page width in PDF points (72pt = 1in). When set with `height`, overrides `size`/`orientation`. */
|
|
9574
|
+
width?: number
|
|
9575
|
+
/** Custom page height in PDF points (72pt = 1in). When set with `width`, overrides `size`/`orientation`. */
|
|
9576
|
+
height?: number
|
|
9577
|
+
/** Page margins. Default: `'none'`. */
|
|
9578
|
+
margin?: PdfPageMargin
|
|
9579
|
+
}
|
|
9580
|
+
PdfPageOrientation:
|
|
9581
|
+
domain: action
|
|
9582
|
+
code: |-
|
|
9583
|
+
/** PDF page orientation. */
|
|
9584
|
+
type PdfPageOrientation = 'portrait' | 'landscape'
|
|
9585
|
+
PdfPageSize:
|
|
9586
|
+
domain: action
|
|
9587
|
+
code: |-
|
|
9588
|
+
/** Named PDF paper sizes. */
|
|
9589
|
+
type PdfPageSize = 'letter' | 'legal' | 'tabloid' | 'a4' | 'a3'
|
|
9532
9590
|
PieChartLayout:
|
|
9533
9591
|
domain: component
|
|
9534
9592
|
code: type PieChartLayout = 'pie' | 'arch'
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0.0",
|
|
3
|
-
"sourceHash": "
|
|
4
|
-
"timestamp":
|
|
3
|
+
"sourceHash": "4054b27eb8116771508a3e3fbe66d3cf",
|
|
4
|
+
"timestamp": 1778854916954,
|
|
5
5
|
"stats": {
|
|
6
6
|
"inputFiles": 300,
|
|
7
|
-
"extractionTimeMs":
|
|
7
|
+
"extractionTimeMs": 11351
|
|
8
8
|
}
|
|
9
9
|
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
## Basic Setup
|
|
2
|
+
|
|
3
|
+
Create an array field and set its required title:
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
const members = step.addArrayField({ name: 'members', label: 'Team Members' })
|
|
7
|
+
// Title is required - identifies items in list
|
|
8
|
+
members.with({
|
|
9
|
+
title: new JsonataBuilder('$name', { name: members.getCurrentItemRef('name') }),
|
|
10
|
+
})
|
|
11
|
+
// Add fields to each item
|
|
12
|
+
members.addText({ name: 'name', label: 'Name' })
|
|
13
|
+
members.addEmail({ name: 'email', label: 'Email' })
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## getCurrentItemRef() - Item Field References
|
|
17
|
+
|
|
18
|
+
Reference fields within the current array item using `getCurrentItemRef()`:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
const members = step.addArrayField({ name: 'members', label: 'Members' })
|
|
22
|
+
// Single field reference
|
|
23
|
+
members.with({
|
|
24
|
+
title: new JsonataBuilder('$n', { n: members.getCurrentItemRef('name') }),
|
|
25
|
+
})
|
|
26
|
+
// Combine multiple fields
|
|
27
|
+
members.with({
|
|
28
|
+
subtitle: new JsonataBuilder('$first & " " & $last', {
|
|
29
|
+
first: members.getCurrentItemRef('firstName'),
|
|
30
|
+
last: members.getCurrentItemRef('lastName'),
|
|
31
|
+
}),
|
|
32
|
+
})
|
|
33
|
+
members.addText({ name: 'name', label: 'Name' })
|
|
34
|
+
members.addText({ name: 'firstName', label: 'First Name' })
|
|
35
|
+
members.addText({ name: 'lastName', label: 'Last Name' })
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## getCurrentItemLabelRef() - Dropdown Labels
|
|
39
|
+
|
|
40
|
+
For dropdown fields inside arrayField, use `getCurrentItemLabelRef()` to display the human-readable label instead of the value id:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
const items = step.addArrayField({
|
|
44
|
+
name: 'orderItems',
|
|
45
|
+
label: 'Order Items',
|
|
46
|
+
})
|
|
47
|
+
// Display dropdown LABEL (e.g., "Electronics") in item title
|
|
48
|
+
// instead of dropdown VALUE (e.g., "electronics")
|
|
49
|
+
items.with({
|
|
50
|
+
title: new JsonataBuilder('$product & " - " & $categoryLabel', {
|
|
51
|
+
product: items.getCurrentItemRef('productName'),
|
|
52
|
+
categoryLabel: items.getCurrentItemLabelRef('category'),
|
|
53
|
+
}),
|
|
54
|
+
})
|
|
55
|
+
items.addText({ name: 'productName', label: 'Product Name' })
|
|
56
|
+
items.addDropdown({
|
|
57
|
+
name: 'category',
|
|
58
|
+
label: 'Category',
|
|
59
|
+
data: [
|
|
60
|
+
{ label: 'Electronics', value: 'electronics' },
|
|
61
|
+
{ label: 'Clothing', value: 'clothing' },
|
|
62
|
+
{ label: 'Home & Garden', value: 'home' },
|
|
63
|
+
],
|
|
64
|
+
})
|
|
65
|
+
items.addNumber({ name: 'quantity', label: 'Quantity' })
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| Method | Returns | Use Case |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| `arrayField.getCurrentItemRef('dropdown')` | Value (id) | Business logic |
|
|
71
|
+
| `arrayField.getCurrentItemLabelRef('dropdown')` | Label (text) | Display in title/subtitle |
|
|
72
|
+
|
|
73
|
+
## Item Display
|
|
74
|
+
|
|
75
|
+
Customize how items appear in the list:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
const items = step.addArrayField({ name: 'items', label: 'Order Items' })
|
|
79
|
+
// Required title
|
|
80
|
+
items.with({
|
|
81
|
+
title: new JsonataBuilder('$name', { name: items.getCurrentItemRef('productName') }),
|
|
82
|
+
})
|
|
83
|
+
// Optional subtitle with formatted currency
|
|
84
|
+
items.with({
|
|
85
|
+
subtitle: new I18nBuilder('item.subtitle', 'Qty: {qty} × {price}', {
|
|
86
|
+
qty: new JsonataBuilder('$qty', {
|
|
87
|
+
qty: items.getCurrentItemRef('quantity'),
|
|
88
|
+
}),
|
|
89
|
+
price: new FormatBuilder(
|
|
90
|
+
new JsonataBuilder('$price', {
|
|
91
|
+
price: items.getCurrentItemRef('unitPrice'),
|
|
92
|
+
}),
|
|
93
|
+
{ numberStyle: 'currency', currency: 'USD' },
|
|
94
|
+
),
|
|
95
|
+
}),
|
|
96
|
+
})
|
|
97
|
+
// Optional description
|
|
98
|
+
items.with({
|
|
99
|
+
description: new JsonataBuilder('$notes', { notes: items.getCurrentItemRef('notes') }),
|
|
100
|
+
})
|
|
101
|
+
items.addText({ name: 'productName', label: 'Product Name' })
|
|
102
|
+
items.addNumber({ name: 'quantity', label: 'Quantity' })
|
|
103
|
+
items.addNumber({ name: 'unitPrice', label: 'Unit Price' })
|
|
104
|
+
items.addText({ name: 'notes', label: 'Notes' })
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Nested Fields
|
|
108
|
+
|
|
109
|
+
Array fields support all standard field types:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const items = step.addArrayField({ name: 'items', label: 'Line Items' })
|
|
113
|
+
items.with({
|
|
114
|
+
title: new JsonataBuilder('$p', { p: items.getCurrentItemRef('product') }),
|
|
115
|
+
})
|
|
116
|
+
items.addText({ name: 'product', label: 'Product Name' })
|
|
117
|
+
items.addNumber({ name: 'quantity', label: 'Quantity' })
|
|
118
|
+
items.addNumber({ name: 'unitPrice', label: 'Unit Price' })
|
|
119
|
+
items.addDropdown({
|
|
120
|
+
name: 'category',
|
|
121
|
+
label: 'Category',
|
|
122
|
+
data: [
|
|
123
|
+
{ label: 'Electronics', value: 'electronics' },
|
|
124
|
+
{ label: 'Clothing', value: 'clothing' },
|
|
125
|
+
],
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Sections Within Array Fields
|
|
130
|
+
|
|
131
|
+
Group related fields within array items:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
const items = step.addArrayField({
|
|
135
|
+
name: 'items',
|
|
136
|
+
label: 'Order Items',
|
|
137
|
+
addButtonLabel: 'Add Item',
|
|
138
|
+
})
|
|
139
|
+
// Set title for array items - need to use .with() since we need the variable declared first
|
|
140
|
+
items.with({
|
|
141
|
+
title: new JsonataBuilder('$productName', {
|
|
142
|
+
productName: items.getCurrentItemRef('product-name'),
|
|
143
|
+
}),
|
|
144
|
+
})
|
|
145
|
+
const section = items.addSection()
|
|
146
|
+
section.with({ title: 'Product Details' })
|
|
147
|
+
section.addText({ name: 'product-name', label: 'Product' })
|
|
148
|
+
section.addNumber({ name: 'quantity', label: 'Quantity' })
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Configuration Options
|
|
152
|
+
|
|
153
|
+
Customize array behavior:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const tasks = step.addArrayField({
|
|
157
|
+
name: 'tasks',
|
|
158
|
+
label: 'Tasks',
|
|
159
|
+
addButtonLabel: 'Add Task', // Custom button text
|
|
160
|
+
})
|
|
161
|
+
tasks.with({
|
|
162
|
+
title: new JsonataBuilder('$t', { t: tasks.getCurrentItemRef('taskName') }),
|
|
163
|
+
})
|
|
164
|
+
tasks.addText({ name: 'taskName', label: 'Task Name' })
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Conditional Visibility
|
|
168
|
+
|
|
169
|
+
Show/hide array field based on conditions:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
const hasTeam = step.addCheckbox({
|
|
173
|
+
name: 'has-team',
|
|
174
|
+
label: 'This project has a team',
|
|
175
|
+
})
|
|
176
|
+
const teamMembers = step.addArrayField({
|
|
177
|
+
name: 'team-members',
|
|
178
|
+
label: 'Team Members',
|
|
179
|
+
addButtonLabel: 'Add Team Member',
|
|
180
|
+
// Option 1: Set title in constructor
|
|
181
|
+
title: 'Team Member',
|
|
182
|
+
})
|
|
183
|
+
// Option 2: Set title via .with()
|
|
184
|
+
teamMembers.with({
|
|
185
|
+
// Can't use in constructor because the variable doesn't exist yet
|
|
186
|
+
title: new JsonataBuilder('$name', { name: teamMembers.getCurrentItemRef('member-name') }),
|
|
187
|
+
// Use Jsonata expression to show array field only when checkbox is checked
|
|
188
|
+
isVisible: new JsonataBuilder('$hasTeam = true', { hasTeam: hasTeam.state.value }),
|
|
189
|
+
})
|
|
190
|
+
teamMembers.addText({
|
|
191
|
+
name: 'member-name',
|
|
192
|
+
label: 'Name',
|
|
193
|
+
})
|
|
194
|
+
teamMembers.addEmail({
|
|
195
|
+
name: 'member-email',
|
|
196
|
+
label: 'Email',
|
|
197
|
+
})
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Formatting in Array Item title / subtitle / description
|
|
201
|
+
|
|
202
|
+
**Date fields MUST use `FormatBuilder`** in arrayField `title`, `subtitle`, and `description`. Without it, unreadable ISO 8601 strings like `"2024-01-15T14:30:00Z"` display instead of human-readable dates.
|
|
203
|
+
|
|
204
|
+
**Number/currency fields** should also use `FormatBuilder` for locale-aware display instead of JSONata string concatenation.
|
|
205
|
+
|
|
206
|
+
**Ref:** `./formatting.md` for `FormatBuilder` usage, date format codes, and common mistakes
|
|
207
|
+
|
|
208
|
+
## Retrieving Array Data
|
|
209
|
+
|
|
210
|
+
Access array items from other steps using `getArrayFieldData()`:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
// Step 1: Collect team members
|
|
214
|
+
const step1 = form.addStep({ instanceId: 'team' }, (step) => {
|
|
215
|
+
const members = step.addArrayField({
|
|
216
|
+
name: 'members',
|
|
217
|
+
label: 'Team Members',
|
|
218
|
+
})
|
|
219
|
+
members.with({
|
|
220
|
+
title: new JsonataBuilder('$name', { name: members.getCurrentItemRef('name') }),
|
|
221
|
+
})
|
|
222
|
+
members.addText({ name: 'name', label: 'Name' })
|
|
223
|
+
members.addText({ name: 'role', label: 'Role' })
|
|
224
|
+
})
|
|
225
|
+
step1.with({ icon: 'multiple-users-3', title: 'Team Members' })
|
|
226
|
+
// Step 2: Select team lead from collected members
|
|
227
|
+
const step2 = form.addStep({ instanceId: 'lead' }, (step) => {
|
|
228
|
+
step.addDropdown({
|
|
229
|
+
name: 'teamLead',
|
|
230
|
+
label: 'Select Team Lead',
|
|
231
|
+
// IMPORTANT: Use getArrayFieldData to map arrayField items to dropdown options
|
|
232
|
+
data: step1.data.getArrayFieldData('members'),
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
step2.with({ icon: 'messages-people-user-check', title: 'Select Lead' })
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Custom mapping:**
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
const step1 = form.addStep({ instanceId: 'step1' }, (step) => {
|
|
242
|
+
const items = step.addArrayField({ name: 'items', label: 'Items', title: 'Item' })
|
|
243
|
+
items.addText({ name: 'code', label: 'Code' })
|
|
244
|
+
items.addText({ name: 'description', label: 'Description' })
|
|
245
|
+
})
|
|
246
|
+
step1.with({ icon: 'list', title: 'Items' })
|
|
247
|
+
const step2 = form.addStep({ instanceId: 'step2' }, (step) => {
|
|
248
|
+
step.addDropdown({
|
|
249
|
+
name: 'selectedItem',
|
|
250
|
+
label: 'Select Item',
|
|
251
|
+
// IMPORTANT: Use getArrayFieldData to map arrayField items to dropdown options
|
|
252
|
+
data: step1.data.getArrayFieldData('items', {
|
|
253
|
+
valueKey: 'code',
|
|
254
|
+
labelKey: 'description',
|
|
255
|
+
}),
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
step2.with({ icon: 'check', title: 'Select' })
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Data Structure
|
|
262
|
+
|
|
263
|
+
Each array item has this structure:
|
|
264
|
+
|
|
265
|
+
| Property | Description |
|
|
266
|
+
| --- | --- |
|
|
267
|
+
| `id` | Auto-generated UUID (used as default dropdown value) |
|
|
268
|
+
| `item_title` | Captured from title expression (used as default dropdown label) |
|
|
269
|
+
| `<fieldName>` | Values from each nested field |
|
|
270
|
+
|
|
271
|
+
**item_title mechanism:** A hidden field automatically captures the evaluated title expression when saving. This enables dropdown compatibility - when you use `getArrayFieldData()`, the `item_title` is available as the default label without re-evaluating the expression.
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
// Example data structure after user adds items:
|
|
275
|
+
[
|
|
276
|
+
{ id: "uuid-1", item_title: "John Doe", name: "John Doe", email: "john@example.com" },
|
|
277
|
+
{ id: "uuid-2", item_title: "Jane Smith", name: "Jane Smith", email: "jane@example.com" }
|
|
278
|
+
]
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Use Case: Team Members
|
|
282
|
+
|
|
283
|
+
Classic pattern for collecting people with name/email:
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
const members = step.addArrayField({
|
|
287
|
+
name: 'teamMembers',
|
|
288
|
+
label: 'Team Members',
|
|
289
|
+
addButtonLabel: 'Add Member',
|
|
290
|
+
})
|
|
291
|
+
members.with({
|
|
292
|
+
title: new JsonataBuilder('$name', { name: members.getCurrentItemRef('fullName') }),
|
|
293
|
+
})
|
|
294
|
+
members.with({
|
|
295
|
+
subtitle: new JsonataBuilder('$role', { role: members.getCurrentItemRef('role') }),
|
|
296
|
+
})
|
|
297
|
+
members.addText({ name: 'fullName', label: 'Full Name' })
|
|
298
|
+
members.addEmail({ name: 'email', label: 'Email' })
|
|
299
|
+
members.addDropdown({
|
|
300
|
+
name: 'role',
|
|
301
|
+
label: 'Role',
|
|
302
|
+
data: [
|
|
303
|
+
{ label: 'Developer', value: 'developer' },
|
|
304
|
+
{ label: 'Designer', value: 'designer' },
|
|
305
|
+
{ label: 'Manager', value: 'manager' },
|
|
306
|
+
],
|
|
307
|
+
})
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Use Case: Line Items
|
|
311
|
+
|
|
312
|
+
Invoice or order items with quantities:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
const items = step.addArrayField({
|
|
316
|
+
name: 'lineItems',
|
|
317
|
+
label: 'Order Items',
|
|
318
|
+
})
|
|
319
|
+
items.with({
|
|
320
|
+
title: new JsonataBuilder('$product', {
|
|
321
|
+
product: items.getCurrentItemRef('productName'),
|
|
322
|
+
}),
|
|
323
|
+
})
|
|
324
|
+
items.with({
|
|
325
|
+
subtitle: new I18nBuilder('line-item.subtitle', 'Qty: {qty} × {price}', {
|
|
326
|
+
qty: new JsonataBuilder('$qty', {
|
|
327
|
+
qty: items.getCurrentItemRef('quantity'),
|
|
328
|
+
}),
|
|
329
|
+
price: new FormatBuilder(
|
|
330
|
+
new JsonataBuilder('$price', {
|
|
331
|
+
price: items.getCurrentItemRef('unitPrice'),
|
|
332
|
+
}),
|
|
333
|
+
{ numberStyle: 'currency', currency: 'USD' },
|
|
334
|
+
),
|
|
335
|
+
}),
|
|
336
|
+
})
|
|
337
|
+
items.addText({ name: 'productName', label: 'Product' })
|
|
338
|
+
items.addNumber({ name: 'quantity', label: 'Quantity' })
|
|
339
|
+
items.addNumber({ name: 'unitPrice', label: 'Unit Price' })
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Use Case: Inspection Checklist
|
|
343
|
+
|
|
344
|
+
Status tracking with notes:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
const checks = step.addArrayField({
|
|
348
|
+
name: 'inspectionItems',
|
|
349
|
+
label: 'Inspection Checklist',
|
|
350
|
+
})
|
|
351
|
+
checks.with({
|
|
352
|
+
title: new JsonataBuilder('$item', { item: checks.getCurrentItemRef('checkItem') }),
|
|
353
|
+
})
|
|
354
|
+
checks.with({
|
|
355
|
+
subtitle: new JsonataBuilder('$uppercase($status)', {
|
|
356
|
+
status: checks.getCurrentItemRef('status'),
|
|
357
|
+
}),
|
|
358
|
+
})
|
|
359
|
+
checks.addText({ name: 'checkItem', label: 'Item to Check' })
|
|
360
|
+
checks.addDropdown({
|
|
361
|
+
name: 'status',
|
|
362
|
+
label: 'Status',
|
|
363
|
+
data: [
|
|
364
|
+
{ label: 'Pass', value: 'pass', icon: 'check-circle-1' },
|
|
365
|
+
{ label: 'Fail', value: 'fail', icon: 'remove-circle' },
|
|
366
|
+
{ label: 'N/A', value: 'na', icon: 'remove-circle-1' },
|
|
367
|
+
],
|
|
368
|
+
})
|
|
369
|
+
checks.addText({ name: 'notes', label: 'Notes', isMultiline: true })
|
|
370
|
+
checks.addMedia({ name: 'photo', label: 'Photo Evidence' })
|
|
371
|
+
```
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
## Basic Conditional Visibility
|
|
2
|
+
|
|
3
|
+
```typescript
|
|
4
|
+
const attendanceType = step.addDropdown({
|
|
5
|
+
name: 'attendance',
|
|
6
|
+
label: 'Attendance Type',
|
|
7
|
+
data: [
|
|
8
|
+
{ label: 'In Person', value: 'in-person' },
|
|
9
|
+
{ label: 'Virtual', value: 'virtual' },
|
|
10
|
+
],
|
|
11
|
+
})
|
|
12
|
+
// Show only for in-person attendance
|
|
13
|
+
step.addText({
|
|
14
|
+
name: 'hotel-preferences',
|
|
15
|
+
label: 'Hotel Preferences',
|
|
16
|
+
isVisible: new JsonataBuilder('$attendance = "in-person"', {
|
|
17
|
+
attendance: attendanceType.state.value,
|
|
18
|
+
}),
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Conditional Required
|
|
23
|
+
|
|
24
|
+
Often combine with visibility - if a field is shown, it should be required:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
const attendanceType = step.addDropdown({
|
|
28
|
+
name: 'attendance',
|
|
29
|
+
label: 'Attendance Type',
|
|
30
|
+
data: [
|
|
31
|
+
{ label: 'In Person', value: 'in-person' },
|
|
32
|
+
{ label: 'Virtual', value: 'virtual' },
|
|
33
|
+
],
|
|
34
|
+
})
|
|
35
|
+
const visibilityCondition = new JsonataBuilder('$attendance = "in-person"', {
|
|
36
|
+
attendance: attendanceType.state.value,
|
|
37
|
+
})
|
|
38
|
+
// Show and require together using same condition
|
|
39
|
+
step.addText({
|
|
40
|
+
name: 'hotel-preferences',
|
|
41
|
+
label: 'Hotel Preferences',
|
|
42
|
+
isVisible: visibilityCondition,
|
|
43
|
+
isRequired: new JsonataBuilder('$attendance = "in-person"', {
|
|
44
|
+
attendance: attendanceType.state.value,
|
|
45
|
+
}),
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Best practice:** Apply same condition to both `isVisible` and `isRequired`.
|
|
50
|
+
|
|
51
|
+
## Multiple Conditions
|
|
52
|
+
|
|
53
|
+
Combine conditions with `and`/`or`:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const attendance = step.addDropdown({
|
|
57
|
+
name: 'attendance',
|
|
58
|
+
label: 'Attendance',
|
|
59
|
+
data: [
|
|
60
|
+
{ label: 'In Person', value: 'in-person' },
|
|
61
|
+
{ label: 'Virtual', value: 'virtual' },
|
|
62
|
+
],
|
|
63
|
+
})
|
|
64
|
+
const needsAccommodation = step.addCheckbox({
|
|
65
|
+
name: 'needs-accommodation',
|
|
66
|
+
label: 'I need accommodation',
|
|
67
|
+
})
|
|
68
|
+
// Show when BOTH conditions are true
|
|
69
|
+
step.addText({
|
|
70
|
+
name: 'hotel-preferences',
|
|
71
|
+
label: 'Hotel Preferences',
|
|
72
|
+
isVisible: new JsonataBuilder('$attendance = "in-person" and $needsAccommodation = true', {
|
|
73
|
+
attendance: attendance.state.value,
|
|
74
|
+
needsAccommodation: needsAccommodation.state.value,
|
|
75
|
+
}),
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Either/Or Conditions
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const email = step.addEmail({
|
|
83
|
+
name: 'email',
|
|
84
|
+
label: 'Email',
|
|
85
|
+
})
|
|
86
|
+
const phone = step.addPhone({
|
|
87
|
+
name: 'phone',
|
|
88
|
+
label: 'Phone',
|
|
89
|
+
isRequired: new JsonataBuilder('$email = null or $email = ""', {
|
|
90
|
+
email: email.state.value,
|
|
91
|
+
}),
|
|
92
|
+
})
|
|
93
|
+
email.with({
|
|
94
|
+
isRequired: new JsonataBuilder('$phone = null or $phone = ""', {
|
|
95
|
+
phone: phone.state.value,
|
|
96
|
+
}),
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Cross-Step Conditions
|
|
101
|
+
|
|
102
|
+
For conditions based on other steps, use `step.data.getFieldData()`:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const step1 = form.addStep({ instanceId: 'type' }, (step) => {
|
|
106
|
+
step.addDropdown({
|
|
107
|
+
name: 'account-type',
|
|
108
|
+
label: 'Account Type',
|
|
109
|
+
data: [
|
|
110
|
+
{ label: 'Personal', value: 'personal' },
|
|
111
|
+
{ label: 'Business', value: 'business' },
|
|
112
|
+
],
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
step1.with({ icon: 'monitor-user', title: 'Account Type' })
|
|
116
|
+
form.addStep({ instanceId: 'business', icon: 'small-office-building' }, (step) => {
|
|
117
|
+
// Required only for business accounts
|
|
118
|
+
step.addText({
|
|
119
|
+
name: 'tax-id',
|
|
120
|
+
label: 'Tax ID',
|
|
121
|
+
isRequired: new JsonataBuilder('$type = "business"', {
|
|
122
|
+
type: step1.data.getFieldData('account-type'),
|
|
123
|
+
}),
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Ref:** `./cross-step-data.md` for more on `getFieldData()`
|
|
129
|
+
|
|
130
|
+
## Conditional Disabled
|
|
131
|
+
|
|
132
|
+
Make fields read-only based on conditions:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const status = step.addDropdown({
|
|
136
|
+
name: 'status',
|
|
137
|
+
label: 'Status',
|
|
138
|
+
data: [
|
|
139
|
+
{ label: 'Draft', value: 'draft' },
|
|
140
|
+
{ label: 'Submitted', value: 'submitted' },
|
|
141
|
+
],
|
|
142
|
+
})
|
|
143
|
+
// Can't edit title after submission
|
|
144
|
+
step.addText({
|
|
145
|
+
name: 'title',
|
|
146
|
+
label: 'Title',
|
|
147
|
+
isDisabled: new JsonataBuilder('$status = "submitted"', {
|
|
148
|
+
status: status.state.value,
|
|
149
|
+
}),
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Updating Conditions with .with()
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const toggleField = step.addCheckbox({
|
|
157
|
+
name: 'show-notes',
|
|
158
|
+
label: 'Add Notes',
|
|
159
|
+
})
|
|
160
|
+
const field = step.addText({ name: 'notes', label: 'Notes' })
|
|
161
|
+
// Add conditions after construction
|
|
162
|
+
field.with({
|
|
163
|
+
isVisible: new JsonataBuilder('$show = true', { show: toggleField.state.value }),
|
|
164
|
+
isRequired: new JsonataBuilder('$show = true', { show: toggleField.state.value }),
|
|
165
|
+
})
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## JSONata Operators
|
|
169
|
+
|
|
170
|
+
| Operator | Meaning | Example |
|
|
171
|
+
| --- | --- | --- |
|
|
172
|
+
| `=` | Equals | `$status = "active"` |
|
|
173
|
+
| `!=` | Not equals | `$status != "inactive"` |
|
|
174
|
+
| `and` | Both true | `$a = 1 and $b = 2` |
|
|
175
|
+
| `or` | Either true | `$a = 1 or $b = 2` |
|
|
176
|
+
| `>`, `<`, `>=`, `<=` | Comparison | `$age >= 18` |
|
|
177
|
+
|
|
178
|
+
**Note:** Use single `=` for equality (not `==`).
|