Tutorial: a product list
In this tutorial you build the product list of a small tea shop, one feature at a time. Each step shows the template, the data it is rendered with, and the output. You need nothing installed to follow along.
The data is the same throughout. The application gives the template one data root, shop, and two settings:
{
"data": {"shop": {
"name": "Leaf & Pot",
"products": [
{"title": "Sencha", "price": 1250, "stock": 12, "tags": ["green", "japan"]},
{"title": "Assam <strong>", "price": 899, "stock": 0, "tags": ["black"]},
{"title": "Oolong", "price": 1575, "stock": 3, "tags": []}
]
}},
"allow": ["shop"],
"settings": {"heading": "Our teas", "show_tags": true}
}#1. Print a value
{{ }} prints a value. shop.name reads the field name of the root shop; settings.heading reads a setting.
<h1>{{ settings.heading }} at {{ shop.name }}</h1>{"data": {"shop": {"name": "Leaf & Pot"}}, "allow": ["shop"], "settings": {"heading": "Our teas"}}<h1>Our teas at Leaf & Pot</h1>The & was escaped automatically. Everything you print is.
#2. Loop over the products
{% for %} repeats its body for each item. Inside, product is the current item.
<ul>{% for product in shop.products %}
<li>{{ product.title }}</li>{% endfor %}
</ul>{"data": {"shop": {"products": [{"title": "Sencha"}, {"title": "Assam <strong>"}, {"title": "Oolong"}]}}, "allow": ["shop"]}<ul>
<li>Sencha</li>
<li>Assam <strong></li>
<li>Oolong</li>
</ul>A product title containing markup cannot break the page: it is printed as text.
#3. Format the price with filters
Prices are in cents. Filters, written after |, transform a value, and you can chain them: divide by 100, then round to two decimal places. Notice that 12.5 prints without a trailing zero: proper money formatting is a job for a money filter that the application provides (see Extending).
{% for product in shop.products %}{{ product.title }}: €{{ product.price | divided_by: 100.0 | round: 2 }}
{% endfor %}{"data": {"shop": {"products": [{"title": "Sencha", "price": 1250}, {"title": "Oolong", "price": 1575}]}}, "allow": ["shop"]}Sencha: €12.5
Oolong: €15.75#4. Show stock with conditions
{% if %} chooses what to show. Conditions compare values with ==, > and the other operators.
{% for product in shop.products %}{{ product.title }}: {% if product.stock > 5 %}in stock{% elsif product.stock > 0 %}only {{ product.stock }} left{% else %}sold out{% endif %}
{% endfor %}{"data": {"shop": {"products": [{"title": "Sencha", "stock": 12}, {"title": "Assam", "stock": 0}, {"title": "Oolong", "stock": 3}]}}, "allow": ["shop"]}Sencha: in stock
Assam: sold out
Oolong: only 3 left#5. List the tags, or nothing
join turns an array into text. An empty array is still truthy, so test its size rather than the array itself. The setting show_tags lets the shop owner switch tags off.
{% for product in shop.products %}{{ product.title }}{% if settings.show_tags and product.tags.size > 0 %} ({{ product.tags | join: ", " }}){% endif %}
{% endfor %}{"data": {"shop": {"products": [{"title": "Sencha", "tags": ["green", "japan"]}, {"title": "Oolong", "tags": []}]}}, "allow": ["shop"], "settings": {"show_tags": true}}Sencha (green, japan)
Oolong#6. Count with variables
assign stores a value. Count the products in stock as you go, and print the total after the loop.
{% assign available = 0 %}{% for product in shop.products %}{% if product.stock > 0 %}{% assign available = available | plus: 1 %}{% endif %}{% endfor %}{{ available }} of {{ shop.products.size }} teas available{"data": {"shop": {"products": [{"stock": 12}, {"stock": 0}, {"stock": 3}]}}, "allow": ["shop"]}2 of 3 teas availableThe same in one line, with filters: {{ shop.products | where: "stock" | size }} would not work here, because a stock of 0 is truthy. Variables are clearer.
#7. Move the card into its own template
The product markup is getting long. {% render %} renders another template, and passes it what it needs as arguments. The child sees only its arguments, so it cannot accidentally depend on the page around it.
<ul>{% for product in shop.products %}{% render "product-card", product: product %}{% endfor %}</ul>{
"data": {"shop": {"products": [{"title": "Sencha", "stock": 12}, {"title": "Assam", "stock": 0}]}},
"allow": ["shop"],
"children": {"product-card": {"source": "<li class=\"{% if product.stock == 0 %}sold-out{% endif %}\">{{ product.title }}</li>"}}
}<ul><li class="">Sencha</li><li class="sold-out">Assam</li></ul>#8. Put it together
<h1>{{ settings.heading }} at {{ shop.name }}</h1>
{%- assign available = 0 -%}
{%- for product in shop.products -%}
{%- if product.stock > 0 %}{% assign available = available | plus: 1 %}{% endif -%}
{%- endfor %}
<p>{{ available }} of {{ shop.products.size }} teas available</p>
<ul>
{%- for product in shop.products %}
<li>{{ product.title }}: €{{ product.price | divided_by: 100.0 | round: 2 }}
{%- if product.stock == 0 %} (sold out){% endif -%}
{%- if settings.show_tags and product.tags.size > 0 %} [{{ product.tags | join: ", " }}]{% endif -%}
</li>
{%- endfor %}
</ul>{
"data": {"shop": {
"name": "Leaf & Pot",
"products": [
{"title": "Sencha", "price": 1250, "stock": 12, "tags": ["green", "japan"]},
{"title": "Assam <strong>", "price": 899, "stock": 0, "tags": ["black"]},
{"title": "Oolong", "price": 1575, "stock": 3, "tags": []}
]
}},
"allow": ["shop"],
"settings": {"heading": "Our teas", "show_tags": true}
}<h1>Our teas at Leaf & Pot</h1>
<p>2 of 3 teas available</p>
<ul>
<li>Sencha: €12.5 [green, japan]</li>
<li>Assam <strong>: €8.99 (sold out) [black]</li>
<li>Oolong: €15.75</li>
</ul>The - inside {%- and -%} removes the whitespace around those tags, which keeps the output tidy without cramming the template onto one line. See Whitespace control.
#Where next
- The rest of the language: Variables, Loops, Filters.
- Rendering this from Go: Getting started.