Skip to main content
Version: 1.0.8

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

A property holds one binding. To combine several sources into one value, use an expression binding rather than a second binding.

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 property, a view parameter, the session, a client variable or a gateway variableCoupling 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.

The Properties tree also lists two kinds of variable:

NamespaceReferenceBehaviour
Client Variablesclient.<name>Tab-local state, set by a Set variable action or a script. Instant.
Gateway Variablesvars.<name>Read-only mirror of a gateway variable, refreshed every 4 seconds while something on screen is bound to it.

Gateway variables in LiveView Changed in 1.0.8​

A vars.<name> binding now works for operators. Before 1.0.8 it only resolved for a signed-in Platform user: in LiveView, for an operator or a visitor to a public project, the request was refused every 4 seconds, the refusal was swallowed and the widget stayed empty.

In LiveView a view can read only the variables that its own project references — in a view binding or a client script (vars.x, vars["x"], system.vars.get("x")). The variable store is shared across projects, so a variable that no view or script of this project mentions is not sent. A reference you add is picked up within about 10 seconds.

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.

The Syntax pane in the Binding Manager shows the query's statement in the same editor it was written in — coloured, read-only and copyable — and grows to fill the column.

1.0.8

A reopened view runs its queries again Changed in 1.0.8​

Mounting a widget marks its query bindings stale, so a view that opens again — a popup, a tab switched back to, a map marker's detail for the same ship — re-runs its queries instead of replaying the rows it showed the first time. The old rows stay on screen until the new ones land, so it never flashes empty. Before 1.0.8 a result was cached for the life of the page, and only a full reload fetched it again. Identical bindings mounting together still share one request.

Transforms​

A transform chain shapes the raw value before the component sees it. Add steps with the + in the Transforms header; each step 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";

The script receives value, error, context, system and project.

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.

Validation Changed in 1.0.8​

The footer under the editor validates the script as you type. A name that resolves nowhere — a typo such as totl for total, or a variable nobody declared — now fails validation, with a red squiggle on the name and an entry in the problems list. Before 1.0.8 the footer said Script Valid, and LiveView then threw on that line every time it ran.

The validator also stopped skipping most of the script: error used to be stood in by a truthy placeholder, so the template's if (error) return 0; guard returned at once and nothing below it was checked.

A script transform on a value that never changes Changed in 1.0.8​

Script transforms run in a sandbox that loads after the page. A value that arrived before it had loaded used to pass through untransformed, waiting for a next update that a gateway variable or a static parameter never sends — a Table bound to vars.x with a rows transform showed No data available until something else redrew it. The transform now re-runs once the sandbox is ready.

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 when the widget mounts, and on demand:

  • A Refresh binding event action, from a button or any other event. A query-bound Table, which fetches its own pages, honours it too 1.0.8 — before 1.0.8 it kept its old rows until a reload.
  • context.refreshBinding(...) from a script.
  • Re-evaluation when a bound parameter changes.
  • Re-evaluation when the view opens again.

Do not poll a query binding at high frequency. If a value needs to be live, it should be a tag.

Working with one property​

Right-click a property row in the properties panel for a menu that acts on that one property 1.0.8. It is separate from the bind control's own menu beside it (Copy binding / Paste binding): the bind control owns the binding, the row owns the value. The menu's heading names the property, so a mis-aimed right-click is obvious before you pick anything.

ItemWhat it does
Copy propertyPuts the property on the system clipboard as JSON — its key and its value, or its binding (source and whole transform chain) when it is bound, never the value the binding happened to resolve to
Paste propertyPastes it back onto this row. Our own JSON is pasted in full; any other JSON is taken as a value; anything else as a string. A value that does not fit the property's type is refused rather than stored
Copy pathThe script path to this property, e.g. context.nodes.<id>.props.resources[0].id — ready to paste into a script or expression
Expand all / Collapse allOnly on rows with children. Expand all stops at 500 rows and says so
Revert to savedBack to the value in the last save. Offered only when that saved value can be found
Reset to defaultBack to the component's default. Offered only when the value differs from it

The copied JSON is readable in a text editor:

{
"qubiq": "qubiq.property",
"version": 1,
"key": "data",
"binding": { "source": "query.Orders", "bindingType": "query" }
}

Paste property becomes available once something has been copied in this session; the browser would otherwise have to ask permission to read the clipboard just to open the menu.

A view's own properties (width, height, themeLock) can now be reverted too 1.0.8, and Copy path emits a path a script can actually use — before 1.0.8 it gave the internal nodes.X.props.resources.0.id, which is a syntax error in JavaScript.

Date and time properties New in 1.0.8​

A date-time property — the Scheduler's rangeStart and rangeEnd — has a calendar button beside its text field. Typing stays the main way in, because the field takes more than one fixed instant:

You typeMeans
now, now-2h, now+30mRelative to the moment the view loads (m, h, d, w)
today, today+5dRelative to midnight today
2026-10-20 08:00That local time
1760947200000Epoch milliseconds, UTC

Relative and epoch values show in amber, and the calendar opens empty for them rather than pretending today+5d is one particular date. A relative value is usually what a live board wants: the window stays on screen without anyone re-dating the widget every morning.

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.
  6. For script transforms, read the validation footer — a misspelled name is reported there.

Next​

→ Event actions