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.gaugeColor → tag …/Speed → Map transform (numeric ranges) → green / amber / red
Panel.visible → tag …/Running
Table.data → query production_by_shift
Binding types
The Binding Manager's sidebar has four sources:
| Sidebar | Source | Use for |
|---|---|---|
| Tags | A namespace tag, picked directly or built from a template | Live process values — the common case |
| Properties | Another component's property | Coupling widgets: a dropdown that drives a chart |
| Queries | A named query | Tables, lists, lookups |
| Expression | A formula over one or more tags | Arithmetic, comparisons, combining several tags into one value |
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 through a named query. A named query keeps the statement in one place, binds its parameters rather than concatenating them, and needs no database credentials on the screen.
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 |
|---|---|
| Number Format | Format a number with a mask |
| Map | Look up an output for an input — exact value, numeric range or expression |
| Script | JavaScript over value, for anything the other two cannot do |
Those three are the whole list. Banding a value into colours is a Map with numeric ranges; rescaling is a line of Script — or, better, scaling on the tag, where every screen inherits it.
Map
The most-used transform. Turn a state into a label, a colour, an icon.
Match input by
| Option | Meaning | Example |
|---|---|---|
| Exact value | Literal equality | 1 → Running |
| Numeric range | Interval; [ ] include the end, ( ) leave it out, either bound optional | [0,10) → Low |
| Expression | JavaScript over value; the row matches when truthy | value > 100 → High |
Produce output as
| Option | 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 (Exact value → Color)
0 → #6b7280 (stopped)
1 → #16a34a (running)
2 → #dc2626 (faulted)
fallback → #6b7280
Numeric ranges give alarm-style banding:
Speed → colour (Numeric range → Color)
[,2400) → #16a34a
[2400,2800] → #f59e0b
(2800,] → #dc2626
Number Format
Formats a number with a mask. The presets are 0, 0.0, 0.00, 000, #,##0, #,##0.0,
#,##0.00, #,###.## and ##.0##, or User Defined for your own. It formats numbers only, not
dates.
If the tag has a format string, prefer that: it applies everywhere the tag is shown.
Script
// value is the source value (after any earlier steps); return the result
if (error) return "—";
return (value * 3.6).toFixed(1) + " km/h";
Reach for it last. A Map is declarative, visible in the Binding Manager, and survives the next engineer; a script 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. Choose Indirect instead of Direct in the Tags source:
Template: /Line1/{{equipment}}/Speed
Mapping: Reference equipment ← Property Path Dropdown.value
Each {{name}} in the template is a Reference, filled from the Property Path mapped to it.
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 Refresh binding event action, from a button or any other 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.