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:
| 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, a view parameter, the session, a client variable or a gateway variable | 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.
The Properties tree also lists two kinds of variable:
| Namespace | Reference | Behaviour |
|---|---|---|
| Client Variables | client.<name> | Tab-local state, set by a Set variable action or a script. Instant. |
| Gateway Variables | vars.<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 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.
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.8A 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.
| 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";
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.
| Item | What it does |
|---|---|
| Copy property | Puts 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 property | Pastes 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 path | The 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 all | Only on rows with children. Expand all stops at 500 rows and says so |
| Revert to saved | Back to the value in the last save. Offered only when that saved value can be found |
| Reset to default | Back 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 type | Means |
|---|---|
now, now-2h, now+30m | Relative to the moment the view loads (m, h, d, w) |
today, today+5d | Relative to midnight today |
2026-10-20 08:00 | That local time |
1760947200000 | Epoch 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
- 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.
- For script transforms, read the validation footer — a misspelled name is reported there.