Actions
Actions run in the order listed in do[]. Use a literal control path for
field, from, and to; use ${...} only for values that are expressions.
| Action | Result | Detail |
|---|---|---|
set | Write a static or calculated value | Below |
copy | Deep-copy a form value | Below |
dispatch | Dispatch an application action | Below |
clear | Empty, unset, or reset a control | Clearing and resetting |
script | Call a server-side script | Scripts and execution order |
runEffect | Invoke a reusable effect | Scripts and execution order |
set
set writes one static or evaluated value to a control:
{
"type": "set",
"field": "fullName",
"value": "${[$value.firstName, $value.lastName].filter(x => x).join(' ')}"
}
field is a literal control path. It is not an expression. A row-scoped action
may use one $row segment, for example items.$row.total:
| Incorrect | Correct |
|---|---|
"field": "${$value.customer.email}" | "field": "customer.email" |
"field": "$value.customer.email" | "field": "customer.email" |
"field": "customer/email" | "field": "customer.email" |
value can be a static string or a ${...} expression. Expression results may
have any value type accepted by the target control. The runtime applies the
resolved value to the control even when it equals the current value. It reports
a field change for cascading only when the resulting value differs.
The value is resolved and any required write completes before the next action
in the same do[] starts. If the resolved value is already present, execution
still continues to later actions. For example, a following runEffect is
invoked even when set did not change the value; a value-change effect
listening to the field is not triggered in that case.
{"type": "set", "field": "status", "value": "active"}
{"type": "set", "field": "price", "value": "${$value.quantity * $value.unitPrice}"}
copy
copy reads one form path and deep-copies its value to another:
{"type": "copy", "from": "shippingAddress", "to": "billingAddress"}
Both paths use the same literal dot notation as set.field. Objects and arrays
are cloned so later changes to the target do not mutate the source. Form values
used with copy must remain JSON-compatible.
When an exact target control is not present, the runtime can patch the deepest existing parent control at the remaining nested path.
dispatch
dispatch sends an NgRx action at its exact position in do[]:
{
"type": "dispatch",
"action": "[Attachments] Refresh",
"actionData": {"ownerId": "${$value.ownerId}"}
}
action is a literal action type. actionData may contain nested expressions;
their values, including supported datasource results, are resolved before the
action is dispatched. The next action in do[] starts after the dispatch call
returns. The form workflow does not await asynchronous NgRx effects, navigation,
or other follow-up application work. Dispatch does not itself change a form
control or start a value-change cascade.
Worked examples
Every value that should trigger recalculation is listed in listen.
{
"id": "prepare-order-label",
"listen": ["$value.orderNumber", "$value.customer.name"],
"do": [
{
"type": "set",
"field": "orderLabel",
"value": "${[$value.orderNumber, $value.customer.name].filter(x => x).join(' - ')}"
}
]
}
If orderLabel is always read-only and has no workflow meaning, prefer a
computed default instead.
React to a widget result
This effect runs only when the button at /layout/2 emits success:
{
"id": "customer-loaded",
"trigger": {
"type": "widgetEvent",
"objectPointer": "/layout/2",
"event": "success"
},
"do": [
{"type": "set", "field": "status", "value": "loaded"},
{"type": "clear", "field": "errorMessage", "mode": "empty"}
]
}
Use $payload in when or action expressions when the widget event contains a
result payload.