Expressions
WildflowerJS binding attributes accept both simple property names and inline JavaScript expressions, enabling dynamic values without computed properties.
Overview
Most binding attributes accept either a simple property name or a JavaScript expression. Simple property names resolve directly from component state or computed properties. Expressions are evaluated at render time and re-evaluated whenever their dependencies change.
<!-- Simple property reference -->
<span data-bind="username"></span>
<!-- Expression with arithmetic -->
<span data-bind="price * quantity"></span>
<!-- Expression with ternary -->
<span data-bind="isActive ? 'Active' : 'Inactive'"></span>
The framework automatically detects whether a binding value is a simple property name or an expression by looking for operators, parentheses, or ternary syntax.
Where Expressions Work
Expressions are supported in these binding attributes:
| Attribute | Example | Purpose |
|---|---|---|
data-bind |
data-bind="price * qty" |
Text content |
data-bind-html |
data-bind-html="htmlContent" |
HTML content |
data-bind-class |
data-bind-class="isActive ? 'on' : 'off'" |
CSS classes |
data-bind-style |
data-bind-style="{ color: isError ? 'red' : 'black' }" |
Inline styles |
data-bind-attr |
data-bind-attr="{ disabled: !isValid }" |
HTML attributes |
data-show |
data-show="items.length > 0" |
Visibility toggle |
data-render |
data-render="isLoggedIn && hasPermission" |
Conditional rendering |
Syntax Reference
Property References
Reference state properties and computed properties by name. Nested properties use dot notation:
<span data-bind="count"></span>
<span data-bind="user.name"></span>
<span data-bind="address.city"></span>
Arithmetic
<span data-bind="price * quantity"></span>
<span data-bind="subtotal + tax"></span>
<span data-bind="total / count"></span>
<span data-bind="index % 2"></span>
<span data-bind="side ** 2"></span>
<span data-bind="(price * quantity) + shipping"></span>
Exponentiation follows JavaScript's rules: it binds tighter than multiplication and chains right to left, so 2 ** 3 ** 2 is 512.
Comparisons
<div data-show="count > 0">Has items</div>
<div data-show="status === 'active'">Active</div>
<div data-show="score >= 90">High score</div>
<div data-show="type !== 'hidden'">Visible</div>
Logical Operators
<div data-show="isLoggedIn && isAdmin">Admin Panel</div>
<div data-show="hasError || isLoading">Status</div>
<span data-bind="nickname || username"></span>
Ternary Operator
<span data-bind="isOnline ? 'Online' : 'Offline'"></span>
<span data-bind-class="isActive ? 'btn-primary' : 'btn-secondary'"></span>
<span data-bind="count === 1 ? 'item' : 'items'"></span>
Negation
<!-- Simple negation (not treated as an expression) -->
<div data-show="!isHidden">Visible</div>
<!-- Negation in expressions -->
<div data-show="!isLoading && items.length > 0">Ready</div>
<div data-bind-attr="{ disabled: !isValid }"></div>
String and Number Literals
<span data-bind="count + ' items'"></span>
<span data-bind="score > 100 ? 'High' : 'Low'"></span>
Store Access
Access store values in expressions using the $storeName.property shorthand:
<span data-bind="$user.name"></span>
<div data-show="$cart.itemCount > 0">Items in cart</div>
<span data-bind="$settings.theme === 'dark' ? 'Dark Mode' : 'Light Mode'"></span>
The $store.path syntax is converted to external('store', 'path') internally. See Basic Stores for more on store subscriptions.
Expressions vs Computed Properties
Both inline expressions and computed properties can derive values from state. Choose based on complexity:
| Use | When |
|---|---|
| Inline expression | Simple, one-off calculations: a comparison, a ternary, basic arithmetic |
| Computed property | Complex logic, reused across multiple bindings, or multi-step calculations |
<!-- Inline: fine for simple cases -->
<span data-bind-class="isActive ? 'active' : ''"></span>
<span data-bind="price * quantity"></span>
<!-- Computed: better for complex logic -->
<span data-bind="formattedTotal"></span>
<span data-bind-class="statusClass"></span>
wildflower.component('invoice', {
state: {
price: 29.99,
quantity: 3,
taxRate: 0.08
},
computed: {
formattedTotal() {
var total = this.price * this.quantity
var tax = total * this.taxRate
return '$' + (total + tax).toFixed(2)
},
statusClass() {
if (this.quantity === 0) return 'text-muted'
if (this.quantity > 10) return 'text-success fw-bold'
return 'text-primary'
}
}
})
As a rule of thumb: if the expression needs more than one operator or is used in multiple places, extract it into a computed property.
Content Security Policy (CSP)
By default, WildflowerJS compiles expressions with new Function(). In environments with strict Content Security Policy headers that block unsafe-eval, the framework detects this and switches to a built-in parser that never calls new Function(). Auto-detection works by attempting one new Function() call at startup; under a strict policy that attempt is blocked and reported as a single benign securitypolicyviolation before the framework switches.
new Function() is under 3x on a sub-nanosecond baseline, which is why the submitted js-framework-benchmark entries run in this mode and post the results they do. What the default buys is not speed, it is the wider grammar described under CSP Mode Limitations below.
Expressions are code, and markup is where they live
A binding attribute is evaluated as JavaScript, so data-bind deserves the same trust you give a <script> tag. This is the same position every template framework takes: Vue tells you never to compile a template you did not write, and Angular removed its expression sandbox in 1.6 after concluding it had never been a security boundary.
It matters more here than it might elsewhere, because the framework finds bindings for you. The mutation observer watches the document, so an attribute that arrives in markup later is picked up with no explicit compile step. If any part of a page is assembled from content you did not author, a comment body, a chat message, a CMS field, then injected markup is not inert the way it would be in a page without a framework, because a <script> tag inserted through innerHTML does not run but a binding attribute does.
Run in CSP-safe mode when that is true of your page. Its grammar is closed rather than filtered: identifiers, member access, arithmetic, comparisons, logical operators, ternary, and external() calls, with every other call form rejected outright. That is a real boundary, and as noted above it costs you close to nothing. The default mode has no such boundary, and no denylist can give it one.
Cross-field rules: checks go further. They use the CSP-safe interpreter in every mode, never new Function(), because their setup already parses each expression to learn which variables it reads. They behave identically with or without a strict policy.
Recommended: the data-csp-safe attribute
Add data-csp-safe to the framework script tag on a page served with a strict CSP, and on any page that renders content you did not author. The framework then starts directly in CSP-safe mode and never attempts dynamic code execution at all, so the page generates zero CSP violations and zero violation reports:
<script src="wildflower.min.js" data-csp-safe defer></script>
This matters on sites that use report-uri or report-to: without the attribute, the startup capability probe files one violation report per page load with your security tooling.
See it live: an interactive list running under default-src 'none' with a violation counter reading zero.
Runtime switch: wildflower.config()
When the script tag isn't under your control, switch at runtime instead:
wildflower.config({ forceCSPMode: true });
Expressions compiled after the call use the CSP-safe parser. This runs after the framework has already loaded, so it cannot suppress the startup probe; prefer the attribute when you control the script tag.
What CSP Mode Supports
The AST-based parser supports the same expression syntax used in bindings:
- Arithmetic:
+,-,*,/,%,** - Comparison:
==,!=,===,!==,<,>,<=,>= - Logical:
&&,||,! - Bitwise:
&,|,^,~,<<,>>,>>> - Ternary:
condition ? a : b - Unary:
-x,+x,!x,~x - Property access: dot notation (
user.name) and bracket notation (items[0]) - Function calls:
external()for store access - Literals: strings, numbers,
true,false,null,undefined, arrays - Grouping: parentheses
(a + b) * c
CSP Mode Limitations
The AST parser does not support:
- Method calls (e.g.,
str.toUpperCase()); use a computed property instead - Arrow functions
- Template literals (
`hello ${name}`) - Destructuring or spread operators
- Global objects (
Math,Date, etc.); use computed properties for these
new Function() and can reach JavaScript globals such as Math.round() or Date.now(). These do not work in CSP mode, which is the point of it: the same reachability that gives you Math gives an injected expression everything else on the page. Use computed properties for logic that needs global objects or method calls, and they will work in either mode.
Security
The CSP expression evaluator includes built-in security protections:
- No global access:
window,document,fetch,eval, and other browser globals are blocked - No prototype pollution: Access to
__proto__,constructor, andprototypeis blocked - Scope-limited: Expressions can only reference component state, computed properties, and store data via
external()