Views and Templates
Views
First, we cannot keep using res.send
to send a response. Ultimately, we'll want to send HTML files back to the client. It would be much more efficient to store them in files. Let's make a folder, /public
, and create an index.html
page inside.
<!DOCTYPE html>
<html>
<head>
<title>Testing a View</title>
</head>
<body>
<h1>Hello world!</h1>
</body>
</html>
Let's modify the index.js
to send this file via .sendFile
. In order to use this function, we also need to add where .sendFile
can find the views.
index.js
const express = require('express');
const path = require('path');
const app = express();
// this sets a static directory for 'public' folder
app.use(express.static(path.join(__dirname, 'public')));
app.get('/', function(req, res) {
// use sendFile to render the index page
res.sendFile('index.html');
});
app.listen(3000);
Templating with Handlebar
The downside to this method is that we are only sending HTML files, and what if we want to customize what's on the page? On the front-end, we could manipulate the page using jQuery. But on the back-end, we can inject values into the HTML using template engines. So we're going to set up a template engine called Handlebar and use that instead.
We need to do a couple steps to get the template engine working.
First, install express-handlebars
by running yarn add express-handlebars
in the command line.
Then, prepare this directory structure on your node
project.
.
├── app.js
└── views
├── home.handlebars
└── layouts
└── main.handlebars
2 directories, 3 files
Once structure is setup, you can setup the express
view engine to handlebar
in this manner.
const express = require('express')
const exphbs = require('express-handlebars')
var app = express();
// this line below, sets a layout look to your express project
app.engine('handlebars', exphbs({defaultLayout: 'main'}))
// this line sets handlebars to be the default view engine
app.set('view engine', 'handlebars')
app.get('/', function (req, res) {
// running this will let express to run home.handlebars file in your views folder
res.render('home')
})
Notice that all .html
file is now a .handlebars
file.
Expression
Templating with variables means we can pass in an object to the .render
function and access those variables inside the handlebars
template. These variables are also called context
index.js
const express = require('express');
const exphbs = require('express-handlebars')
const app = express();
app.engine('handlebars', exphbs({defaultLayout: 'main'}))
app.set('view engine', 'handlebars')
app.get('/', function(req, res) {
// giving home.handlebars file an object/context with `name` as a property
res.render('home', {name: "Sterling Archer"});
});
app.listen(3000);
then we need to update our home.handlebars
to use a templating variable.
views/home.handlebars
<!DOCTYPE html>
<html>
<head>
<title>Testing a View</title>
</head>
<body>
<h1>Hello, {{ name }}!</h1>
</body>
</html>
The JavaScript being embedded is enclosed by the {{ }}
tags, this in handlebars
is also called as an expression
.
HTML Escaping
By default, handlebars
will automatically escape the given context
. We can also use {{{ }}}
to tell handlebars
to NOT escape the context given:
// context given are these
var context = {
name: "<p>Sterling Archer</p>",
body: "<p>This is a post about <p> tags</p>"
}
app.get('/', function(req, res) {
res.render('home', context);
});
// in home.handlebars
<body>
<h1>{{name}}</h1>
{{{body}}}
</body>
Results in:
<body>
<h1><p> Sterling Archer <p></h1>
<p>This is a post about <p> tags</p>
</body>
so:
double curly braces
will escape theexpression
(e.g. convert<>
to<>
triple curly braces
will NOT escape theexpression
<% name %>
will not print out the expression, but it will execute it- Handy for
if
statements and loops
- Handy for
This doesn't only apply to primitive variables. We can even include variable declarations and iterators using ejs.
<!DOCTYPE html>
<html>
<head>
<title>Testing a View</title>
</head>
<body>
<h1>Hello, <%= name %>!</h1>
<% var obsessions = var obsessions = ['spying', 'sarcasm', 'Kenny Loggins']; %>
<ul>
<% obsessions.forEach(function(item) { %>
<li><%= item %></li>
<% }); %>
</ul>
</body>
</html>
Block Expressions
Block expressions allow you to define helpers that will invoke a section of your template with a different context than the current. These block helpers are identified by a #
preceeding the helper name (e.g. {{#each}}
or {{#if}}
) and require a matching pair with a /
sign (e.g. {{/each}}
or {{/if}}
.
Handlebars offers a variety of built-in helpers such as the if
conditional and each
iterator.
#each
Say we have the following context
given to our handlebars
template.
var context = {
people: [
"Yehuda Katz",
"Alan Johnson",
"Charles Jolley"
]
}
you can iterate through each of the people's name with the #each
helper:
<ul class="people_list">
{{#each people}}
<li>{{this}}</li>
{{/each}}
</ul>
will result in:
<ul class="people_list">
<li>Yehuda Katz</li>
<li>Alan Johnson</li>
<li>Charles Jolley</li>
</ul>
#if & #unless
Handlebar
can also conditionally render a block with #if
and #unless
.
Say the context are like so:
var context = {
maybeYesMaybeNo: Math.floor(Math.random()) // 50/50 chance of truth or false
}
// handlebars
<div>
{{#if maybeYesMaybeNo }}
<h1>YES</h1>
{{/if}}
{{#unless maybeYesMaybeNo }}
<h1>It's a no</h1>
{{/if}}
</div>
with the template above, each h1
will only be displayed YES if maybeYesMaybeNo
is truthy, and show NO if otherwise.
Layouts
A layout is simply a Handlebars template with a {{{body}}}
placeholder. Usually it will be an HTML page wrapper into which views will be rendered.
// this line below, creates a layout look to your express project
app.engine('handlebars', exphbs({defaultLayout: 'main'}))
With the given code above, handlebars
sets the layout file to be at "views/layouts/main.handlebars"
.
The layout into which a view should be rendered can be overridden per-request by assigning a different value to the layout
request local. The following will render the "home" view with no layout:
app.get('/diff', function (req, res, next) {
res.render('diff', {layout: false});
});
Example
In the root of the views folder, create a folder called layouts.handlebars
and add a layout called main.handlebars
views/layouts/main.handlebars
<!DOCTYPE html>
<html>
<head>
<title>Page</title>
</head>
<body>
{{{ body }}}
</body>
</html>
This layout will be used by all pages, and the content will be filled in where the {{{ body }}}
tag is placed. {{{ body }}}
is a special tag used by express-handlebars
that cannot be renamed.
Now we can create another page animals.handlebars
and see that it's content is placed in the page. We can create new pages without having to write the include statements for the header and footer.
First we add a simple route to app.js
:
app.get("/animals", function(req, res) {
res.render("animals", {title: "Favorite Animals", animals: ["sand crab", "corny joke dog"]})
});
And we create a new file views/animals.handlebars
:
<h1><%= title %></h1>
<ul>
{{#each animals}}
<li>{{this}}</li>
{{/each}}
</ul>
Add a simple navigation list to the to of the layout page so there's a link to every page from every page:
<!DOCTYPE html>
<html>
<head>
<title>Page</title>
</head>
<body>
<ul>
<li><a href="/">Favorite Foods</a></li>
<li><a href="/animals">Favorite Animals</a></li>
</ul>
{{{ body }}}
</body>
</html>
Partials
Partials can be used to modularize views and reduce repetition. A common pattern is to move the header and footer of a page into separate views, or partials, then render them on each page.
Example
views/partials/header.handlebars
<!DOCTYPE html>
<html>
<head>
<title>My Site</title>
</head>
<body>
views/partials/footer.handlebars
</body>
</html>
views/index.ejs
{{ > header }}
<h1>Welcome to my site!</h1>
{{ > footer }}