Expressions

More than a dotted path, less than JavaScript - and never eval.

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.

markup/feat-literals.html - the literals are in the attributesview whole file
<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:

apps/features.jsview whole file
/**
 * 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'

Next: components All features