CHAPTER 4
Swig is a simple, powerful, and extendable JavaScript template engine. A template engine is typically used to display data that has been returned from a query to the database. The template engine combines data and markup to generate an output that can be rendered by the browser. Swig’s syntax is very similar to many existing template engines such as Jinja2 and Django, used by other programming languages. If you are familiar with any of these technologies, you should find Swig really simple.
Some of the advantages of using Swig are that it:
Swig templates can either end with a .swig or .html extension. You can control this in the keystone.init method in the keystone.js file in the root of the application.
Code Listing 34: Set up view engine
keystone.init({ //use .html extension for template files 'view engine': 'html', }); |
All templates reside within /templates/views folder within the application.
In a template language, variables passed to the template are replaced at predefined locations in the template. In Swig, variable substitutions are defined by {{ }}. There are also control blocks defined by {% %}, which declare language functions, such as loops or if statements.
To render the title of a news item, use the following notation.
Code Listing 35: Render a variable
{{ news.title }} |
Swig follows the same rules as JavaScript. If a key includes non-alphanumeric characters, it must be accessed using bracket-notation, not dot-notation.
We can also invoke functions and render the output as if we were rendering a variable. If you recall, we introduced a virtual method named url in the previous chapter (see Code Listing 19). We can invoke the url method from the template and render a URL to the detail page for the news item.
Code Listing 36: Invoke a method from template
<a href=’{{ news.url() }}’> {{news.title}} </a> |
To output comments, use a curly brace followed by the hash sign. Comments are removed by the parser during rendering, and will not be seen even if you do a view-source on the rendered HTML page.
Code Listing 37: Output code comments
{# This is a comment. #} |
Swig also provides convenient syntaxes for common control structures, such as conditional statements and loops. These shortcuts provide a very clean, terse way of working with control structures, while also remaining similar to their JavaScript counterparts.
If statements
You may construct if statements using the if, elif, else, and endif directives.
Code Listing 38: If conditionals
{% if length(newsitems) > 10 %} I have more than 10 records {% elif length(newsitems) < 5 %} I have less than 5 records! {% else %} I don't have any records! {% endif %} |
Boolean operators like and and or can be used within logic tags. Code Listing 39 is an example illustrating the use of the and conditionals.
Code Listing 39: Boolean operators
{% if length(news.title) > 10 and length(news.content) > 10 %} {{news.title}} can be displayed. {% endif %} |
We can also use built-in JavaScript functions within the conditional statements.
Code Listing 40: Use JS functions in conditionals
{% if news.title.indexOf(“critical”) > -1 %} This is a critical news item {% endif %} |
Loop statements:
Swig also supports looping statements to iterate over arrays and objects. To iterate over the tags array in the news item object, add the following markup to the template.
Code Listing 41: Loop statements
<ul> {% for tag in newsitem.tags %} <li> {{tag}} </li> {% endfor %} </ul> |
Swig has a collection of very helpful loop control helpers. These provide additional information about the state of the loop in an iteration.
Code Listing 42: Loop control helpers
{% for tag in data.ticket.tags %} {% if loop.first %}<ul>{% endif %} <li>{{ loop.index }} - {{ tag }}</li> {% if loop.last %}</ul>{% endif %} {% endfor %} |
During every for loop iteration, the following helper variables are available:
To reverse a loop, we can use the reverse filter:
Code Listing 43: Reverse loop
<ul> {% for tag in newsitem.tags | reverse %} <li> {{tag}} </li> {% endfor %} </ul> |
Filters are methods through which output can be manipulated before rendering. Filters are special functions that are applied after any object token in a variable block using the pipe character (|). Filters can also be chained together, one after another.
If, for example, we wanted to convert the title of a news item to title case and strip any HTML tags that might have been input, we can use the title and striptags filters.
Code Listing 44: Use filters in templates
<div> News Title - {{newsItem.title | title | striptags}} </div> |
Here is a list of the 23 available filters in Swig:
Since most web applications maintain the same general layout across various pages, it's convenient to define this layout as a single Swig template and use some kind of template inheritance/injection to be able to render specific partial templates. Swig makes this easy with extends and block directives.
Create a template named layout.swig and save it under the /templates/layouts folder with the following content.
Code Listing 45: Base layout template
<!doctype html> <html> <head> <meta charset="utf-8"> <title>{% block title %}NodePress{% endblock %}</title> {% block head %} <link rel="stylesheet" href="main.css"> {% endblock %} </head> <body> {% block content %}{% endblock %} </body> </html> |
Next, to use the base template in another template, create a template named news.swig and save it in the templates/views directory with the following content.
Code Listing 46: Extend base layout template
{% extends 'layout.html' %} {% block title %}News Detail Page{% endblock %} {% block content %} <p>We will display the news details here</p> {% endblock %} |
Template partials
Templates can easily get bulky and difficult to maintain if we do not organize the contents in a good manner. An easy way to organize the different sections of a template is to use template partials. Template partials are pieces of templates that reside in separate files and are combined together to make a single template.
For example, the preceding news layout template can leverage multiple partials that are concerned with displaying the header and footer of the pages.
Code Listing 47: Header template
<!doctype html> <html> <head> <meta charset="utf-8"> <title>{% block title %}NodePress{% endblock %}</title> {% block head %} <link rel="stylesheet" href="main.css"> {% endblock %} </head> |
Code Listing 48: Footer template
<footer> © nodepress 2016. All rights reserved. </footer> </html> |
Code Listing 49: Updated base template
{% include 'header.swig' %} <body> {% block content %}{% endblock %} </body> {% include 'footer.swig' %} |
Another recommended scenario in which to use template partials would be within loops. The code will be much easier to understand and maintain.
Code Listing 50: Template partial in loops
<table class="table table-striped"> {% for news in data.newsitems %} {% include 'news.swig' %} {% endfor %} </table> |
Macros
A macro is a function in Swig that returns a template or HTML string. This is used to avoid code that is repeated over and over again and reduce it to one function call. For example, the following is a macro to show a news item.
Code Listing 51: Macro in template
{% macro showNews(news) %} <div class="post"> <h2> <a href="{{ news.url() }}">{{ news.title }}</a> </h2> <p>Posted {% if news.publishedDate %} <br>on {{ news._.publishedDate.format("MMMM Do, YYYY") }} {% endif %} </p> </div> {% endmacro %} |
Now, to quickly display a news item in any template, call your macro using the following.
Code Listing 52: Invoke macro
{{ showNews(newsObj) }} |
In this chapter, you learned about Swig, the powerful and flexible templating option for Node.js. There are other similar frameworks, and I would suggest you play around with a few to find the one that best fits your style of coding.