Back to articles

working with templates

I’ve worked with a number of templating engine across different systems which vary quite a lot.

A lot of the approach you take to building something will depend on the features the engine has.

Rather than give an overview of the language, I thought I’d talk a little about how to apply some of it.

I’m going to talk a little about nunjucks - it is feature rich, well supported and has extensive docs.

For this, I’ve mashed together some bootstrap templates to make a pretty generic looking website.

example web page

You can see the code on github.

With nunjucks there are three features we need to talk about before we start deciding how to start to split up the page.

Extend - this allows us to define a base template, and then override parts of it within the child template. This is known as template inheritance.

Includes - this allows us to pull another template into our current template

Macro - these are functions, you can define a reusable chunk of code.

Base template

You would normally hope for more than one page, then you can make a better judgement about what you should turn the elements of the page into, but given the page above, the first part we will work out is a base template

We will be having one base template that all other pages will extend out of this, in the case above we’d have one with the header and the footer in it.

This template will have a series of blocks that we can override in a child page - so we will have a home page that extends the base template.

A block is a named space that can be replaced by the child template.

If we assume that the header and footer won’t need to be replaced then our base template will look something like this:

  <!doctype html>
    <html lang="en">
      <head>
      // stuff for header
      </head>
      <body>
        // navigation html

        // block: content

        //footer html
    </body>
  </html>

Even within this, we have some options, we could already extract the navigation and footer to their own either macros or includes.

When I look at templating I am looking at how we compose different elements, once we start splitting it down into smaller chunks we start to think about how they are used.

The aim is to make it more maintainable, though if we over extract then we can be in danger of making it too complex.

Check out the full base template, you can see I’ve moved navigation and footer into includes, this is to isolate them.

The home page

We can now create a page by making a file like the one below

    {% extends 'templates/base-includes.nunjucks' %}

    {% block pageTitle %}
    
    {% endblock %}


    {% block content %}
    // home page content lives here
    {% endblock %}

We can now start working out what to do with our home page content, this can be broken down into three parts.

  1. jumbotron
  2. subscription information
  3. three column content

We will assume that all three of these are includes for the moment, these can now be included in new pages, so if we want to make an alternative version change what we include, or if we want to update a section, we are now changing a smaller file.

What can we make a macro

The thing that stands out that will work as a macro is the subscription information.

We have three repeating blocks, if we look at all three we work out what is common so we have:

  // classes

  // header

  // price

  // features

  // button

We can start by building a macro that takes one parameter - the title, then build up the rest.

The macro is called priceBox and takes a parameter title, this is then printed in place of the original header.

    {% macro priceBox(title) %}
      <div class="card mb-3 shadow-sm">
          <div class="card-header">
            <h4 class="my-0 font-weight-normal">{{ title }}</h4>
          </div>
          <div class="card-body">
          </div>
      </div>
    {% endmacro %}

To use this we first import it to our other template, and then call it, below is the wrap around include we are using.

  {% from '../../components/price-box.nunjucks' import priceBox %}

  <div class="container">
      <div class="card-deck mb-3 text-center">

      {{ priceBox('Free') }}

      {{ priceBox('Pro') }}

      {{ priceBox('Enterprise') }}

      </div>
  </div>

There are two ways to import a macro, you can read more about import over at the docs.

We can now expand our macro to take price, features and buttons.

Below is an example of an extended version, I’ve named the variables we are passing in so it makes it a little clearer.

We can give these defaults in the macro if needed, for this it doesn’t really need it.

Features takes an array of items that we loop over in the macro and button takes an object so we can supply multiple values.

{{ 
    priceBox(
        title = 'Free',
        price = '0.00',
        features = [
            '10 users included',
            '2 GB of storage', 
            'Email support', 
            'Help center access'
            ],
        button = {
            text: 'Sign up for free',
            classes: 'btn-outline-primary'
            }
        )
}}

Our macro now looks like the below, if no futures are provided then we default the text.

  {% macro priceBox(title, price, features, button) %}
  <div class="card mb-3 shadow-sm">
        <div class="card-header">
          <h4 class="my-0 font-weight-normal">{{ title }}</h4>
        </div>
        <div class="card-body">
          <h1 class="card-title pricing-card-title">${{ price }} <small class="text-muted">/ mo</small></h1>
          <ul class="list-unstyled mt-3 mb-4">
          {% for feature in features %}
            <li>{{ feature }}</li>
          {% else %}
            <li>No features</li>
          {% endfor %}
          </ul>
          <button type="button" class="btn btn-lg btn-block {{ button.classes }}">{{ button.text }}</button>
        </div>
  </div>
{% endmacro %}

We now have the same page as we started with, but broken down into re-usable chunk, we can now build additional pages.

Nunjucks also has a number of built-in filters you can also extend it to add your own.

orginally published on dev.to