Skip to main content
Version: 1.0.4

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

TypeSourceUse for
TagA namespace tagLive process values — the common case
ExpressionA formula over one or more tagsArithmetic, comparisons, combining several tags into one value
PropertyAnother component's propertyCoupling widgets: a dropdown that drives a chart
QueryA database query or named queryTables, 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 bindingThe calculation matters only to this one property on this one screen
Expression tagThe 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.

Expression functions

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.

TransformPurpose
mapLook up an output for an input — value, range or expression matching
thresholdPick an output from numeric bands
scaleLinear rescale from one range to another
formatNumber and date formatting, with masks
jsAn inline JavaScript expression over value
scriptA longer script for complex shaping

map

The most-used transform. Turn a state into a label, a colour, an icon.

Input typeMeaningExample
valueLiteral equality1Running
rangeNumeric interval; [ ] inclusive, ( ) exclusive, either bound optional[0,10)Low
expressionJavaScript over value; the row matches when truthyvalue > 100High
Output typeMeaning
valueLiteral — numbers, booleans and null are coerced; anything else stays a string
colorA CSS colour, kept verbatim
expressionJavaScript 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 refreshBinding event 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

  1. Open the binding manager and read the live preview — it shows the resolved value with transforms applied.
  2. Remove transforms one at a time to find which one changes the value unexpectedly.
  3. Check quality: a Bad-quality tag makes every downstream transform suspect.
  4. For indirect bindings, verify the resolved path — a typo in the template gives NotFound.
  5. For query bindings, run the statement directly against the connection.

Next

Event actions