Skip to main content
Version: 1.0.6

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:

SidebarSourceUse for
TagsA namespace tag, picked directly or built from a templateLive process values — the common case
PropertiesAnother component's propertyCoupling widgets: a dropdown that drives a chart
QueriesA named queryTables, lists, lookups
ExpressionA formula over one or more tagsArithmetic, 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 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 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.

TransformPurpose
Number FormatFormat a number with a mask
MapLook up an output for an input — exact value, numeric range or expression
ScriptJavaScript 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

OptionMeaningExample
Exact valueLiteral equality1 → Running
Numeric rangeInterval; [ ] include the end, ( ) leave it out, either bound optional[0,10) → Low
ExpressionJavaScript over value; the row matches when truthyvalue > 100 → High

Produce output as

OptionMeaning
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 (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​

  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