Variables and Composition

You have seen selectors, data extraction, filtering, mapping, aggregation, null handling, and element construction. This guide covers the tools that hold longer expressions together: let bindings, if expressions, dictionary construction, namespace declarations, and the patterns that emerge when all of these are combined.

let bindings

A let binding names a value and makes it available in the rest of the expression.

let name = expr; body

The binding evaluates expr, assigns the result to name, and then evaluates body. The value of the whole expression is the value of body.

let count = ${div.item}.count();
let label = count > 0 ? "items found" : "no items";
label

Bindings are evaluated in order. A later binding can reference an earlier one. The final expression — the one without let — is the return value.

let is an expression, not a statement. It can appear anywhere an expression is expected: inside an if branch, inside a ternary, inside a map callback, inside a construction body. You will see this used extensively in the "Putting it all together" example below.

if expressions

When conditional logic needs more than a simple ternary, use if expressions. They support multiple branches and multi-statement bodies:

let count = ${div.item}.count();
let message = if (count > 10) {
  "Showing first 10 of " + count.String()
} else if (count > 0) {
  count.String() + " items"
} else {
  "No items found"
};
message

if is an expression — the whole construct evaluates to the value of the taken branch. When no branch matches and there is no else, it returns null.

Inside an if body, you can use let bindings for intermediate calculations. The bindings are scoped to that body:

if (${[itemprop="product"]}.count() > 0) {
  let products = ${[itemprop="product"]};
  let total = products.map(p => p.value().Number()).reduce(0, (a, b) => a + b);
  new div.summary { text: total.String() + " total" }
}

Building dictionaries

Dictionaries created with let support property assignment using either dot notation or subscript syntax. This is how you build up structured data step by step.

let product = {};
product.name = "Widget";
product.price = 9.99;
product

Dot notation works when the key is a known identifier. Subscript notation works with a dynamic key from another expression:

product[dynamicKey] = value

Dictionaries also have read methods:

Method Returns
.keys() List of all keys in the dictionary
.values() List of all values
.entries() List of { key, value } dictionaries
.contains(key) true if the key exists, false otherwise
.count() Number of entries in the dictionary

@namespace declarations

At the top of an expression, you can declare XML namespace prefixes using @namespace:

@namespace prefix url("uri");

Once declared, the prefix can be used in selector literals and construction expressions with the prefix|name syntax.

This is how you work with SVG elements:

@namespace svg url("http://www.w3.org/2000/svg");
new svg|svg[width="200"][height="200"] {
  new svg|circle[cx="100"][cy="100"][r="50"][fill="red"] {}
}

And with Pagelove namespace attributes:

@namespace p url("https://pagelove.org/Binding/CSS");
new div[p|transient][p|ttl="3600"] { new h1 { text: "Draft" } }

Multiple @namespace declarations are allowed. They must all appear before the first expression in the file.

Putting it all together

Here is a complete transformation that uses let, map, if, construction, and a namespace declaration together:

@namespace p url("https://pagelove.org/Binding/CSS");
let products = ${[itemprop="product"]};
let count = products.count();
let header = new h2 { text: "Found " + count.String() + " products" };
let items = products.map(el =>
  let name = el.attr("data-name");
  let price = el.value().Number();
  let badge = if (price > 100) { new span.premium { text: "Premium" } } else { new span.value { text: "Great value" } };
  new li.product {
    new span[itemprop="name"] { text: name },
    new span[itemprop="price"] { text: price },
    badge
  }
);
new section.results[p|transient] {
  header,
  new ul { items }
}

Walking through the steps:

  1. The @namespace declaration makes p| available for Pagelove attributes.
  2. let products captures the list of product elements once.
  3. let count derives a number from that list.
  4. let header builds an <h2> element using string concatenation.
  5. let items maps over products. The callback is itself a let chain: it extracts name and price from each element, uses an if expression to choose a badge element, then constructs and returns an <li>.
  6. The final expression assembles a <section> marked p|transient, containing the header and a <ul> built from the mapped items.

Notice that let inside the map callback is not special syntax — it is just a let expression appearing in the position where the callback body is expected. The same composability applies everywhere.

Where to go from here

This is the last guide in the progressive sequence. For complete details on everything covered across all four guides, see the reference section: