Object and array literals (new in 1.2)
Style objects and small lookup lists can now be written straight into a binding. Both are parsed by hand like everything else, and each evaluation builds a fresh value, so a binding never shares an object between renders.
Risk: Medium
An object literal sets three style properties; an array literal indexed by level.value picks the colour and the label.
<div class="demo-stage">
<div class="demo-row demo-row--wrap">
<label class="rx-field">Level
<input type="range" min="0" max="2" step="1" data-model="level.value" value="1">
</label>
<label><input type="checkbox" data-model="bold.value"> bold</label>
<label><input type="checkbox" data-model="italic.value"> italic</label>
</div>
<p class="rx-literal-out"
data-bind-style="{color: ['var(--dm-success)', 'var(--dm-warning)', 'var(--dm-danger)'][level.value], fontWeight: bold.value ? 700 : 400, fontStyle: italic.value ? 'italic' : 'normal'}">
Risk: <span data-bind-text="['Low', 'Medium', 'High'][level.value]">Medium</span>
</p>
<p class="demo-note">An object literal sets three style properties; an array literal indexed by <code>level.value</code> picks the colour and the label.</p>
</div>The view model is three observables and nothing else:
/**
* Object and array literals in binding expressions (1.2).
*
* The style object and the label list are written straight into the
* attributes. They are parsed by hand, like every expression here - no eval,
* no Function constructor - and each evaluation builds a fresh value.
*/
function literals() {
return {
level: observable(1),
bold: observable(false),
italic: observable(false)
};
}What an expression can say
| Category | Forms |
|---|---|
| Paths | a, a.b.c, a[0], a[key], a['x'] |
| Literals | 'str', 1, 1.5, 1e3, true, false, null |
| Objects and arrays | {a: x, 'b-c': y}, [a, b, 'c'], [a, b][i] |
| Arithmetic and comparison | + - * / %, === !== < <= > >= |
| Logic | &&, ||, ! (short-circuiting), a ? b : c |
| Calls | helper(arg) - registered helpers only |
| Context | $data, $root, $parent, $parents, $parentContext, $index, $length |
Precedence and associativity are JavaScript's.
What it will not say
Assignment, new, member calls (name.toUpperCase()), loose equality, ??, regular expressions, template literals and
reads of __proto__, constructor or prototype - in any form, including an object key. Most are recognised
specifically so the warning can say what to do instead. Anything more complicated belongs in a computed.
Why no eval matters
Knockout, Alpine and Vue's runtime template compiler all turn binding strings into code with the Function
constructor, which a strict Content Security Policy blocks unless you allow unsafe-eval. Here the expressions are
parsed into a tree and walked, so the whole library runs under script-src 'self' - asserted against the source in
the unit suite and against every built bundle.
import {compileExpression, registerHelper} from 'domma-reactive';
registerHelper('upper', (s) => String(s).toUpperCase());
const evaluate = compileExpression("count > 0 ? upper(label) : 'none'");
evaluate({count: 3, label: 'items'}); // 'ITEMS'