I hated Jade as many other Node.js developes do. But I changed 180 after I realized that it has tons of features.
At Storify and DocuSign we used Jade for EVERYTHING. We used Jade even in the browser. There is a little trick called jade-browser. It was developed by folks at Storify. I maintained it for a bit.
The funny thing is that DocuSign team used jade-browser long before they met me. They swear they hired me without knowing that I was involved in that library. :-)
Anyway, after covering Jade and Handlebars in previous posts, it’s time to apply them to do some real work. In this post, I’ll cover:
- Jade and Handlebars usage in Express.js 4
- Project: adding Jade templates to Blog
By default, Express.js 4.x (and 3.x) uses either a template extension provided to the 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/).
Here is a quick example of Consolidate.js for Express.js 4 (version 4.2.0 and Consolidate version is 0.10.0):
var express = require('express'),
cons = require('consolidate'),
app = express()
app.engine('html', cons.swig)
app.set('view engine', 'html')
app.set('views', __dirname + '/views')
var platforms = [
{ name: 'node' },
{ name: 'ruby' },
{ name: 'python' }
]
app.get('/', function(req, res){
res.render('index', {
title: 'Consolidate This'
})
})
app.get('/platforms', function(req, res){
res.render('platforms', {
title: 'Platforms',
platforms: platforms
})
})
app.listen(3000)
console.log('Express server listening on port 3000')
Usually, the source code is in the GitHub repository and the snippet is in the
ch4/consolidate
folder.
For more information on how to configure Express.js settings and use Consolidate.js, refer to the Pro Express.js 4 book (Apress, 2014).
Jade and Express.js
Jade is compatible with Express.js out of the box (in fact, it’s the default choice), so to use Jade with Express.js, you just need to install a template engine module jade (https://www.npmjs.org/package/jade) and provide an extension to Express.js via the view engine
setting).
For example, in the main server file we set the setting:
app.set('view engine', 'jade');
■ Note If you use $ express <app_name>
command-line tool, you can add the option for engine support, i.e.,–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 Jade.
In the route file, we can call the template—for example, views/page.jade
(the views
folder name is another Express.js default, which can be overwritten with the
view
setting):
app.get('/page', function(req, res, next){
//get the data dynamically
res.render('page', data);
});
If we don’t specify the views engine
setting, then the extension must be passed explicitly to res.render()
:
res.render('page.jade', data);
Handlebars and Express.js
Contrary to Jade, the Handlebars library from http://handlebarsjs.com/ doesn’t come with the __express method, but there are a few options to make Handlebars work with Express.js:
consolidate
: a Swiss-army knife of Express.js template engine libraries (shown above)hbs
(https://github.com/donpark/hbs): wrapper library for Handlebarsexpress-Handlebars
(file://pchns-f01/TECHNOLOGY/BPR/Techutilities/Apress/Apress%20Outline/express3-handlebars
): despite the name, this module should work just fine with Express.js 4 as well as version 3.x
Here’s how we can use hbs
approach (extension hbs
). Inside of the typical Express.js app code (i.e., configuration section of the main file that we launch with the $ node
command) write the following statements:
...
app.set('view engine', 'hbs');
...
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 express3-handlebars
approach usage is as follows:
...
app.engine('handlebars', exphbs({defaultLayout: 'main'}));
app.set('view engine', 'handlebars');
...
Project: Adding Jade Templates to Blog
Last, we can continue with Blog. In this section we add main pages using Jade, plus add a layout and some partials:
layout.jade
: global app-wide templateindex.jade
: home page with the list of postsarticle.jade
: individual article pagelogin.jade
: page with a login formpost.jade
: page for adding a new articleadmin.jade
: page to administer articles after logging in
The demo where we’ll plug in the MongoDB database is in the ch5
folder of the GitHub repository practicalnode: https://github.com/azat-co/practicalnode. So the source code for the Jade templates is exactly the same as in that GitHub project. Feel free to copy it from there or follow the instructions below.
layout.jade
Let’s open the project where we left off in the ch3
from https://github.com/azat-co/practicalnode and add layout.jade
with the document type statement:
doctype html
■ Note doctype 5
was deprecated around v1.0. Now we can add the main tags of the page:
html
head
The title of the each page is provided from the appTitle
variable (aka, 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='https://azat.wpenginepowered.com/css/bootstrap-3.0.2/css/bootstrap.min.css')
link(rel="stylesheet", href="https://azat.wpenginepowered.com/css/bootstrap-3.0.2/css/bootstrap-theme.min.css")
link(rel="stylesheet", href="https://azat.wpenginepowered.com/css/style.css")
script(type="text/javascript", src="https://azat.wpenginepowered.com/css/bootstrap-3.0.2/js/bootstrap.min.js")
script(type="text/javascript", src="https://azat.wpenginepowered.com/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 texts:
h1.page-header= appTitle
p.lead Welcome to example from Express.js Experience by
a(href="http://twitter.com/azat_co") @azat_co
|. 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, we can display messages for users:
block alert
div.alert.alert-warning.hidden
Main content goes in this block:
.content
block content
Lastly, the footer looks as follows:
block footer
footer
.container
p
| Copyright © 2014 | Issues? Submit to a(href="https://github.com/azat-co/blog-express/issues") GitHub
| .
The full code of layout.jade
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="https://azat.wpenginepowered.com/css/bootstrap-3.0.2/css/bootstrap.min.css")
link(rel="stylesheet", href="https://azat.wpenginepowered.com/css/bootstrap-3.0.2/css/bootstrap-theme.min.css")
link(rel="stylesheet", href="https://azat.wpenginepowered.com/css/style.css")
script(type="text/javascript", src="https://azat.wpenginepowered.com/css/bootstrap-3.0.2/js/bootstrap.min.js")
script(type="text/javascript", src="https://azat.wpenginepowered.com/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="http://twitter.com/azat_co") @azat_co
|. 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
| .
index.jade
Now we can look at the home page template index.jade
that extends layout:
extends layout
We set the menu
variable to index
, so the menu include (i.e., menu.jade
) can determine which tab to show as active:
block page
- var menu = 'index'
The main content with the list of articles that comes from locals
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
The full code of index.jade
is as follows:
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.
article.jade
The individual article page (Figure 4–5 ) is relatively unsophisticated because most of the elements are abstracted into layout.jade
:
extends layout
block content
p
h1= title
p= text
login.jade
Similarly, the login page contains only a form and a button (with the Twitter Bootstrap classes/markup):
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="hi@azat.co")
p
input.form-control(name="password", type="password", placeholder="***")
p
button.btn.btn-lg.btn-primary.btn-block(type="submit") Log in
p
input.form-control(name="password", type="password", placeholder="***")
p
button.btn.btn-lg.btn-primary.btn-block(type="submit") Log in
Figure 4–6 shows how the login page looks.
post.jade
The post page (Figure 4–7 ) has another form. This time, the form contains a text area element:
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
admin.jade
The admin page (Figure 4–8 ) has a loop of articles just like the home page. In addition, we can include a front-end script (js/admin.js
) specific to this page:
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")
We use interpolation to print article IDs as attributes data-id
:
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!
span.glyphicon(class=(article.published)?"glyphicon-pause":"glyphicon-play",
title=(article.published)?"Unpublish":"Publish")
Summary
You learned about Jade 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 Jade.
In another tutorial, we examined an important aspect of modern web development and software engineering: test-driven development. We looked at the Mocha module and wrote some tests for Blog in true TDD/BDD style.
The example with a database added to Blog to populate these templates is in ch5
of https://github.com/azat-co/practicalnode. It shows you how to turn Jade templates into working HTML pages!
Cool how you advertise this as a Handlebars and Jade tutorial then just use Jade
Your website is restricting me from seeing an entire column portion of your article. (hopefully that makes sense)