Skip to Content

Value Sources

A value source gives an entry one value: static, or computed by a JavaScript expression. Three forms:

ValueSource = { expr } | { literal } // props ValueSourceAssignment = ValueSource & { set } // seed steps CallbackValueSourceAssignment = ValueSource & { set?, confirm?, debounce? } // callback steps

A callback step may drop set and run only for its effect; a seed step cannot. Entry fields covers which field takes which form.

ValueSource

expr takes any JavaScript expression.

{ "literal": "Hello" } // any JSON value, used as-is { "expr": "scopes.root.title" } // a JavaScript expression, evaluated against the scopes

ValueSourceAssignment

A ValueSource plus set: evaluate, then write the field set names, scopes.<scope>.<field>. Never deeper, never a number: scopes.root.rows.0.qty is rejected. Edit a row through its scope, or replace the array (why).

{ "set": "scopes.root.count", "literal": 0 } { "set": "scopes.root.itemCount", "expr": "scopes.cart.items.length" } { "set": "scopes.root.count", "expr": "currentValue + 1" } // read-modify-write

currentValue is the value now at the set path, the one about to be replaced, so a read-modify-write never names the path twice. Only set defines it, so a bare props, hidden, loading or each expression cannot use it:

// ❌ no `set` here — throws: "currentValue" is not available in expressions { "hidden": "!currentValue" }

A callbacks step overwrites. A seed step is first-writer-wins: it assigns only if the path is still undefined:

{ "key": "filters", "component": "Card", "children": ["status-input"], "seed": [{ "set": "scopes.root.status", "literal": "open" }] } { "key": "status-input", "component": "Input", "seed": [{ "set": "scopes.root.status", "literal": "all" }], "props": { "expr": "({ value: scopes.root.status })" } }

The input renders open: the parent seeds first, so the child’s seed is a silent no-op. See lifecycle.

CallbackValueSourceAssignment

confirm gates the step behind a confirmation message:

{ // … "callbacks": { "onClick": [ { "expr": "deleteUser({ id: scopes.row.id })", "confirm": "Delete this user? This cannot be undone." }, { "set": "scopes.root.users", "expr": "listUsers()" } ] } }

The first step has no set, so the delete’s return value is discarded. Declining runs neither step. A confirmed step is also an ordering barrier: everything before it settles first, and nothing after it starts until it passes.

debounce delays the step, and every step after it, until the callback has been quiet for 300 ms. A newer call replaces the pending run, so only the last one fires, with its own evt. Steps before it run at once.

{ // … "callbacks": { "onChange": [ { "set": "scopes.root.q", "expr": "evt.value" }, { "set": "scopes.root.rows", "expr": "searchRows({ q: scopes.root.q })", "debounce": true } ] } }

q is written on every keystroke; the search runs once typing pauses. Unmounting the element cancels a pending run.

Last updated on