Property Bindings
A binding connects a component property to a live source. Any property can be bound — not just
value: colours, visibility, text, ranges, enabled state, even another component's input.
Gauge.value → tag Line1/Filler/Motor1/Speed
Gauge.fillColor → tag …/Speed → threshold transform → green / amber / red
Panel.visible → tag …/Running
Table.data → query production_by_shift
Binding types
| Type | Source | Use for |
|---|---|---|
| Tag | A namespace tag | Live process values — the common case |
| Expression | A formula over one or more tags | Arithmetic, comparisons, combining several tags into one value |
| Property | Another component's property | Coupling widgets: a dropdown that drives a chart |
| Query | A database query or named query | Tables, reports, lists, lookups |
Tag bindings
Pick the tag from the namespace tree, or drag it onto the property. The binding delivers the value
and its quality — a widget bound to a Bad-quality tag shows that, rather than rendering a
stale number as current.
Bidirectional by default for input widgets: a Slider bound to a writable tag reads it and writes
it, subject to the tag's writable flag and write level.
Property bindings
Bind one component's property to another's:
Dropdown.value (operator picks a line)
↓
Chart.tagPath → property binding → "Line1/" + Dropdown.value + "/Speed"
This is how a screen becomes interactive without a single line of script.
Expression bindings
Where a tag binding delivers one tag's value, an expression binding computes one from several:
Label.text → expression tag["Line1/Filler/Motor1/Speed"] * 60
Panel.visible → expression tag["Line1/Running"] && !tag["Line1/Fault"]
Gauge.value → expression tag["Line1/Good"] / (tag["Line1/Good"] + tag["Line1/Scrap"]) * 100
Reference a tag with tag["<path>"], using the full namespace path in quotes.
Subscriptions are worked out from the expression. Every literal tag["…"] in the source is
found when the screen loads and subscribed to, so the property re-evaluates whenever any of those
tags ticks. You do not list the inputs anywhere.
The consequence is worth knowing: a path assembled at runtime — concatenated from a variable, or looked up indirectly — cannot be found by that scan, so it never gets a subscription and the value will not update. Keep the path a literal, and use an indirect tag binding when the tag itself has to change.
Reading the clock
An expression that mentions now is recognised and driven by a clock tick, because nothing else
would make it advance:
Label.text → expression secondsBetween(tag["Batch/StartedAt"], now)
Without that, an elapsed-time display would sit frozen on the canvas — there is no tag change to wake it — while appearing to work in the Binding Manager preview, which redraws on its own cadence.
Expression binding or expression tag?
Both compute a value from tags, and the difference is where it lives:
| Use | |
|---|---|
| Expression binding | The calculation matters only to this one property on this one screen |
| Expression tag | The value is worth naming — trend it, alarm it, read it from a script, reuse it on other screens |
If you find yourself pasting the same expression onto a second screen, it wanted to be a tag.
Query bindings
Bind to a database result. Prefer a named query over inline SQL:
- The statement lives in one place.
- Parameters are bound, not concatenated.
- The screen needs no database credentials of its own.
Named queries
A named query is a saved, parameterised statement with its connection baked in:
-- production_by_shift
SELECT shift, SUM(units) AS units
FROM production
WHERE ts >= :start AND ts < :end
GROUP BY shift
ORDER BY shift
Bound to a table, with :start and :end supplied from date pickers or expressions. Also callable
from scripts as system.db.runNamedQuery("production_by_shift", {...}).
Named queries are project resources, saved and versioned with the project.
Transforms
A transform chain shapes the raw value before the component sees it. Chain as many as you need — each receives the previous one's output.
| Transform | Purpose |
|---|---|
| map | Look up an output for an input — value, range or expression matching |
| threshold | Pick an output from numeric bands |
| scale | Linear rescale from one range to another |
| format | Number and date formatting, with masks |
| js | An inline JavaScript expression over value |
| script | A longer script for complex shaping |
map
The most-used transform. Turn a state into a label, a colour, an icon.
| Input type | Meaning | Example |
|---|---|---|
value | Literal equality | 1 → Running |
range | Numeric interval; [ ] inclusive, ( ) exclusive, either bound optional | [0,10) → Low |
expression | JavaScript over value; the row matches when truthy | value > 100 → High |
| Output type | Meaning |
|---|---|
value | Literal — numbers, booleans and null are coerced; anything else stays a string |
color | A CSS colour, kept verbatim |
expression | JavaScript over value, evaluated to produce the output |
A fallback covers inputs that match no row — always set one, or an unexpected value renders as nothing.
Motor state → colour
0 → color #6b7280 (stopped)
1 → color #16a34a (running)
2 → color #dc2626 (faulted)
fallback → color #6b7280
threshold
Numeric bands, ideal for alarm-style colouring:
value < 40 → #16a34a
40 – 80 → #f59e0b
value > 80 → #dc2626
scale
Linear rescale — a 0–3000 RPM tag driving a 0–100 progress bar. Prefer configuring engineering scaling on the tag where possible; use this transform for presentation-only rescaling.
format
Number and date formatting with a mask (#,##0.00, 0.0 %, yyyy-MM-dd HH:mm). If the tag has a
format string, prefer that — it applies everywhere the tag is shown.
js and script
// js — a single expression over `value`
value == null ? "—" : (value * 3.6).toFixed(1) + " km/h"
Reach for these last. A map or threshold is declarative, visible in the binding manager, and
survives the next engineer; an expression is only as clear as it was written.
Indirect bindings
An indirect binding builds the tag path from a template with substituted values, so one component follows a selection.
pathTemplate: "Line1/{equipment}/Speed"
mapper: equipment ← Dropdown.value
Combined with embedded views, this is what makes a single faceplate serve every instance of a class.
Quality propagation
Quality flows through the whole chain. Transforms propagate the worst quality of their inputs — a computed value can never appear healthier than the data behind it. Components render bad quality distinctly rather than showing a plausible wrong number.
Design for it: give a value display a bad-quality appearance, and do not colour a tank green when
the level tag is Bad.
Refreshing
Tag bindings update by subscription. Query bindings evaluate on load and on demand:
- A
refreshBindingevent action — from a button, a timer or another event. - Re-evaluation when a bound parameter changes.
Do not poll a query binding at high frequency. If a value needs to be live, it should be a tag.
Debugging a binding
- Open the binding manager and read the live preview — it shows the resolved value with transforms applied.
- Remove transforms one at a time to find which one changes the value unexpectedly.
- Check quality: a
Bad-quality tag makes every downstream transform suspect. - For indirect bindings, verify the resolved path — a typo in the template gives
NotFound. - For query bindings, run the statement directly against the connection.