This article is the second in a series dedicated to server-side templating languages. In the previous article, we covered the goals of templates and an overview of the main templating languages. Now, let’s dive into the technical aspects and explore two major features of templates:
- Data injection into pages
- Page layout management through inheritance
Injecting data into pages
Templating languages provide instructions to easily integrate data from the application into HTML pages. A template file is thus a mix of classic HTML/CSS and specific templating syntax elements, such as variables, loops, conditions, etc.
Example: A template file can be used to build a blog's article list from article metadata (title, date, tags, etc.).
The PHP, Python, JavaScript, or other backend code provides the collection of metadata, and the template iterates over this collection to construct an HTML list (using <ul>
and <li>
elements).
Here’s an excerpt from the first version of the Nunjucks template I used to build my blog’s article list. It uses a collection named category.posts
containing the articles of a category.
{# List of posts of the category #}
<ul id="postList">
{%- for post in category.posts %}
<li class="post" data-rank="0">
<a class="post-title" href="{{ post.url }}">{{ post.data.title }}</a>
<p>{{ post.data.description }}</p>
<div class="post-metadata">
<div class="post-taglist">Tags:
{%- for tag in post.data.tags %}<div class="post-tag">{{ tag }}</div>{%- endfor %}
</div>
<div>{{ labels.lastEdition }}:
<time class="post-date" datetime="{{ post.date | htmlDateString }}">{{ post.date | readableDate() }}</time>
</div>
</div>
</li>
{%- endfor %}
</ul>
All the code between curly braces is Nunjucks template syntax. Three types of braces are possible:
{# #}
for comments{% %}
for flow control (loops, conditions, etc.){{ }}
for evaluating or displaying expressions
In the above excerpt, you can see a few syntax elements:
{%- for ... in ... %}...{%- endfor %}
represents afor
loop.{{ post.data.description }}
displays the article’s description.{{ post.date | readableDate() }}
calls thereadableDate
method onpost.Date
to format the article’s creation date.
Note
The |
(pipe) operator allows calling a method while passing the starting object as a parameter.
Multiple |
can be chained to call different methods in sequence. For example, the expression {{ object | filter1() | filter2() }}
calls filter1()
with object
as a parameter, then filter2()
with the result of filter1()
as a parameter. This is called function composition, a powerful feature of some templating languages.
Without delving deeper into Nunjucks syntax, this example already gives you a good idea of how templating syntax integrates data into HTML. Now, let’s explore layout and composition techniques.
Managing Page Layouts with Template Inheritance
When creating templates for different page types (e.g., homepage, article list page, single article page, etc.), it’s common for these templates to share common parts like headers and footers.
Instead of repeating the code for these shared parts in every template, we can share them between templates using template inheritance. This concept is similar to what you’d find in object-oriented programming languages.
Warning
Advanced templating languages like Jinja2, Nunjucks, Twig, Django, Pug, and Blade support inheritance, but simpler languages like Handlebars, Mustache, HAML, or Markdown do not, nor does the Liquid language.
How template inheritance works
The inheritance relationship involves creating a base template containing all the shared elements and derived templates representing the different page types you need. Derived templates inherit all the content from the base template and can add or override specific elements (e.g., the main content area of the page).
For example, my Eleventy-powered blog uses 5 Nunjucks templates corresponding to different page types, all derived from a base template containing the header (with title and main menu) and footer.
The green shapes represent category and article pages. Articles are Markdown files, but they can still inherit from the Nunjucks layout post.njk
thanks to an abstraction layer managed by Eleventy, which relies on frontmatter placed at the beginning of files (more info on this documentation page). However, in the following code examples, to avoid confusion, I’ll show you pure Nunjucks inheritance syntax.
Note
The inheritance relationship can, of course, be extended to more than two levels to create a more complex template hierarchy.
Implementing the inheritance relationship
As you’ll see, the technique is very simple.
Here’s an example structure for the base template:
<head>
<!-- head content -->
</head>
<body>
<header>
<!-- header content -->
</header>
<main>
{% block mainContent %}{% endblock %}
</main>
<footer>
<!-- footer content -->
</footer>
</body>
{% block mainContent %}{% endblock %}
is Nunjucks syntax for defining a named block called mainContent, which is empty here but can be overridden in derived templates.
In all derived templates, add the following code to implement inheritance:
{% extends "base.njk" %}
{% block mainContent %}
<!-- code that describes the main content of the page -->
{% endblock %}
The first line establishes the inheritance relationship. The following lines override the mainContent block by adding code specific to the derived template.
To be continued
Template inheritance already allows for efficient project structuring. But we can go further with advanced composition and modularity techniques, which we’ll explore in the final part of this series.
In the meantime, if you found this article helpful, please like it to help spread the word. 😉