Skip to main content
Version: 1.0.8

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. Its left rail has two tabs: Events, the component's own events, and Message, the handlers that run when a message arrives.

Events​

Each event in the list carries a muted line saying what it hands a script 1.0.8 — row, event, context.point — with the full text on hover and in the event's parameters card as In a script: …. It is written as the identifier you actually type, which is not uniform: a few payload keys are bound as bare names ahead of an event script, and the rest stay on context.

Bare nameComes from
valueInput changes
errorA failed action, Form submitError
rowTable rowClick
selectedRows · selectedRowIdsTable selectionChange
event 1.0.8Scheduler eventClick, eventChange, eventDoubleClick
slot 1.0.8Scheduler slotSelect

Everything else is reached through context — context.point on a chart click, context.file on an upload, context.submit on a form. An event with no payload line simply has not had its payload written down yet.

Scheduler scripts: event and slot are now bare

Changed in 1.0.8 Before 1.0.8 a Scheduler script had to write context.event.id, while a Table script wrote row.id, and a bare event.id threw a ReferenceError. Both spellings work now. slotSelect also carries resourceId and time; those are deliberately not bare (a script declaring its own time would break), so reach them as slot.time or context.time.

Available on every component​

EventLabelFires on
clickClickSingle click
dblclickDouble clickDouble click
contextmenuRight clickRight click — see the note below
mouseenterMouse enterPointer enters
mouseleaveMouse leavePointer 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, Arrange, Copy, Delete …), and the event is consumed there. Your configured contextmenu actions run at runtime only — in preview and in LiveView.

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 Run 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
rowClickrow — the clicked row
rowDoubleClickThe row
selectionChangeselectedRows and selectedRowIds
rowEditThe edited row
rowDeleteThe row to delete
rowReorderrowIds, movedRowId, fromIndex, toIndex
cellClick · cellDoubleClickPer column — see events on a part

Form​

EventPayload
submitSuccesscontext.submit — the submitted values
submitErrorerror (bare), plus context.submit

Upload​

EventPayload
fileReceivedcontext.file and context.files

Other widgets​

Charts (dataClick, dataDblClick with context.point, legendSelect), the Scheduler, Tree, Tag Browser, File Manager, Map, Video Player, the menus, and the QR/Barcode widgets and scanner have events of their own. Each widget's page in the component reference lists them.

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. On a 3D Viewer you can also right-click the object on the canvas and pick Edit Events for "<part>".

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​

The Add action menu is split into four groups 1.0.8:

GroupActions
Logic & dataRun script · Set tag · Set variable · Refresh binding
Screens & layoutNavigate · Popover · Panel · Dock
Feedback & messagingNotification · Send message
User sessionProfile · Sign in · Sign out

Picking Notification adds two cards at once: one that runs On success and one that runs On error, whose message defaults to {{error.message}}.

Most actions have a Require confirmation before … checkbox. Ticked, the operator is asked first; leave the message blank for a sensible default (Write these tags?, Sign out?). Cancelling stops the whole chain, not just that action.

Run 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

Set tag​

Write one or more tags. The writes sit in a Tag writes table — + Add tag on its title row adds one — with a Mode per row: Set, Toggle or Pulse (write, then restore after a duration, 200 ms by default).

Set tag → Tag writes:
Line1/Filler/Motor1/SpeedSetpoint Set {{self.props.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, and set a write level so only sufficiently privileged operators can reach it.

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 of this project, shared by every session, script and pipeline in it — or by every project, if the name is global.

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

Refresh binding​

Re-evaluate bindings on demand — a refresh button, or after a write that changes what a table should show. The target is This widget, Every binding in this view, or Specific components. A query-bound Table re-fetches its pages too 1.0.8.

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.

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​

Panel opens, closes or toggles a panel. Dock toggles, collapses or expands a docked region — Header, Footer, Left sidebar or Right sidebar.

Notification​

Show the operator a message. When: Always, On success (previous action) or On error (previous action). Kind: Simple message (toast) with a severity of Info, Success, Warning or Error, or Custom view to show a view as the notification.

Send message Changed in 1.0.8​

Send a message on the message bus. Widgets with a matching handler receive it — and, depending on the scope, so do other views, other browsers and pipelines.

FieldWhat it does
SubjectA dotted name, e.g. line1.motor.start. Letters, digits, _ and - per segment; wildcards are for receiving only.
ScopeWho can hear it — see below.
ModeNetwork scopes only: Send — don't wait, or Request — wait for a reply.
TimeoutFor a request. 5000 ms by default, 100–60000.
Reply toFor a request: a client variable that receives the reply, so a widget bound to client.<name> shows it. The reply is also the action's result (last).
PayloadKey/value fields, + Add field on the table's title row. A value can be literal, a {{token}}, or a property.
ScopeReaches
All projects (network) — the defaultEvery project on the server: other browsers, and pipelines listening on the subject
This project (network)This project's browsers and pipelines only
This browser tab onlyThis tab
This view onlyThis view
A specific componentOne component, picked as Target

A network message is limited to 256 KB of payload, and a chain of messages that triggers further messages is cut off after 8 hops, so two handlers answering each other cannot loop forever.

→ Message bus · Messages from scripts

Messaging changed in 1.0.8

In 1.0.7 a Send message action had a free-text Type and stayed inside the browser. On upgrade, views are migrated when they are first opened:

  • The type becomes the subject, with every character a subject cannot hold turned into _: Motor Start → Motor_Start. Handlers are renamed the same way, so a pair that matched still matches.
  • The scopes Whole session and This page (tab) — and no scope at all — become This browser tab only, which is what they always did.
  • {{messageType}} in a handler's actions still works; it is the same value as {{subject}}.

Script code is not rewritten. A script that sends with a type containing a space, : or /, or with scope: "session" / "page", needs editing — and context.message.send(...) with no scope now broadcasts to every project instead of staying in the tab. See context.message and What changed from 1.0.7.

On message​

On the Event Manager's Message tab, type a subject pattern and Add message handler. The handler is listed as On message "<pattern>" and takes actions like any event.

PatternMatches
line1.motor.startExactly that subject
line1.*.start* is one segment
line1.>> is one or more trailing segments, and must come last

Its actions can use {{payload}}, {{subject}}, {{senderId}} and {{source}}.

Profile / Sign in / Sign out​

LiveView only, and inert in the Designer preview. Sign in and Sign out trigger the LiveView runtime login and logout, for projects bound to a User Database. Profile opens the signed-in operator's own account — photo, display name and password — and asks the viewer to sign in when nobody is.

Chaining actions​

Actions on one event run in order:

Button "Apply recipe" → click
1. Run script validate the entered values
2. Set tag write the setpoint
3. Notification "Recipe applied"
4. Refresh binding refresh the recipe table

Keep chains short. Once the logic outgrows a few steps, move it into a single Run 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 Notification does not count as "the author reported this", so the built-in failure message can still appear if an earlier Set tag or Run script failed.

Patterns​

Drill-down from a table

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

Guarded start

Button.click → Set tag 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 client "line" = {{value}}
→ Send message "dashboard.filter" scope: This view only { line: {{value}} }

Server-side work from a button

Button.click → Run 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