Expressions
bind: attributes, ui: directives and {{ }} interpolations hold an expression: a small language for reading your data and combining it. It is not ThicketScript. It has no calls, no assignment and no indexing, on purpose: logic lives in the controller, and the template only reads.
xml
<Panel layout="vbox" spacing="8">
<Label bind:text="name ? 'Hello, ' + name + '!' : 'Type your name'" />
<Button text="Buy" bind:enabled="gold >= price" />
<Panel ui:show="!loading && items" />
</Panel>Values
| Kind | Written as |
|---|---|
| Number | 42, 3.5 |
| String | 'single' or "double" quotes. Inside an attribute that uses double quotes, use single. Escapes: \n, \t, \\, and a backslash before any other character keeps that character (\'). |
| Boolean | true, false |
| Nothing | nil |
| A name | count, user.name, item.price |
Names
A name is looked up in this order:
- Loop variables from an enclosing
ui:for, innermost first. A dotted name whose first part is a loop variable walks into that value: map keys, aReactiveRecord's fields, or plain object fields. - The controller:
lookupRef(path)is called with the whole name, dots included ("user.name"). Your controller decides what that means.
If the answer is a Ref (or a computed), its current value is used and the binding subscribes to it: when the ref changes, the expression runs again. That is the whole reactive story. Return refs from lookupRef for anything that changes.
A name nobody answers is nil.
Operators
From lowest to highest precedence:
| Operators | Meaning |
|---|---|
c ? a : b | Conditional. Right-associative. |
a || b | Or. Short-circuits. The result is true or false. |
a && b | And. Short-circuits. In XML write &&. |
a == b, a != b | Equality. Two strings compare as text, anything else as numbers. |
<, <=, >, >= | Comparison, as numbers. In XML write < for <. |
+, - | Add and subtract. + joins strings if either side is a string. |
*, /, % | Multiply, divide, remainder. Dividing by zero gives 0. % works on whole parts. |
!a, -a | Not, negate. |
( ) | Grouping. |
Truthiness
!, &&, ||, ? : and ui:show treat these as false: nil, false, 0 and the empty string "". Everything else is true. So ui:show="items" hides a panel while items is nil, and name ? ... : ... tests for an empty string.
Comparisons
When both sides of == or != are strings, they compare as text: name == 'Bob' is true only for Bob, and name != '' is true for any name that is not empty. Otherwise both sides are converted to numbers first: booleans become 0 and 1, numeric strings parse, and any other string becomes 0. <, <=, > and >= always compare as numbers.
Interpolation
Inside element text, {{ }} marks an expression:
xml
<Label>Level {{level}}: {{hp}} / {{maxHp}} HP</Label>The text becomes a concatenation of literal parts and expressions. An unclosed {{ or a piece that does not parse is kept as literal text.
Errors
A syntax error in bind:, ui:if, ui:show, ui:for or ui:key fails the compile with a TemplateError naming the attribute. A runtime problem (a missing name, a wrong type) never throws: the value becomes nil, 0 or "", and a binding whose evaluation fails simply skips that update.
In code
The expression API is public, which is handy for tests or for your own widgets: parseExpression(src) returns an ExprNode, evalExpression(node, new EvalContext(provider)) evaluates it against any DataProvider.