Triggers and Conditions
The trigger starts an effect; when decides whether its actions run.
| Trigger | Use it when |
|---|---|
| Value change | A watched form or context value changes |
| Widget event | A widget emits a semantic event such as success |
| Invoked effect | Another effect explicitly calls reusable logic |
Conditions
when is optional. It must evaluate to the boolean true for the actions to run:
{"when": "${$value.customerType == 'company'}"}
A boolean field can be used directly, for example
{"when": "${$value.sameAsShipping}"}. The string "true", number 1, and
non-empty objects are not boolean true. A missing when permits the actions
after the trigger fires.
Trigger Types
Value Change
A value-change effect has listen and no trigger. It runs when at least one
watched path changes according to deep equality.
{
"id": "recalculate-total",
"listen": ["$value.quantity", "$value.price"],
"do": [
{
"type": "set",
"field": "total",
"value": "${($value.quantity || 0) * ($value.price || 0)}"
}
]
}
An empty or missing listen array never runs as a value-change effect.
Supported listen roots are:
| Root | Watched data |
|---|---|
$value | Current root form value |
$context | Form context |
$outputContext | Output context |
$config | Application/API configuration |
$runtimeInfo | Current user and runtime information |
$version | Application version |
$build | Build metadata |
listen is not inferred from expressions. If an effect reads
$value.customer.id and its change should rerun the effect, include that path
explicitly.
Datasource results are resolved as part of the expressions that call them;
they are not a top-level value-change trigger. Put the datasource call in
when, set.value, or runEffect.inputs and listen to the form or context
values passed as its parameters. Datasource calls inside scriptData are not
resolved by the current Form Effects runtime.
Initialization
The first settled form snapshot normally establishes only the comparison
baseline. runOnInitialization: true opts a value-change effect into exactly
one run after initial values, expression defaults, and their data sources have
settled.
Intermediate default or datasource emissions do not each cause a separate initialization run. The option does not apply to widget-event or invoked effects, and the designer removes it when the trigger type changes.
Fluent-array row scope
Use $row in a watched path to run once for each changed row of one
fluent-array. All $row listeners in an effect must identify the same
array; one $row represents one array level. For path syntax, lifecycle,
and complete examples, see Arrays and nested forms.
Widget Event
A widget-event effect listens to one semantic event from one concrete widget or layout node:
{
"id": "remember-opened-panel",
"trigger": {
"type": "widgetEvent",
"objectPointer": "/layout/0",
"event": "panelOpened"
},
"do": [
{
"type": "set",
"field": "lastOpenedPanel",
"value": "${$payload.index}"
}
]
}
| Trigger property | Description |
|---|---|
type | Always widgetEvent. |
objectPointer | Canonical JSON Pointer of the emitting schema node. |
event | Stable event name declared by that widget's plugin definition. |
Only events declared by the widget are accepted at runtime. The Form Designer filters the event selector accordingly and provides known payload members to JEXL autocomplete.
The source selector displays the widget breadcrumb, selector, and pointer. Use Locate widget to select that source in the designer and scroll it into view.
The event context contains:
{
name: string;
payload?: unknown;
objectPointer: string;
dataPointer?: string;
row?: number;
indexes?: number[];
selector?: string;
}
Use $payload as the preferred shorthand for $event.payload. Use $event
when event metadata such as name, objectPointer, or selector is required.
For an event emitted inside a fluent-array, objectPointer identifies the
authored schema node while dataPointer identifies its concrete rendered
occurrence. indexes lists its enclosing array indexes from outermost to
innermost, and row is the index owned by the event source. The expression
context exposes that same value as $row.
For example, an event from orders[2].lines[1] has indexes: [2, 1] and
$row === 1. Expressions can read the outer row as $event.indexes[0] and
the inner row as $row:
$value.orders[$event.indexes[0]].lines[$row].description
This is how a root-level widget-event effect identifies its concrete nested
source. The root location of effects[] does not imply row 0 and the runtime
does not infer the deepest row from schema nesting alone.
For tab and step transitions, prefer stable identifiers such as
$payload.previousId and $payload.activeId when available. Position-based
previousIndex and activeIndex remain available.
objectPointer identifies a schema position rather than a persistent widget
ID. Do not maintain it by hand after structural edits. The Form Designer remaps
widget-event pointers when nodes are moved, inserted, renamed, duplicated, or
deleted.
Explicit Invocation
An invoked effect is a reusable named action block. It runs only through a
runEffect action:
{
"id": "process-customer",
"trigger": {"type": "invoked"},
"when": "${$input.customer != null}",
"do": [
{
"type": "set",
"field": "customerName",
"value": "${$input.customer.name}"
}
]
}
Its non-empty id must identify exactly one effect in the root schema. Values
passed by runEffect.inputs are available through $input.
For row-scoped worked examples, see Arrays and nested forms.