Skip to main content
Version: 1.0.4

Event Actions

An event action is what happens when an operator interacts with a component. Configure them in the Event Manager: pick an event, add one or more actions, and they run in order.

Open it from a component's right-click menu on the canvas — Edit Events….

Events​

Available on every component​

EventFires on
clickSingle click
dblclickDouble click
contextmenuRight click — see the note below
mouseenterPointer enters
mouseleavePointer leaves
contextmenu does not fire on the Designer canvas

Right-click is the Designer's own gesture: on the canvas it opens the component's context menu (Edit Events…, Copy, Delete …), and the event is consumed there. Your configured contextmenu actions run at runtime only — in preview and in LiveView.

Test right-click in preview. Configuring it, right-clicking the canvas, seeing the context menu and concluding it is broken is the usual first encounter with this.

Two further things worth knowing:

  • The browser's own menu still appears. The runtime fires your actions but does not suppress the native context menu. If you are building a custom right-click menu, suppress it yourself in a script action.
  • It applies to every component, in every layout mode — the behaviour lives in the shared widget wrapper, not in individual widgets.

Input components​

EventFires on
changeThe value changed
submitSubmitted
focusReceived focus
blurLost focus

Table​

EventPayload
rowClickThe clicked row
rowDoubleClickThe row
selectionChangeThe current selection
rowEditThe edited row
rowDeleteThe row to delete
cellClick · cellDoubleClickPer column — see events on a part

Form​

EventPayload
submitSuccesscontext.submit — the write result
submitErrorerror (bare), plus context.submit

Upload​

EventPayload
fileReceivedcontext.file and context.files

Events on a part of a widget New in 1.0.0​

Some widgets have addressable parts, and a part can carry its own events. Wiring "click this 3D viewer" is rarely what you meant; "click the robot's eye" is.

WidgetIts partsEvents they carry
3D ViewerObjects in the modelobjectClick · objectDoubleClick
TableColumnscellClick · cellDoubleClick

A wired part appears as an extra section in the Event Manager's list, below the widget's own events — not as a mode the dialog switches into, so opening it shows at a glance what the widget does and what each of its parts does. Everything else works unchanged: the action cards, chaining, and Only when… all behave on a part exactly as they do on the widget.

Adding one​

Open the Event Manager and use the Add object… / Add column… field at the foot of the list.

A part is addressed by the same string a binding row targets — a 3D node name or index path, a Table column id — so a part never acquires a second name that could drift from the first.

The 3D Viewer publishes its node list, so the field offers them as suggestions. The Table does not publish a column list at runtime, so you type the column id — the same id used everywhere else in the Table's configuration. Typing is allowed either way: in the editor the model may not be loaded yet, and you may already know the name.

Removing a part removes every event on it.

Rows are deliberately not addressable

Only address spaces you can enumerate at design time get sections. A query can produce thousands of rows, so branching on a row stays a guard — rowClick with an Only when… on the row payload.

What a click resolves to​

Both resolutions are worth knowing, because both fail silently when the address is wrong — the event simply never fires.

  • 3D Viewer. A click reports the mesh the ray actually hit, which for a multi-primitive node is a leaf like Eye_L_2 rather than the Eye_L you wired. The hit is walked up to the deepest ancestor that has events on it, so wiring the name you see in the model tree works.
  • Table. The column comes from the cell's own column marker, not from a handler threaded through every cell — which is what keeps rows from re-rendering on every sort.

Actions​

Switch to view — another view, optionally with parameters — or Open URL, optionally in a new tab.

navigate → view: "equipment-detail"
params: { tagPath: row.path }

The foundation of drill-down: an overview passes the selected equipment's path to a single parameterised detail view.

setTag​

Write a value to a tag.

setTag → tag: "Line1/Filler/Motor1/SpeedSetpoint"
value: Slider.value

Subject to the tag's writable flag and write level, enforced server-side. A modified client cannot bypass it.

Confirm consequential writes

A button that starts equipment should ask first. Tick Require confirmation before writing on the setTag action, and set a write level so only sufficiently privileged operators can reach it.

setClientVar (Set variable)​

Set a variable by key. The Scope decides who sees it:

ScopeBehaviour
Client — tab-local, instantScreen-local state no other session sees. Read it as client.<key>.
Gateway — shared (system.vars)A gateway variable, shared by every session and pipeline.
Set variable → key: "selectedLine"
value: Dropdown.value

Use client variables for selections, filters and UI state; for anything shared, use a gateway variable or an internal tag.

script​

Run a client script — JavaScript in the browser, with the full system API.

const r = await system.tag.read("Line1/Filler/Motor1/Speed");
if (r.quality !== "Good") {
context.ui.notify({ message: "Speed unavailable", severity: "warning" });
return;
}
await system.tag.write("Line1/Filler/Motor1/SpeedSetpoint", r.value * 0.9);

For anything transactional or long-running, call a Gateway script instead — it runs server-side, so a browser disconnect cannot strand an open transaction. → Gateway scripts

notify​

Show the operator a message.

notify → message: "Setpoint applied"
severity: info | success | warning | error

sendMessage​

Send a message to other components. Any component with a matching "on message" handler receives it.

sendMessage → type: "refreshDashboard"
payload: { line: "L1" }

This is local, in-browser messaging. To push from the gateway down to browsers — from a pipeline or a Gateway script — use system.message.send, which reaches all sessions, one project, or one user.

refreshBinding​

Re-evaluate a query binding on demand — a refresh button, or after a write that changes what a table should show.

popover​

Open, Toggle or Close a view as a popover anchored to This widget or the Cursor, on a chosen side (Auto (most room) by default). Close and Toggle act on The most recent popover or This popover (self).

panel / dock​

Open, close or toggle a panel or dock region — detail drawers, side panels.

Sign in / Sign out​

Trigger the LiveView runtime login or logout flow, for projects bound to a User Database.

Chaining actions​

Actions on one event run in order:

Button "Apply recipe" → click
1. script validate the entered values
2. setTag write the setpoint
3. notify "Recipe applied"
4. refreshBinding refresh the recipe table

Keep chains short. Once the logic outgrows a few steps, move it into a single script action — or a Gateway script — where it can be read as one piece.

Running an action conditionally​

Every action carries an optional guard, edited from the Only when… chip in its header. It is a comparison, not an expression: a source on the left, one of is / is not / is set / is empty, and a value on the right.

Vertical Menu → Menu item click
1. Popover only when {{item.key}} is help
2. Sign out only when {{item.key}} is logout

This is what lets ONE handler serve several cases. A menu's item click is the clearest example — the event belongs to the whole widget, so without a guard every action would fire for every item — but the same applies to a table row, a chart point, or any chain that has to behave differently depending on a tag or a prior result.

The left side accepts anything a binding does: the event's own payload ({{item.key}}, {{row.status}}, {{submit.rows}}), a widget property (self.props.mode), a tag, a view param, a variable, or a chain result (ok, error.message, results.0.id). The picker offers exactly what applies to the event you are editing.

The right side is compared as typed unless you wrap it in {{ }}. That matters: error, ok, last, results and outcomes are also the names of chain results, and a plain error here means the word, not the previous action's error object.

Values are compared as text, so a tag holding 42 matches a typed 42. is set / is empty treat an empty string, false, 0, null and undefined as empty — so a bound boolean tag behaves the way you would expect rather than the way JavaScript would.

What a skipped action does to the chain

A guarded-out action is skipped completely: it records no result and raises no error, exactly as if it were not in the list for that particular firing.

Two consequences worth knowing:

  • A later action's On success / On error condition refers to the last action that actually ran, not necessarily the one directly above it.
  • A guarded-out notify does not count as "the author reported this", so the built-in failure message can still appear if an earlier setTag or script failed.

Patterns​

Drill-down from a table

Table.rowClick → navigate("equipment-detail", { tagPath: row.path })

Guarded start

Button.click → setTag("Line1/Conv1/StartCmd", true)
with Require confirmation ticked: "Start conveyor 1?"

Confirmation is an option on the action, not something a script asks for. A confirmation a script raises could be skipped by a script, and the point of one is that it cannot be.

Filter that drives everything on the screen

Dropdown.change → Set variable key: "line" = {{value}}
→ sendMessage("filterChanged", { line: value })

Server-side work from a button

Button.click → script:
const res = await system.script.runOnGateway("reports.generateShiftReport",
{ shift: "A" },
{ idempotencyKey: "shift-A-" + today });
context.ui.notify({ message: `Report ${res.id} generated`, severity: "success" });

The idempotency key means a double-click produces one report, not two.

Security​

  • Every action a screen can perform is still subject to server-side authorization. Client-side logic decides what to offer; the server decides what is allowed.
  • Tag writes are gated by writable and the write level.
  • Runtime operators are authorised by their runtime roles, independently of platform permissions.
  • Consequential actions are recorded in the audit journal.

Design screens so operators do not see actions they cannot perform — but never rely on hiding a button as the control.

Next​

→ Drawing & symbols