Vascula v0.1.5

Variables, data and scope

#Where names come from

When a template reads a name such as product in {{ product.title }}, Vascula looks it up in this order:

  1. Variables the template created with assign or capture, and loop variables inside their loop.
  2. **settings**: the template's own parameters. Always readable.
  3. Globals: names the application makes readable everywhere, such as theme in a theme system.
  4. Data roots the application allowed for this template.

Anything else is an error, so a typo cannot quietly print nothing:

Template
{{ produt.title }}
Data
{"data": {"product": {"title": "Cup"}}, "allow": ["product"]}
Error
undeclared name "produt"

The application decides which data roots a template may read (see Embedding). A root in the data but not allowed is just as unreadable as a name that does not exist.

#Reading fields

Use a dot for a field and brackets for a key or position.

Template
{{ product.title }} / {{ product["title"] }} / {{ product.tags[0] }} / {{ product.tags[-1] }} / {{ product.tags[settings.i] }}
Data
{"data": {"product": {"title": "Cup", "tags": ["new", "sale", "blue"]}}, "allow": ["product"], "settings": {"i": 1}}
Output
Cup / Cup / new / blue / sale

A negative position counts from the end. A missing field or an out-of-range position is nil, which prints nothing; only the first name of a path must exist.

Template
[{{ product.missing }}] [{{ product.missing.deeper }}] [{{ product.tags[9] }}]
Data
{"data": {"product": {"tags": ["new"]}}, "allow": ["product"]}
Output
[] [] []

#size, first and last

Every array, map and string has three built-in properties. A field of the same name in a map wins.

PropertyArrayMapString
.sizenumber of itemsnumber of keysnumber of characters
.firstfirst itemnilnil
.lastlast itemnilnil
Template
{{ product.tags.size }} {{ product.tags.first }} {{ product.tags.last }} {{ product.title.size }}
Data
{"data": {"product": {"title": "Café", "tags": ["new", "sale"]}}, "allow": ["product"]}
Output
2 new sale 4

.size of something missing is 0, so {% if product.images.size > 0 %} is safe even when images is absent.

#assign

assign stores the value of an expression, filters included.

Template
{% assign title = product.title | upcase %}{% assign count = product.tags.size %}{{ title }} has {{ count }} tags.
Data
{"data": {"product": {"title": "Cup", "tags": ["new", "sale"]}}, "allow": ["product"]}
Output
CUP has 2 tags.

#capture

capture renders its body and stores the result as a string. The stored string is ordinary text: printing it escapes it again.

Template
{% capture label %}{{ product.title }} ({{ product.tags.size }}){% endcapture %}<span title="{{ label }}">{{ label }}</span>
Data
{"data": {"product": {"title": "Cup", "tags": ["new"]}}, "allow": ["product"]}
Output
<span title="Cup (1)">Cup (1)</span>

For reusable markup, prefer [render](/composition) to capture.

#Scope

A template has one set of variables. A value assigned or captured anywhere, including inside if, case, for or capture, is available after that block ends.

Template
{% for n in (1..5) %}{% if n > 2 %}{% assign first_big = first_big | default: n %}{% endif %}{% endfor %}{{ first_big }}
Error
undeclared name "first_big"

Reading a variable before anything has assigned it is still an error, as the example shows: the first default reads first_big before it exists. Assign a starting value first:

Template
{% assign first_big = nil %}{% for n in (1..5) %}{% if n > 2 %}{% assign first_big = first_big | default: n %}{% endif %}{% endfor %}{{ first_big }}
Output
3

Three exceptions keep things predictable:

  • Loop variables belong to their loop. After {% endfor %} the name is gone, and assigning to it inside the loop changes only the current item.
  • **forloop** exists only inside a loop.
  • A child template rendered with {% render %} has its own variables. It cannot see the caller's, and its assignments do not leak back. See Composition.
Template
{% assign total = 0 %}{% for price in settings.prices %}{% assign total = total | plus: price %}{% endfor %}Total: {{ total }}
Data
{"settings": {"prices": [3, 4, 5]}}
Output
Total: 12

#Reserved names

settings and forloop cannot be assigned, captured, used as a loop variable or passed to a child. Applications reserve their own globals the same way, so a template cannot hide them.

Template
{% assign settings = "x" %}
Error
assignment name "settings" is reserved

Next: Expressions and operators.