Skip to main content
Version: 2.5

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.

ActionResultDetail
setWrite a static or calculated valueBelow
copyDeep-copy a form valueBelow
dispatchDispatch an application actionBelow
clearEmpty, unset, or reset a controlClearing and resetting
scriptCall a server-side scriptScripts and execution order
runEffectInvoke a reusable effectScripts 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:

IncorrectCorrect
"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.