A template engine is a library or a framework that uses some rules/languages to interpret data and render views. In the case of web applications, views are HTML pages (or parts of them), but they can be JSON or XML files, or GUIs in the case of desktop programs. For those of you familiar with the model–view–controller concept, templates belong to the view.
In web apps, it's beneficial to use templates because we can generate an infinite number of pages dynamically with a single template! Another side benefit is when we need to change something; we can do it in one place only.
If we go back to the diagrams in the previous chapter (traditional vs. REST API approaches), we can deduce that templates can be compiled into HTML either server-side (traditional approach) or client-side (REST API approach). No matter which approach we take, the syntax of the libraries themselves remains intact.
In this chapter we cover the following:
- Pug syntax and features
- Pug standalone usage
- Handlebars syntax
- Handlebars standalone usage
- Pug and Handlebars usage in Express.js
- Project: adding Pug templates to Blog
Pug is a Node.js brother of Haml, in the sense that it uses whitespace and indentation as part of its language. As with a real pugs, this Pug can either be cute and friendly or can chew your butt off if you don't know how to use it. Therefore, we need to be careful to follow the proper syntax.
You can follow the Pug syntax examples in this section online, at the official web site's demo page (https://pugjs.org/api/reference.html) or by writing standalone Node.js scripts (examples are presented in the section “Pug Standalone Usage,” which appears later in this chapter).
Any text at the beginning of a line—by default—is interpreted as an HTML tag. The main advantage of Pug is that this text renders both closing and opening tags for the HTML element, as well as the <></>
symbols. Therefore, we save many keystrokes as developers writing in Pug! It's very important to type as little as possible. It will allow you not only to avoid silly typos but also to avoid having a repetitive stress injury done to your hands.
The text following a tag and a space (e.g., tag <text>
) is parsed as the inner HTML (i.e., content inside the element). For example, if we have the following Pug code with h1
and p
tags (elements). After the tag/element name, there's a space, then text:
body
div
h1 Practical Node.js
p The only book most people will ever need.
div
footer © Apress
The text after the first space becomes the content of those elements.The output of the template above will be <h1>
, <p>
, and other elements with the corresponding text inside of them:
<body>
<div>
<h1> Practical Node.js </h1>
<p> The only book most people will ever need. </p>
</div>
<div>
<footer> © Apress </footer>
</div>
</body>
The preceding code above is an HTML <body>
element. How about some more interesting HTML elements to generate the entire web page with the <head>
and other tags? Sure. You can do that too (eat that, React!). Here's an example of how to define DOCTYPE
, and element attributes such as lang
(for html
), type
(for script
), and id
and class
for div
:
doctype html
html(lang="en")
head
title Why JavaScript is Awesome | CodingFear: programming and human circumstances
script(type='text/javascript').
const a = 1
console.log(`Some JavaScript code here and the value of a is ${a}`)
body
h1 Why JavaScript is Awesome
div(id="container", class="col")
p You are amazing
p Get on it!
p.
JavaScript is fun. Almost everything
can be written in JavaScript. It is huge.
The output will contain attributes defined with parenthesis (key=value)
, such as id
, class
, type
and lang
. The output will also have JavaScript code that will be executed when the page is viewed in the browsers. The output will also have text in <p>
. A dot .
after the element name or parenthesis allows to define text on a new line and to use multiple lines as show in the last p
element.
The #
means it's an id
attribute, whereas the dot in the element means a class
attribute. Thus, omitting the element name like we did with the #container.col
will produce <div>
with the id container
and class col
. See for yourself:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Why JavaScript is Awesome | CodingFear: programming and human circumstances</title>
<script type="text/javascript">
const a = 1
console.log(`Some JavaScript code here and the value of a is ${a}`)
</script>
</head>
<body>
<h1>Why JavaScript is Awesome</h1>
<div class="col" id="container">
<p>You are amazing</p>
<p>Get on it!</p>
<p>
JavaScript is fun. Almost everything
can be written in JavaScript. It is huge.
</p>
</div>
</body>
</html>
Check out the code bellow without the tag/element name... nothing?! Huh. You see, when you omit the tag name like in the #contaner.col
, Pug will use div
, so the code below:
#container.col
p You are amazing
p Get on it!
becomes a <div
with the id container
and the class col
:
<div class="col" id="container">
<p>You are amazing</p>
<p>Get on it!</p>
</div>
You can play with these example using the code, which is in the code/ch4/pug-example/pug-method-example.js
. The code uses the pug
npm modules and its render()
method. For example, this is a Node file and it generates HTML:
const pug = require('pug')
const pugTemplate = `body
div
h1 Practical Node.js
p The only book most people will ever need.
div
footer © Apress`
const htmlString = pug.render(pugTemplate, {pretty: true})
console.log(htmlString)
So far, we've just outputted some pre-programmed code that is not modifiable by the application. This is static and not much fun. Most of the time we want to have some dynamism in the form of variables that will allow the application itself to modify the output, that is HTML.
Pug, Express and Node developers call the data that is passed to the Pug template local. This data is available within the template as a regular Node variable. To output the value of a local/variable, use =
. Let's look at some examples to make the lesson stick.
This Pug code prints values of variables title
and body
using the equal =
symbol:
h1= title
p= body
The variables title
and body
are called locals. They are the data to supply to the Pug template to generate HTML. The data comes in the form of an object. It must have properties and the properties must be the same as the names of the locals that you want to use, i.e., title
and body
:
{
title: "Express.js Guide",
body: "The Comprehensive Book on Express.js"
}
The HTML output generated from the Pug template and locals shows the values of the variables title
and body
:
<h1>Express.js Guide</h1>
<p>The Comprehensive Book on Express.js</p>
What about HTML element attributes such as href
or class
? You saw some of these, already but, let's dive deeper.
Attributes are added by putting them into parentheses right after the tag name. They follow the tagName(name=value)
format. In addition, multiple attributes need to be separated by a comma. For example, this Pug code has various attributes on div
, a
, and other elements:
div(id="content", class="main")
a(href="https://expressjsguide.com", title="Express.js Guide", target="_blank") Express.js Guide
form(action="/login")
button(type="submit", value="save")
div(class="hero-unit") Lean Node.js!
The preceding Pug template code above turns into the following HTML with attributes rendered inside of the HTML elements:
<div class="main" id="content"><a href="https://expressjsguide.com" title="Express.js Guide" target="_blank">Express.js Guide</a>
<form action="/login"><button type="submit" value="save"></button></form>
<div class="hero-unit">Lean Node.js!</div>
</div>
Yes, the <a>
element is right on the same line as <div>
. It's a mystery to me too.
Sometimes, the value of an attribute needs to be dynamic. It's more fun this way! In this case, just use the variable name without double quotes as the value of the attribute.
Another trick is to use the pipe, or |
. It allows us to define text DOM node. In other words, the line with the pipe becomes raw text. This is useful when defining multiple lines of text.
An example uses attribute values from locals/variables and defines the <input>
content text yes/no
on a new line:
a(href=url, data-active=isActive)
label
input(type="checkbox", checked=isChecked)
| yes / no
If the template above is provided with these locals, some of which are boolean and url
is a string:
{
url: "/logout",
isActive: true,
isChecked: false
}
then they both—meaning template and locals data—produce the following HTML output, which doesn't have checked
(false) and has yes/no
as text.
<a href="/logout" data-active="data-active"></a>
<label>
<input type="checkbox"/> yes / no
</label>
Note that the attribute with the value false
is omitted from the HTML output. However, when no value is passed, true
is assumed. For example, this is a Pug template with boolean attributes checked
:
input(type='radio', checked)
input(type='radio', checked=true)
input(type='radio', checked=false)
The attributes checked will be omitted when the value is false. When the value is true in Pug, then the value is "checked" in HTML. This is the resulting HTML:
<input type="radio" checked="checked"/>
<input type="radio" checked="checked"/>
<input type="radio"/>
Next we will study literals.
For convenience, we can write classes and ids right after tag names. For example, we can then apply lead
and center
classes to a paragraph, and create a div
element with the side-bar
id and pull-right
class (again, the pipe signifies an inner text):
div#content
p.lead.center
| webapplog: where code lives
#side-bar.pull-right
span.contact.span4
a(href="/contact") contact us
Note that if the tag name is omitted, div
is used instead. See the <div id="side-bar" class="pull-right"></div>
in the generated HTML below. This <div>
was created by Pug when no element name was provided, and only a an id of side-bar
:
<div id="content">
<p class="lead center">
webapplog: where code lives
<div id="side-bar" class="pull-right"></div>
<span class="contact span4">
<a href="/contact">contact us</a>
</span>
</p>
</div>
Pug is all about eloquence, compactness, and convenience. <div>
elements are very popular for layouts. Therefore, Pug defaults to rendering <div>
when there's no element name and there is a class or an id. Nice!
Our next feature is rendering text.
Outputting raw text is done via |
. For example, this template produces one <div>
with inner text:
div
| Pug is a template engine.
| It can be used in Node.js and in the browser JavaScript.
If you move the |
to the left, then the result will be one empty <div>
with sibling text nodes.
To avoid using pipes on multiple lines, there's a dot .
syntax. Thus, if you want to render all nested (indented) lines as inner text, then use dot .
right after the element name. For example, this template is analogous to the preceding code in that it produces one <div>
with inner text of two lines:
div.
Pug is a template engine.
It can be used in Node.js and in the browser JavaScript.
The result in both cases is HTML with <div>
and text inside:
<div>Pug is a template engine. It can be used in Node.js and in the browser JavaScript.</div>
The dot comes in handy for writing JavaScript that executes at run time, which is the topic of the next section.
Sometimes, developers want to write chunks of content for script
or style
tags in the HTML! This is possible with a dot.
For example, we can write inline front-end JavaScript like this:
script.
console.log('Hello Pug!')
setTimeout(function(){
window.location.href='https://rpjs.co'
},200))
console.log('Good bye!')
And the HTML output will have the <script>
tag with all of our code:
<script>
console.log('Hello Pug!')
setTimeout(function() {
window.location.href = 'https://rpjs.co'
}, 200))
console.log('Good bye!')
</script>
Did you like this little trick with the dot and JavaScript? Of course! But this code is not executed until the page loads. In other words, it's runtime but not compile.
Contrary to the previous example, if we want to use any JavaScript at template compilation time—in other words, to write executable JavaScript code that manipulates the output of the Pug (i.e., HTML)—we can use the -
, =
, or !=
symbols. This may come in handy when we output HTML elements and inject JavaScript.
Obviously, these types of things should be done carefully to avoid cross-site scripting (XSS) attacks. For example, if we want to define an array and output <>
symbols, we can use !=
.
- var arr = ['<a>','<b>','<c>']
ul
- for (var i = 0; i< arr.length; i++)
li
span= i
span!="unescaped: " + arr[i] + " vs. "
span= "escaped: " + arr[i]
The Pug above produces the following HTML which does NOT include JavaScript but the result of the JavaScript code, because this JS is a compile-time JS for Pug. This is not run-time JS for a browser as was defined with script.
earlier. The resulting HTML has <ul>
and three <li>
elements:
<ul>
<li><span>0</span><span>unescaped: <a> vs. </span><span>escaped: <a></span></li>
<li><span>1</span><span>unescaped: <b> vs. </span><span>escaped: <b> </span></li>
<li><span>2</span><span>unescaped: <c> vs. </span><span>escaped: <c> </span></li>
</ul>
Tip One of the main differences between Pug and Handlebars is that the former allows pretty much any JavaScript in its code, whereas the latter restricts programmers to only a handful of built-in and custom-registered helpers.
When it comes to comments, we have a choice to render/output them into HTML or not. To render/output them into HTML, use JavaScript style //
; to not render them, use //-
. For example, here are two comments:
// content goes here
p Node.js is a non-blocking I/O for scalable apps.
//- @todo change this to a class
p(id="footer") Copyright 2014 Azat
The Pug above with comments outputs the HTML style comments with //
but hides them with //-
. Thus, the resulting HTML has only content goes here
without @todo change this to a class
:
<!-- content goes here-->
<p>Node.js is a non-blocking I/O for scalable apps.</p>
<p id="footer">Copyright 2014 Azat</p>
Of course, views (i.e., templates) benefit greatly from an if/else condition. Let's cover them next.
Interestingly enough, in addition to the standard JavaScript code, where the if
statement can be used by prefixing it with -
, we can use an even shorter Pug alternative with no prefix and no parentheses. For example, this if/else works fine:
- var user = {}
- user.admin = Math.random()>0.5
if user.admin
button(class="launch") Launch Spacecraft
else
button(class="login") Log in
There's also unless
, which is equivalent to not
or !
.
Similar to conditions, iterators in Pug can be written simply with each
. For example, this is code to iterate over an array of programming languages and create paragraphs for each of them:
- var languages = ['php', 'node', 'ruby']
div
each value, index in languages
p= index + ". " + value
The HTML output with three <p>
elements is as follows:
<div>
<p>0. php</p>
<p>1. node</p>
<p>2. ruby</p>
</div>
The same iterative each
construction works with objects as well. Developers even can access a key
value. Take a look at this object with languages as keys and their importance as values:
- var languages = {'php': -1, 'node': 2, 'ruby':1}
div
each value, key in languages
p= key + ": " + value
The Pug above is compiled into the HTML output in which each iteration over the array values produces a paragraph <p>
element for each language:
<div>
<p>php: -1</p>
<p>node: 2</p>
<p>ruby: 1</p>
</div>
Next are filters!
Filters are used when there are blocks of texts written in a different language. For example, the filter for Markdown looks like this:
p
:markdown
# Practical Node.js
Note The Markdown modules still need to be installed. The marked
and markdown npm packages are often used for this. There's no need for an additional configuration; just install them in the project's local node_modules
folder.
Interpolation is mixing of strings and dynamic values from variables. That's another term that will make you look at least five (5) IQ points smarter. You are welcome.
In Pug, interpolation is achieved via the syntax with curly braces and a hashtag: #{name}
, where name
is the name of a variable. For example, to output title
in a paragraph, simply use #{title}
in the text, as in the following code:
- var title = "React Quickly: Painless web apps with React, JSX, Redux, and GraphQL"
p Read the #{title} in PDF, MOBI and EPUB
The interpolation is processed at the template compilation. Therefore, don't use interpolation in executable JavaScript, that is, JS with -
. For the -
JS, use standard ES6 string interpolation with ${name}
.
Case allows Node developers to avoid a chain of if/else conditions. You probably used something similar. In other languages, case implemented with switch
. Here's an example of the case
statement in Pug:
- var coins = Math.round(Math.random()*10)
case coins
when 0
p You have no money
when 1
p You have a coin
default
p You have #{coins} coins!
Mixins are functions that take parameters and produce some HTML. They are super cool because they allow you reuse boatloads of code if used correctly. The declaration syntax is mixin name(param, param2,...)
, and the usage is +name(data)
. For example, here I define row
and table
mixins, which I use later with real data from arrays:
mixin row(items)
tr
each item, index in items
td= item
mixin table(tableData)
table
each row, index in tableData
+row(row)
- var node = [{name: "express"}, {name: "hapi"}, {name: "derby"}]
+table(node)
- var js = [{name: "backbone"}, {name: "angular"}, {name: "ember"}]
+table(js)
The preceding Pug code, above when used in Express or elsewhere, produces the following output by "invoking" the mixins table
and row
just as a function would be invoked with arguments (bonus: developers can use table
and row
mixins over and over for other data!):
<table>
<tr>
<td>express</td>
</tr>
<tr>
<td>hapi</td>
</tr>
<tr>
<td>derby</td>
</tr>
</table>
<table>
<tr>
<td>backbone</td>
</tr>
<tr>
<td>angular</td>
</tr>
<tr>
<td>ember</td>
</tr>
</table>
include
is a way to split logic into a separate file for the purpose of reusing it across multiple files. Don't confuse this with ES6 include
. That's JavaScript, but we are talking about Pug here.
This include
is a top-to-bottom approach, meaning we dictate what to use in the file that includes another file. The file that includes is processed first (we can define locals there), and then the included file is processed (we can use earlier defined locals).
To include a Pug template, use include /path/filename
. No need for double quotes "
or single quotes '
. I like it! For example, in a layout file you can import a header:
include ./includes/header
Notice there's no need for double or single quotes for the template name and its path. And it's possible to traverse up the folder tree. This footer can be in a parent folder's includes folder:
include ../includes/footer
But, there's no way to use a dynamic value for the file and path (use a variable), because includes/partials are handled at compilation (not at runtime).
extend
is a bottom-to-top approach (as oppose to include
), in the sense that the included file commands which parts of the main file it wants to replace. The way it works is with extend filename
and block blockname
statements.
In file_a
, which is like a layout you define blocks, define block
elements with some default content:
block header
p some default text
block content
p Loading ...
block footer
p copyright
In file_b
, which is like a subview, you define what layout to use and what blocks to overwrite (and what not to, by omission).
For example, in this file_b
file, the header
and content
blocks will have new content, but footer
will stay as in file_a
. Here's the file_b
example:
extend file_a
block header
p very specific text
block content
.main-content
The bottom line is that extend
and block
implement inverted inheritance pattern.
Template engines (Pug) and web frameworks (Express) go together like ketchup and hotdogs—but not always. Template engines are not not always used with Node.js frameworks like Express.js. Sometimes, we might just want to use Pug in a standalone manner. The use cases include generating an email template, precompiling Pug before deployment, and debugging. In this section, we do the following:
- Install a Pug module
- Create our first Pug file
- Create a Node.js program that uses the Pug file
- Compare
pug.compile
,pug.render
, andpug.renderFile
To add a pug
dependency to your project, or if you're starting from scratch from an empty project folder, do the following:
- Create a
package.json
file manually or with$ npm init -y
. - Install and add
pug
topackage.json
with$ npm i pug –save
. See the results in Figure 4-1. - Create a Node file.
- Import
pug
in the Node file. - Invoke a method from
pug
module in your Node file.
Figure 4-1. Installing Pug
Tip Add {pretty: true}
to pug.render()
, as in pug.render(pugTemplate, {pretty: true})
, in order to have properly formatted, pretty HTML.
Let's say we have some Node.js script that sends an email and we need to use a template to generate HTML dynamically for the email. This is how it might look (file pug-example.pug
):
.header
h1= title
p
.body
p= body
.footer
div= By
a(href="https://twitter.com/#{author.twitter}")= author.name
ul
each tag, index in tags
li= tag
In this case, our Node.js script needs to hydrate, or populate, this template with the following data:
title
: Stringtags
: Arraybody
: Stringauthor
: String
We can extract these variables from multiple sources (databases, file systems, user input, tassology, and so on). For example, in the pug-example.js
file, we use hard-coded values for title
, author
, tags
, but pass through a command-line argument for body
using process.argv[2]
:
const pug = require('pug'),
fs = require('fs')
let data = {
title: 'Practical Node.js',
author: {
twitter: '@azatmardan',
name: 'Azat'
},
tags: ['express', 'node', 'javascript']
}
data.body = process.argv[2]
fs.readFile('pug-example.pug', 'utf-8', (error, source) => {
let template = pug.compile(source)
let html = template(data)
console.log(html)
})
In this way, when we run $ node pug-example.js 'email body'
, we get the HTML output printed in the terminal as shown in Figure 4-2.
Figure 4-2. The result of pug-example
output
The "prettified" HTML output with proper spaces and indentation that I took from the terminal looks as follows:
<div class="header">
<h1>Practical Node.js</h1>
<p></p>
</div>
<div class="body">
<p>email body</p>
</div>
<div class="footer">
<div><a href="https://twitter.com/@azatmardan">Azat</a>
</div>
<ul>
<li>express</li>
<li>node</li>
<li>javascript</li>
</ul>
</div>
In addition to pug.compile()
, the Pug API has the functions pug.render()
and pug.renderFile()
. For example, the previous file can be rewritten with pug.render()
:
fs.readFile('pug-example.pug', 'utf-8', (error, source) => {
const html = pug.render(source, data)
console.log(html)
})
Furthermore, with pug.renderFile()
, the pug-example.js
file is even more compact because it will do two things at the same time: read a file and render it:
pug.renderFile('pug-example.pug', data, (error, html) => {
console.log(html)
})
Note Pug can also be used as a command-line tool after installing it with the -g
or --global
option via npm. For more information, run pug -h
or see the official documentation (https://pug-lang.com/command-line).
To use Pug in a browser, you can use browserify
(https://github.com/substack/node-browserify) and its pugify
(https://www.npmjs.org/package/pug-browser) middleware.
Note To use the same Pug templates on front-end (browser) and server sides, I recommend jade-browser
(https://www.npmjs.org/package/jade-browser) by Storify, for which I was the maintainer for a time during my work there. jade-browser
acts as an Express.js middleware, and exposes server-side templates to the browser along with helpful utility functions.
The Handlebars library is another template engine. It inherits from Mustache and, for the most part, is compatible with Mustache's syntax. However, Handlebars adds more features. In other words, Handlebars is a superset of Mustache.
Unlike Pug, Handlebars by design was made so that developers can't write a lot of JavaScript logic inside the templates. This helps to keep templates lean and related strictly to the representation of the data (no business logic).
Another drastic difference between Pug and Handlebars is that the latter requires full HTML code (<
, >
, closing </>
tags, and so on). For this reason it could care less about whitespace and indentation, which means that it's easy to copypasta your existing HTML and make it Handlebars, and that developers have to type more code when writing templates from scratch.
A Handlebars expression is {{
, some content, followed by }}
, hence the name of the library (see the resemblance to handlebars on a bicycle?). For example, this Handlebars code:
<h1>{{title}}</h1>
<p>{{body}}</p>
with data that has title
and body
properties:
{
title: "Express.js Guide",
body: "The Comprehensive Book on Express.js"
}
renders the elements with values from title
and body
:
<h1>Express.js Guide</h1>
<p>The Comprehensive Book on Express.js</p>
In Handlebars, each
is one of the built-in helpers; it allows you to iterate through objects and arrays. Inside the block, we can use @key
for the former (objects), and @index
for the later (arrays). In addition, each item is referred to as this
. When an item is an object itself, this
can be omitted, and just the property name is used to reference the value of that property.
The following are examples of the each
helper block in Handlebars:
<div>
{{#each languages}}
<p>{{@index}}. {{this}}</p>
{{/each}}
</div>
The template above is supplied with this data that has array of strings:
{languages: ['php', 'node', 'ruby']}
and output this HTML upon compilation, which has <p>
for each array element:
<div>
<p>0. php</p>
<p>1. node</p>
<p>2. ruby</p>
</div>
By default, Handlebars escapes values. If you don't want Handlebars to escape a value, use triple curly braces: {{{
and }}}
.
As data, let's use this object that has an array with some HTML tags (angle braces):
{
arr: [
'<a>a</a>',
'<i>italic</i>',
'<strong>bold</strong>'
]
}
To apply this Handlebars template to our data above (i.e., hydration) use an iterator each
with {{{this}}}
for the unescaped value of an individual array item, which is HTML and hence needs to be unescaped:
<ul>
{{#each arr}}
<li>
<span>{{@index}}</span>
<span>unescaped: {{{this}}} vs. </span>
<span>escaped: {{this}}</span>
</li>
{{/each}}
</ul>
The hydrated template produces the following HTML by printing array indices ({{@index}}
), unescaped HTML ({{{this}}}
) and escaped HTML ({{this
):
<ul>
<li>
<span>0</span>
<span>unescaped: <a>a</a> vs. </span>
<span>escaped: <a&gt;a</a></span>
</li>
<li>
<span>1</span>
<span>unescaped: <i>italic</i> vs. </span>
<span>escaped: <i>italic</i></span>
</li>
<li>
<span>2</span>
<span>unescaped: <strong>bold</strong> vs. </span>
<span>escaped: <strong>bold</strong></span>
</li>
</ul>
if
is another built-in helper invoked via #
. For example, this Handlebars code uses an if/else condition to check for a user.admin
value (if a user is an administrator):
{{#if user.admin}}
<button class="launch"> Launch Spacecraft</button>
{{else}}
<button class="login"> Log in</button>
{{/if}}
The template is populated with data that will make the if/else condition true:
{
user: {
admin: true
}
}
Everything turns into this HTML output, which has a launch
element rendered due to the value of user.admin
being true:
<button class="launch">Launch Spacecraft</button>
To inverse an if not ... (if ! ...)
statement (convert negative to positive), we can harness the unless
built-in helper block. For example, the previous code snippet can be rewritten with unless
.
The Handlebars code that checks the truthiness of the admin flag (property user.admin
). If the value is true, then else will be applied. Notice the change in Log in and Launch Spacecraft. They are flipped now compared to if/else:
{{#unless user.admin}}
<button class="login"> Log in</button>
{{else}}
<button class="launch">Launch Spacecraft</button>
{{/unless}}
We supply our template with the data that makes the user an administrator:
{
user: {
admin: true
}
}
The HTML output renders the launch button, which is available only to admins because this button was in else
, we used unless
, and the value is true.
<button class="launch">Launch Spacecraft</button>
In case there's an object with nested properties, and there are a lot of them, it's possible to use with
to pass the context.
We have this Handlebars code that is handling a user's contact and address information:
{{#with user}}
<p>{{name}}</p>
{{#with contact}}
<span>Twitter: @{{twitter}}</span>
{{/with}}
<span>Address: {{address.city}},
{{/with}}
{{user.address.state}}</span>
Then we merge the template with this data. Notice the properties' names are the same as in the Handlebars template, there's only one reference to the user
object:
{user: {
contact: {
email: '[email protected]',
twitter: 'azat_co'
},
address: {
city: 'San Francisco',
state: 'California'
},
name: 'Azat'
}}
The snippets above, when compiled, produce HTML that prints values using the object name for every property:
<p>Azat</p>
<span>Twitter: @azatmardan</span>
<span>Address: San Francisco, California
</span>
To output comments, use regular HTML <!--
and -->
. To hide comments in the final output, use {{!
and }}
or {{!--
and --}}
. For example, the following code below has two types of comments:
<!-- content goes here -->
<p>Node.js is a non-blocking I/O for scalable apps.</p>
{{! @todo change this to a class}}
{{!-- add the example on {{#if}} --}}
<p id="footer">Copyright 2018 Azat</p>
The preceding code outputs the comments with <!-- ... -->
but omits comments with {{! ... }}
so the result is this:
<!-- content goes here -->
<p>Node.js is a non-blocking I/O for scalable apps.</p>
<p id="footer">Copyright 2014 Azat</p>
Custom Handlebars helpers are similar to built-in helper blocks and Pug mixins. To use custom helpers, we need to create them as a JavaScript function and register them with the Handlebars instance.
For example, let's assume we have a custom helper table
which we'll register (i.e., define) later in the JavaScript/Node.js code, then this Handlebars template uses our table
:
{{table node}}
Here goes the JavaScript/Node.js that registers or tells the Handlebars compiler what to do when it encounters the custom table
function (i.e., print an HTML table out of the provided array):
handlebars.registerHelper('table', (data) => {
let str = '<table>'
for (let i = 0; i < data.length; i++ ) {
str += '<tr>'
for (var key in data[i]) {
str += '<td>' + data[i][key] + '</td>'
}
str += '</tr>'
}
str += '</table>'
return new handlebars.SafeString (str)
})
The following is our array for the table data. It has an array of object. Each object has name and URL:
[
{name: 'express', url: 'https://expressjs.com/'},
{name: 'hapi', url: 'https://spumko.github.io/'},
{name: 'compound', url: 'https://compoundjs.com/'},
{name: 'derby', url: 'https://derbyjs.com/'}
]
The resulting HTML from iterating over the name and URL objects within the table function looks like this:
<table>
<tr>
<td>express</td>
<td>https://expressjs.com/</td>
</tr>
<tr>
<td>hapi</td>
<td>https://spumko.github.io/</td>
</tr>
<tr>
<td>compound</td>
<td>https://compoundjs.com/</td>
</tr>
<tr>
<td>derby</td>
<td>https://derbyjs.com/</td>
</tr>
</table>
Thus, helpers are good for reusing the code. Another way to reuse code is includes or partials.
In Handlebars, includes or partials templates are interpreted by the {{> partial_name}}
expression. Partials are akin to helpers and are registered with Handlebars.registerPartial(name, source)
, where name
is a string and source
is a Handlebars template code for the partial (JS/Node code, not template):
Handlebars.registerPartial('myPartial', '{{name}}')
Calling the partial is done with the following syntax (written in the Handlebars template, not JS/Node code):
For more on includes and partials, see the documentation at https://handlebarsjs.com/partials.html.
Developers can install Handlebars via npm with $ npm install handlebars
or $ npm install handlebars --save
, assuming either node_modules
or package.json
is in the current working directory (see the results of a sample installation in Figure 4-3).
Figure 4-3. Installing Handlebars
Note Handlebars can be installed via npm as a command-line tool with the -g
or --global
options. For more information on how to use Handlebars in this mode, refer to the $ handlebar
command or the official documentation (https://github.com/wycats/handlebars.js/#usage-1).
Here's an example of standalone Node.js Handlebars usage from handlebars-example.js
in which we import modules, then define data
object (with book info), and then register a few helpers and generate HTML:
const handlebars = require('handlebars')
const fs = require('fs')
const path = require('path')
const data = {
title: 'practical node.js',
author: '@azatmardan',
tags: ['express', 'node', 'javascript']
}
data.body = process.argv[2]
const filePath = path.join(__dirname,
'handlebars-example.html')
data.tableData = [
{name: 'express', url: 'https://expressjs.com/'},
{name: 'hapi', url: 'https://spumko.github.io/'},
{name: 'compound', url: 'https://compoundjs.com/'},
{name: 'derby', url: 'https://derbyjs.com/'}
]
fs.readFile(filePath, 'utf-8', (error, source) => {
if (error) return console.error(error)
// Register helper to generate table HTML from data (array)
handlebars.registerHelper('table', (data) => {
let str = '<table>'
for (let i = 0; i < data.length; i++) {
str += '<tr>'
for (var key in data[i]) {
str += '<td>' + data[i][key] + '</td>'
}
str += '</tr>'
}
str += '</table>'
return new handlebars.SafeString(str)
})
// Register helper to create capitalize a string
handlebars.registerHelper('custom_title', (title) => {
let words = title.split(' ')
for (let i = 0; i < words.length; i++) {
if (words[i].length > 4) {
words[i] = words[i][0].toUpperCase() + words[i].substr(1)
}
}
title = words.join(' ')
return title
})
// Compile the template and hydrate it with data to generate HTML
const template = handlebars.compile(source)
const html = template(data)
console.log(html)
})
And the handlebars-example.html
template file that uses custom_title
helper has the following content that calls the helper and outputs some other properties:
<div class="header">
<h1>{{custom_title title}}</h1>
</div>
<div class="body">
<p>{{body}}</p>
</div>
<div class="footer">
<div><a href="https://twitter.com/{{author.twitter}}">{{autor.name}}</a>
</div>
<ul>
{{#each tags}}
<li>{{this}}</li>
{{/each}}
</ul>
</div>
To produce this HTML when we run $ node handlebars-example.js 'email body'
, use the following:
<div class="header">
<h1>Practical Node.js</h1>
</div>
<div class="body">
<p>email body</p>
</div>
<div class="footer">
<div><a href="https://twitter.com/"></a>
</div>
<ul>
<li>express</li>
<li>node</li>
<li>javascript</li>
</ul>
</div>
To use Handlebars in the browser, download the library in a straightforward manner from the official web site (https://handlebarsjs.com) and include it in your pages. Alternatively, it's possible to use just the runtime version from the same web site (which is lighter in size) with precompiled templates. Templates can be precompiled with the Handlebars command-line tool.
By default, Express.js uses either a template extension provided to the response.render
(or res.render
) method or the default extension set by the view engine
setting, to invoke the require
and __express
methods on the template library. In other words, for Express.js to utilize a template engine library out of the box, that library needs to have the __express
method.
When the template engine library doesn't provide the __express
method, or a similar one with (path, options, callback)
parameters, it's recommended that you use Consolidate.js (https://github.com/visionmedia/consolidate.js/).
Let's look at a quick example of an abstraction library for templates called Consolidate.js. In this example, I use the template engine Swig. I picked this template engine because most likely you never heard of it and this makes it a good illustration for an abstraction library like Consolidate. So Swig comes from the consolidate
module. I connected it to express with the app.engine('html', cons.swig)
statement. See the full server implementation that renders Swig templates:
const express = require('express')
const cons = require('consolidate')
const path = require('path')
let app = express()
app.engine('html', cons.swig)
app.set('view engine', 'html')
app.set('views', path.join(__dirname, 'templates'))
var platforms = [
{ name: 'node' },
{ name: 'ruby' },
{ name: 'python' }
]
app.get('/', (req, res) => {
res.render('index', {
title: 'Consolidate This'
})
})
app.get('/platforms', (req, res) => {
res.render('platforms', {
title: 'Platforms',
platforms: platforms
})
})
app.listen(3000, () => {
console.log('Express server listening on port 3000')
})
As usual, the source code is in the GitHub repository, and the snippet is in the code/ch4/consolidate
folder.
For more information on how to configure Express.js settings and use Consolidate.js, refer to the still-up-to-date book on Express.js version 4—Pro Express.js (Apress, 2014), which is available on all major book stores, and of course at https://amzn.to/2tlSwNw.
Pug is compatible with Express.js out of the box (in fact, it's the default choice), so to use Pug with Express.js, you just need to install a template engine module (pug
) (https://www.npmjs.org/package/pug) and provide an extension to Express.js via the view engine
setting.
For example, in the main Express server file we set the view engine
setting as pug
to let Express know which library to use for templates:
app.set('view engine', 'pug')
Of course, developers need to install the pug
npm module into their project so the pug package is stored locally in node_modules
. Express will use the name pug
provided to view engine
to import the pug
package and also use the pug
as a template files extension in the views
folder (views
is the default name).
Note If you use the $ express <app_name>
command-line tool, you can add the option for engine support, i.e., the –e
option for EJS and –H for Hogan. This will add EJS or Hogan automatically to your new project. Without either of these options, the express-generator
(versions 4.0.0-4.2.0) will use Pug.
In the route file, we can call the template—for example, views/page.pug
(the views
folder name is another Express.js default, which can be overwritten with the view
setting):
app.get('/page', (req, res, next) => {
//get the data dynamically
res.render('page', data)
})
If we don't specify the view engine
setting, then the extension must be passed explicitly to res.render()
as a first argument, such as:
res.render('page.pug', data)
Next, let's cover the Express usage for Handlebars.
Contrary to Pug, the Handlebars library from https://handlebarsjs.com doesn't come with the __express
method, but there are a few options to make Handlebars work with Express.js:).
consolidate
(https://github.com/tj/consolidate.js): A Swiss-army knife of Express.js template engine libraries (shown in one of the previous sections)hbs
(https://github.com/pillarjs/hbs): Wrapper library for Handlebarsexpress-handlebarss
(https://github.com/ericf/express-handlebars): A module to use Handlebars with Express
Here's how we can use the hbs
approach (extension hbs
). Somewhere in the configuration section of the main Express file (file that we launch with the $ node
command), write the following statements:
// Imports
app.set('view engine', 'hbs')
// Middleware
Or, if another extension is preferable, such as html
, we see the following:
app.set('view engine', 'html')
pp.engine('html', require('hbs').__express)
The express-handlebars
approach usage is as follows:
const exphbs = require('express-handlebars')
app.engine('handlebars', exphbs({defaultLayout: 'main'}))
app.set('view engine', 'handlebars')
Good. Now we can put our knowledge to practice.
Lastly, we can continue with Blog. In this section we add main pages using Pug, plus we add a layout and some partials:
layout.pug
: Global app-wide templateindex.pug
: Home page with the list of postsarticle.pug
: Individual article pagelogin.pug
: Page with a login formpost.pug
: Page for adding a new articleadmin.pug
: Page to administer articles after logging in
Because the templates in this mini-project require data, we'll skip the demo until Chapter 5, where we'll plug in the MongoDB database. So the source code for the Pug templates is exactly the same as in the code/ch5
folder of the GitHub repository azat-co/practicalnode
: https://github.com/azat-co/practicalnode. Feel free to copy it from there or follow the instructions to implement listed below in this section.
Let's open the project where we left off in the previous chapter and add layout.pug
with the document type statement:
doctype html
Now we can add the main tags of the page:
html
head
The title of the each page is provided from the appTitle
variable (a.k.a., local):
title= appTitle
Then, in the head
tag, we list all the front-end assets that we need app-wide (on each page):
script(type="text/javascript", src="js/jquery-2.0.3.min.js")
link(rel="stylesheet", href="/css/bootstrap-3.0.2/css/bootstrap.min.css")
link(rel="stylesheet", href="/css/bootstrap-3.0.2/css/bootstrap-theme.min.css")
link(rel="stylesheet", href="/css/style.css")
script(type="text/javascript", src="/css/bootstrap-3.0.2/js/bootstrap.min.js")
script(type="text/javascript", src="/js/blog.js")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
The main content lives in body
, which has the same level indentation as head
:
body
Inside the body, we write an id and some classes for the styles that we'll add later:
#wrap
.container
The appTitle
value is printed dynamically, but the p.lead
element only has text:
h1.page-header= appTitle
p.lead Welcome to example from Express.js Experience by
a(href="https://twitter.com/azat_co") @azatmardan
|. Please enjoy.
The block
sections can be overwritten by the children templates (templates that extend this file):
block page
block header
div
Menu is a partial (i.e., an include) that is stored in the views/includes
folder. Note the absence of quotation marks:
include includes/menu
In this block named alert
, we can display messages for users, so let's use special alerty classes on a div
(the indentation is preserved to show hierarchy):
block alert
div.alert.alert-warning.hidden
Main content goes in this block. It is empty now because other template will define it:
.content
block content
Lastly, the footer block with div
with the container
class and with p
with text and a link (link is wrapped in text) looks as follows:
block footer
footer
.container
p
| Copyright © 2018 | Issues? Submit to
a(href="https://github.com/azat-co/blog-express/issues") GitHub
| .
To give you a full picture as well as preserve proper indentation (which is PARAMOUNT in Pug), the full code of layout.pug
is as follows:
doctype html
html
head
title= appTitle
script(type="text/javascript", src="js/jquery-2.0.3.min.js")
link(rel="stylesheet", href="/css/bootstrap-3.0.2/css/bootstrap.min.css")
link(rel="stylesheet", href="/css/bootstrap-3.0.2/css/bootstrap-theme.min.css")
link(rel="stylesheet", href="/css/style.css")
script(type="text/javascript", src="/css/bootstrap-3.0.2/js/bootstrap.min.js")
script(type="text/javascript", src="/js/blog.js")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
body
#wrap
.container
h1.page-header= appTitle
p.lead Welcome to example from Express.js Experience by
a(href="https://twitter.com/azat_co") @azatmardan
|. Please enjoy.
block page
block header
div
include includes/menu
block alert
div.alert.alert-warning.hidden
.content
block content
block footer
footer
.container
p
| Copyright © 2014 | Issues? Submit to
a(href="https://github.com/azat-co/blog-express/issues") GitHub
| .
Next is the home page.
Now, we can look at the home page template index.pug
that extends layout.pug
. Remember the syntax? It's extends name
:
extends layout
Because we can overwrite some blocks, we set the menu
variable to index
, so the menu include (i.e., menu.pug
) can determine which tab to show as active:
block page
- var menu = 'index'
Of course, we need to overwrite the content
block. Ergo, the main content with the list of articles that comes from locals
iterates over the blog posts (articles). Each article link has a title and, needless to say, a URL that is formed by the article.slug
value. When there are no posts/articles, then we show a message that nothing has been published yet. The code is as follows:
block content
if (articles.length === 0)
| There's no published content yet.
a(href="/login") Log in
| to post and publish.
else
each article, index in articles
div
h2
a(href="/articles/#{article.slug}")= article.title
For your reference and to show the ease of comprehension in Pug's style, the full code of index.pug
is as follows. You can see extends
and two block overwrites (of layout
):
extends layout
block page
- var menu = 'index'
block content
if (articles.length === 0)
| There's no published content yet.
a(href="/login") Log in
| to post and publish.
else
each article, index in articles
div
h2
a(href="/articles/#{article.slug}")= article.title
Figure 4-4 shows how the home page looks after adding style sheets.
Figure 4-4. The home page of Blog shows menu and the titles of the published articles
Phew. Next is the page for the actual blog posts/articles.
The individual article page (Figure 4-5) is relatively unsophisticated because most of the elements are abstracted into layout.pug
. We only have extends
and then overwrite the content
block without the article title (h1
heading) and article's text (p
for paragraph).
extends layout
block content
p
h1= title
p= text
This is the awesomeness which we receive for free thanks to Twitter Bootstrap and h1
and p
elements. You can clearly see that even despite defining only h1
and p
, the webpage /articles/node-fundamentals
has a page title menu and the footer. That's due to the inheritance, extends, and layout.pug
.
Figure 4-5. The article page
Did you notice that "Log in" link? Let's implement the login page next.
Similarly to article.pug
, the login page uses login.pug
, which contains... not much! Only a form and a button with some minimal Twitter Bootstrap classes/markup.
So as with article.pug
, we extend layout and overwrite two blocks—one for the active menu value and the other for the content, which is the main part of the page. This main part has guess what? A LOGIN FORM! This is file login.pug
:
extends layout
block page
- var menu = 'login'
block content
.col-md-4.col-md-offset-4
h2 Log in
div= error
div
form(action="/login", method="POST")
p
input.form-control(name="email", type="text", placeholder="[email protected]")
p
input.form-control(name="password", type="password", placeholder="***")
p
button.btn.btn-lg.btn-primary.btn-block(type="submit") Log in
Again, thanks to Twitter Bootstrap, our page looks stellar. It has a menu because of extends
and layout.pug
. Figure 4-6 shows how the login page looks.
Figure 4-6. The login page
But how to create a new article? Easy! By posting its title and text.
The post page (Figure 4-7) has another form and it also extends layout.pug
. This time, the form contains a text area element that will become the main text of the article. In addition to the article text, there are title
, and the URL segment (or path) to the article which is called slug
🐌.
extends layout
block page
- var menu = 'post'
block content
h2 Post an Article
div= error
div.col-md-8
form(action="/post", method="POST", role="form")
div.form-group
label(for="title") Title
input#title.form-control(name="title", type="text", placeholder="JavaScript is good")
div.form-group
label(for="slug") Slug
input#slug.form-control(name="slug", type="text", placeholder="js-good")
span.help-block This string will be used in the URL.
div.form-group
label(for="text") Text
textarea#text.form-control(rows="5", name="text", placeholder="Text")
p
button.btn.btn-primary(type="submit") Save
To give you some visual of the Pug of post.pug
, take a look at the page for posting new articles. The action attribute of <form>
will allow browsers to send the data to the backend and then Express will take care of it by processing, and our Node code will save it to the database.
Figure 4-7. The post page
If a valid administrator user is logged in, then we want to show an admin interface. See the Admin link in the menu? Let's implement the admin page to which this menu link leads to.
The admin page (Figure 4-8) has a loop of articles just like the home page, but in addition to just showing articles, we can include a front-end script (js/admin.js
) specific to this page. This script will do some AJAX-y calls to publish and unpublish articles. These functions will be available only to admins. Of course we will need an server-side validation on the backend later. Don't trust only the front-end validation or authorization!
So the admin.pug
file starts with the layout extension and has content overwrite, in which there's a table of articles. In each row of the table, we use glyphicon
to show a fancy icon for pause or play
extends layout
block page
- var menu = 'admin'
block content
div.admin
if (articles.length === 0 )
p
| Nothing to display. Add a new
a(href="/post") article
|.
else
table.table.table-stripped
thead
tr
th(colspan="2") Actions
th Post Title
tbody
each article, index in articles
tr(data-id=`${article._id}`, class=(!article.published)?'unpublished':'')
td.action
button.btn.btn-danger.btn-sm.remove(type="button")
span.glyphicon.glyphicon-remove(title="Remove")
td.action
button.btn.btn-default.btn-sm.publish(type="button")
span.glyphicon(class=(article.published) ? "glyphicon-pause" : "glyphicon-play", title=(article.published) ? "Unpublish" : "Publish")
td= article.title
script(type="text/javascript", src="js/admin.js")
Please notice that we use ES6 string template (or interpolation) to print article ids as attributes data-id
(indentation was removed):
tr(data-id=`${article._id}`, class=(!article.published) ? 'unpublished':'')
And a conditional (ternary) operator (https://github.com/donpark/hbs) is used for classes and title attributes. Remember, it's JavaScript! (Indentation has was removed for better viewing.)
span.glyphicon(class=(article.published) ? "glyphicon-pause" : "glyphicon-play", title=(article.published) ? "Unpublish" : "Publish")
The result is a beautiful admin page (Okay, enough with sarcasm and saying Twitter Bootstrap is stellar, pretty or cute. It's not... but compared to standard HTML, which puts me to sleep, Twitter Bootstrap style is a HUGE improvement.) It has functionality to publish and unpublish articles.
Figure 4-8. The admin page shows the list of published and draft articles
In this chapter, you learned about the Pug and Handlebars templates (variables, iterations, condition, partials, unescaping, and so forth), and how to use them in a standalone Node.js script or within Express.js. In addition, the main pages for Blog were created using Pug.
In the next chapter, we'll learn how to extract the data from a database and save new data to it. You'll become familiar with MongoDB. Onwards.