Handlers
A handler is an object with at least one of:
-
patch— a merge patch applied to the signals locally. Leaf values may be expressions, evaluated before the patch is applied. -
action— the name of an action to send to the server. May be accompanied bypayload(any JSON, literal) andindicator(a local signal path set totruewhen the action is sent).
When the handler runs, the renderer:
- stops if the element, or one of its ancestors, is disabled or hidden;
- applies the
patch, if any; - stops if there is no
action, or the connection is closed; - sets the
indicatortotrue, if any; -
sends an
actionmessage with the name, the payload if there is one, and the signals, without local ones.
So when both are present, the patch is applied first and the action carries the patched signals. Handlers are structural: they cannot be expressions. The exact rules are in the specification.
Events
| Attribute | Components | Runs when |
|---|---|---|
onPress |
button | The button is pressed. |
onChange |
input | An edit is finished: on losing focus, or after a short idle period. Not on every keystroke. |
| checkbox, select, tabs | After each change. | |
| slider | The slider is released. | |
onSubmit |
input | The user confirms, with Enter in a single-line field. |
onDismiss |
dialog | The user asks to close the dialog. |
For controls with bind, the new value is already in the signals when the
handler runs.
Examples
In these examples, lines from the renderer are marked in comments; everything else is from the server.
An action
A button that sends an action with a payload. The signals hold a filter and a local tab selection.
signal {"filter":"all","_tab":"inbox"}
element {"id":"archive-7","type":"button","label":"Archive",
"onPress":{"action":"archive","payload":{"contact":7}}}
When the user presses it, the renderer sends the action with the signals.
_tab is local, so it is left out:
# renderer → server
action {"name":"archive","payload":{"contact":7},"signals":{"filter":"all"}}
The server answers however it likes, here by removing the row and updating a count:
remove {"id":"row-7"}
signal {"inbox":12}
An action with bound input
An input writes to the signals through bind, so the server reads what was
typed from the action's signals, with no payload needed.
element {"id":"q","type":"input","bind":"search.q","placeholder":"Search",
"onSubmit":{"action":"search"}}
The user types lamp and presses Enter:
# renderer → server
action {"name":"search","signals":{"search":{"q":"lamp"}}}
A local patch
A handler with only a patch changes the signals on the renderer and sends
nothing. Here a button toggles a local signal that shows a section:
signal {"_more":false}
element {"id":"toggle","type":"button","variant":"plain",
"label":{"cel":"s._more ? 'Less' : 'More'"},
"onPress":{"patch":{"_more":{"cel":"!s._more"}}}}
element {"id":"details","type":"text","text":"Lorem ipsum","visible":{"cel":"s._more"}}
Pressing the button sets _more to true: the label becomes
Less and the text appears. No message is sent.
Patch and action
The patch is applied before the action is sent, so the action carries the patched signals. Here the buttons of a filter set the filter and ask the server for the matching items:
signal {"filter":"all"}
element {"id":"done","type":"button","label":"Done",
"variant":{"cel":"s.filter == 'done' ? 'primary' : 'secondary'"},
"onPress":{"patch":{"filter":"done"},"action":"filter"}}
The button is highlighted at once, from the patch, and the server receives the new filter:
# renderer → server
action {"name":"filter","signals":{"filter":"done"}}
An indicator
A confirmation dialog whose Delete button closes the dialog locally, marks the deletion as in progress, and sends the action.
signal {"_confirm":true,"_deleting":false}
element {"id":"confirm","type":"dialog","title":"Delete project?",
"open":{"cel":"s._confirm"},"onDismiss":{"patch":{"_confirm":false}}}
element {"id":"delete","type":"button","parent":"confirm","variant":"failure","label":"Delete",
"onPress":{"patch":{"_confirm":false},"action":"delete","payload":{"project":3},
"indicator":"_deleting"}}
element {"id":"status","type":"progress","label":"Deleting…","visible":{"cel":"s._deleting"}}
When the user presses Delete, the renderer applies the patch (the dialog closes),
sets _deleting to true (the progress appears), and sends:
# renderer → server
action {"name":"delete","payload":{"project":3},"signals":{}}
The renderer never clears an indicator. The server clears it when it is done, usually together with the result, and should do so on every path, including errors:
signal {"_deleting":false}
remove {"id":"project-3"}