JavaScript expression binding

A JavaScript expression binding evaluates a single JavaScript expression during server-side composition and exposes its value as a named variable — the JavaScript peer of the Sessel expression binding (e:). The expression runs server-side in dombase-js; for the broader server-side JavaScript surface see JavaScript in schemas and the JavaScript DOM API.

Namespace declaration

Declare the JavaScript binding namespace on an ancestor element:

<html xmlns:j="https://pagelove.org/Binding/JavaScript">

The namespace URI must be exactly https://pagelove.org/Binding/JavaScript. The prefix (here j) can be any valid XML prefix.

Attribute form

Each j:name="<expr>" attribute evaluates <expr> as one JavaScript expression and exposes its value under name. Attributes evaluate in declaration order, and existing bindings are visible to later expressions as bare identifiers:

<ul j:total="[10, 20, 30].reduce((a, b) => a + b, 0)"
    j:doubled="total * 2"
    pagelove:template="text/liquid">
  <li>Total: {{ total }}</li>
  <li>Doubled: {{ doubled }}</li>
</ul>

Here total is computed first with a JavaScript reduce, then doubled reads it as a plain identifier. The expression may also use await, so it can resolve a Promise before its value is bound.

In action

Given the page above stored on the host:

{% example "setup-js-summary", "body" %}

Fetching it renders the computed values:

GET /js-expr-summary.html
HTTP/1.1 200

<!DOCTYPE html>
<html>
<body>
  <ul>
    <li>Total: 60</li>
    <li>Doubled: 120</li>
  </ul>
</body>
</html>

What the expression can see

A j: expression runs as the body of one JavaScript function. In scope are:

A name is available as a bare identifier only when it is a plain JavaScript identifier and not a reserved word. A binding named after a keyword or with a non-identifier name (j:class, j:new, j:"first name") is still computed, but a later expression must read it as Context["class"] rather than a bare class.

Caching. Reading a per-user field of requestrequest.auth, request.headers, and other identity members — marks the response as user-varying, so it is served Cache-Control: private and excluded from the shared cache. Reading only shared fields (request.path, request.query, request.method) keeps the page publicly cacheable. Naming request without reading an identity member does not taint it.

Only the attribute form

Like e: Sessel bindings, JavaScript bindings exist only in the attribute form j:name="…". There is no <j:…> element form, and no standalone JavaScript expression type to declare on a schema — the j: attribute is the whole surface.

Sessel or JavaScript?

e: and j: are peers: each computes a value during composition and exposes it to later bindings and templates. Reach for:

They interleave freely on one element; use whichever reads best for each value. See the three bindings compared.

Error cases

Condition Result
JavaScript expression fails to parse, throws, or rejects Request fails with an HTML-Microdata error during composition
Namespace URI is not exactly https://pagelove.org/Binding/JavaScript j: attributes are ignored — no bindings are created

See also