Advanced View Templates - Full Stack Web Development with Backbone.js (2014)

Full Stack Web Development with Backbone.js (2014)

Chapter 6. Advanced View Templates

So far, you’ve only used embedded view templates in the code base for Munich Cinema. However, Backbone provides several options to combine advanced HTML templates with Backbone views.

The task of integrating view templates into a Backbone project raises new design questions on an application stack. You could argue that you need support of server-side rendering of views for the first page load or for search engine optimization (SEO) purposes.

For Munich Cinema, support of server-side rendering is not needed. Here, a pure client-side application is sufficient, because our main goal is to provide a subapplication for a better browsing experience. Similar concerns apply if you work on mobile applications or other data-driven interfaces.

In this chapter, we extend the knowledge we’ve gained in previous chapters regarding how to integrate view templates. Also, we will add some better view templates and a basic build process with Grunt.js. With Grunt.js, we easily can “uglify” frontend assets for better transport.

The following Node-based tools for frontend development will be discussed:

§ ECO view templates

§ JST view templates

§ Grunt, including how to set up a build process

Views and Templates

In the prototype of Munich Cinema, the DOM nodes from Backbone.Views were very basic. By embedding templates in views, the views easily bloat up, as you can observe from the current Layout view. Also, if you collaborate with developers that work on HTML and CSS only, it can often be helpful to have a separate directory for HTML templates only. Last, with a template engine, you can embed a bit of logic, such as loops, to simplify building list elements.

As view templates are often written in an HTML style, you need to compile the raw templates into a syntax that is compatible with JavaScript. The conversion from one syntax to another can often be simplified with so-called build tools, such as Grunt. Workflows and build automation will be discussed again later in the book.

Let’s look first at some popular templating engines.

JST

JST stands for JavaScript Templates and is a very popular approach to mix HTML tags with embedded JavaScript.

To include JST templates with Browserify, you need to use a so-called transform plug-in to translate the JST syntax into a JavaScript function. This list provides an overview of supported Browserify transforms.

To transform JST into JavaScript modules, you can use the jstify transform. First, you must install the plug-in with:

$ npm install jstify --save-dev

For now, you will need this dependency only for development, hence the --save-dev.

Templates are often shared with a web designer colleague, so it is good practice to keep templates in a central place. For this, you will need a new directory to work with the templates:

$ mkdir app/templates

Next, you can create a JST template to replace the embedded genres filter with the following template app/templates/genres.html.jst:

<ul class="filter-genres">

<% _.each(genres, function(name) { %>

<li>

<input type="checkbox" name="genres" value="<%= name %>">

<%= name %>

</input>

</li>

<% }) %>

</ul>

You can then require the template in a view app/views/genresFilter.js, as follows:

var Backbone = require('backbone');

var genresTemplate = require('../templates/genres.jst');

// The UI for selecting a Movie Category

var GenresView = Backbone.View.extend({

template: genresTemplate,

render: function() {

this.$el.html(this.template({genres: this.genres}));

return this;

},

initialize: function() {

this.genres = ['Action', 'Drama', 'Comedy'];

}

});

module.exports = GenresView;

The genres could again come from outside the view, but right now this is secondary in the discussion.

Now you can use the browserify command together with the jstify transform:

$ browserify ./app/main.js -t jstify > static/bundle.js

Note, in other application stacks, there is a global JST object template module. This “JST” object acts as a central object from where all templates can be referenced. For example, this is the default approach in frameworks such as Ruby on Rails and Sprockets, or in Grunt-based build processes, which we will discuss soon:

template: JST['genresFilter']

To create this JST object, we usually have a build step where the templates are concatenated into a single file and exported as a JST object. Because this step is very similar to preparing an application for deployment, we’ll discuss this further in Grunt.

ECO

Another approach to improve building DOM nodes is given by ECO templates. ECO stands for “embedded CoffeeScript.” Some prefer the minimal syntax of JavaScript. Also, the template engine can be easily combined with stacks based on Ruby on Rails or Stitch, as ECO is written by the same author, Sam Stephensson. Here’s how we install it:

$ npm install eco

From now on, we can combine the template property in a Backbone.View by requiring an external file. Let’s show this on the example of a better GenresFilter.

A view template for the filter might look like:

<ul class="filter-genres">

<% for genre in genres: %>

<li>

<input type="checkbox" name="genres" value="<%= name %>">

<%= name %>

</input>

</li>

<% end %>

</ul>

You can compile this template with the browserify-eco transform as follows:

$ browserify ./app/main.js -t browserify-eco

By applying the ECO format, we can write logic such as for genre in genres. This will look familiar if you have worked with embedded Ruby other template approaches before.

Handlebars

With Handlebars templates, you can embed a high amount of logic into a view. We will cover Handlebars in detail again in Chapter 11.

To give you an idea of how Handlebars templates should look, let’s rewrite the filter with a Handlebars syntax:

<ul class="filter-genres">

{{#each genres}}

<li>

<input type="checkbox" name="genres" value="{{ name }}">

{{ name }}

</input>

</li>

{{/each}}

</ul>

As you can see, working with templates feels closer to working with HTML than it does to working with JavaScript. Before we apply a better templating in the Munich Cinema example, let’s shortly mention other approaches to templating.

React and Others

Backbone can easily be extended with plug-ins or external libraries, which means you can include very advanced strategies for building DOM nodes. A number of strategies are especially interesting:

Backburner

When changing multiple DOM nodes at once, it often becomes necessary to control events for rendering in one place. The topic of coalescing properties in views is currently beyond the scope of the book, but interested readers should investigate Backburner for a possible approach.

React.js

React.js makes interfaces composable. For this, React.js provides a powerful abstraction, the “Shadow DOM.” To start with React.js, you can use your existing Backbone views and simply replace your render function with React.js. As a next step, you could replace HTML templates with React’s JSX templates. Browserify supports React views with a “JSX” transform. Further discussion of React.js is currently beyond the scope of this book.

Build Automation

So far, you used the command line and Browserify to bundle JavaScript assets in the web application, but integrating many view templates and CSS styling often require some kind of build processes. Also, you probably will deal with a server process as well as processes for testing your application.

Besides building and serving your application, you probably want to control the quality of your code with JSLint or code beautifiers. And to prepare your application for deployment, you will want to minify the frontend assets for better transport over HTTP.

All these goals are the topics of build automation, and in this section we take a look at Grunt to bundle view templates and support the development of an application.

Having an understanding of build processes is also crucial for so-called isomorphic JavaScript applications, where view templates can be rendered on both the client and server. As an example, the Rendr library by Spike Brehm provides an application stack based on Backbone, Grunt, and Browserify, where Backbone views can be rendered on both the client and server.

Grunt

Grunt is a widely popular task runner from the Node ecosystem. By using a Gruntfile, you can automate build tasks and include tasks for managing different tasks, such as running a server process, bundling templates, and testing JavaScript. The syntax for Grunt tasks can be a bit confusing at first. For details you should consult one of the many tutorials on the topic.

NOTE

Working with Grunt has become very popular in the world of frontend development, and a number of resources exist to help you see the many ways that Grunt can support you. We will also return to the discussion of Grunt in Chapter 10, in which we’ll look at its role in bundling applications based on RequireJS.

Grunt is controlled from the command line. The tasks in a Gruntfile can be defined for different modes for operations, such as development, testing, or production. The concepts behind Grunt are closely related to a Makefile or its derivatives Rakefile or Ant, in Ruby or Java.

You can use Grunt’s built-in tasks that are optimized for frontend JavaScript development, or you can extend Grunt with plug-ins. Build tasks can be as easy as syntax checking of source files to more complicated operations as preprocessing styles, or preparing files for production. All the tasks are defined in a so-called Gruntfile.

You can install Grunt with:

$ npm install -g grunt

For now, we want to have Grunt support for the following tasks in development mode:

§ Run a server process, and watch the server file for changes.

§ Concatenate and bundle Handlebars view templates, and watch these files for changes.

To achieve these goals, you also need tasks that come from Grunt plug-ins. To quickly set up your dependencies, you can add the following new dependencies in package.json:

"dependencies": {

...

"grunt-browserify": "~2.0.7",

"grunt-contrib-watch": "~0.6.1",

"grunt-contrib-handlebars": "~0.7.0",

"nodemon": "~1.0.17",

"handlebars": "~1.3.0"

}

These dependencies are installed in our project folder with:

$ npm install

As you can see, you also fetch the dependencies nodemon and handlebars. With nodemon, the server process is automatically reloaded when a file changes. The Handlebars dependency supports you in translating hbs view templates to JavaScript.

Next, let’s look at the Gruntfile. A Gruntfile can become quite large, and sometimes it can be understood if you work your way up, from bottom to top.

First, there are the tasks of a Gruntfile that can be called as commands from the command line. For development purposes, we are mainly interested in a server task, where all changes are observed and translated into new outputs as appropriate. This translates to the following snippet in theGruntfile.js:

grunt.registerTask('compile', ['handlebars', 'browserify']);

// Run the server and watch for file changes

grunt.registerTask('server', ['compile', 'runNode', 'watch']);

// Default task

grunt.registerTask('default', ['compile']);

These tasks can be defined recursively. For example, the runNode task can look like:

grunt.registerTask('runNode', function () {

grunt.util.spawn({

cmd: 'node',

args: ['./node_modules/.bin/nodemon', 'server.js'],

opts: {

stdio: 'inherit'

}

}, function () {

grunt.fail.fatal(new Error("nodemon quit"));

});

});

while the other tasks can be included from a plug-in:

grunt.loadNpmTasks('grunt-browserify');

grunt.loadNpmTasks('grunt-contrib-handlebars');

grunt.loadNpmTasks('grunt-contrib-watch');

grunt.registerTask('runNode', function () {

grunt.util.spawn({

cmd: 'node',

args: ['./node_modules/.bin/nodemon', 'server.js'],

opts: {

stdio: 'inherit'

}

}, function () {

grunt.fail.fatal(new Error("nodemon quit"));

});

});

Last, tasks can be configured. This configuration takes up the most space in a Gruntfile. Let’s first look at the configuration of the Browserify task:

browserify: {

options: {

debug: true,

aliasMappings: [

{

cwd: 'app/',

src: ['**/*.js'],

dest: 'app/'

}

]

},

app: {

src: [ 'app/**/*.js' ],

dest: 'static/bundle.js'

}

}

And, to configure the tasks for watch and Handlebars templates, you can add:

watch: {

scripts: {

files: 'app/**/*.js',

tasks: ['browserify'],

options: {

interrupt: true

}

},

templates: {

files: 'app/**/*.hbs',

tasks: ['handlebars'],

options: {

interrupt: true

}

},

},

handlebars: {

compile: {

options: {

namespace: false,

commonjs: true,

processName: function(filename) {

return filename.replace('app/templates/', '').replace('.hbs', '');

}

},

src: "app/templates/**/*.hbs",

dest: "app/templates/compiledTemplates.js"

}

}

Last, you must wrap all the code in an initConfig function, and the resulting Gruntfile.js should look like this example file.

Other popular tasks include jshint, which performs a syntax check:

jshint: {

options: {

curly: true

},

gruntfile: {

src: 'Gruntfile.js'

}

}

and a task for cleaning up previous builds:

// Clean public folder

clean: {

all: ["dist/*.js"]

}

We can check that the Gruntfile is set up correctly with:

$ jslint Gruntfile.js

$ grunt --help

Running grunt help returns a list of tasks that come from the gruntfile.js:

Available tasks

browserify Grunt task for browserify. *

handlebars Compile handlebars templates and partials. *

watch Run predefined tasks whenever watched files change.

runNode Custom task.

compile Alias for "handlebars", "browserify" tasks.

server Alias for "compile", "runNode", "watch" tasks.

default Alias for "compile" task.

The default task is run if we call grunt without arguments.

NOTE

Depending on the strategies for packaging assets, a Gruntfile can become rather large. It often makes sense to modularize your Gruntfile by having configurations in external files. A number of developers share their preferred application structure with a Gruntfile, too. For an example of a basic Gruntfile for a Backbone app, go to https://github.com/kud/marrow. We will see more ways for automation in Chapter 10.

Running our grunt server task results in:

$ grunt server

Running "handlebars:compile" (handlebars) task

File app/templates/compiledTemplates.js created.

Running "browserify:app" (browserify) task

Running "runNode" task

Running "watch" task

Waiting...

15 Apr 19:25:21 - [nodemon] v1.0.17

15 Apr 19:25:21 - [nodemon] to restart at any time, enter `rs`

15 Apr 19:25:21 - [nodemon] watching: *.*

15 Apr 19:25:21 - [nodemon] starting `node server.js`

Pushstate server started on port 5000

Now, when you change the files in the application, the application is automatically browserified, and you can have faster feedback on your development. Let’s add a reference to Handlebars as follows:

var Handlebars = require('handlebars');

var Templates = require('templates/compiledTemplates')(Handlebars);

and a template for movies in app/templates/movies.hbs:

<h1>

<a href="/movies/{{ id }}">

{{ title }}

</a>

</h1>

<hr>

If you observe the terminal output, you should see that the template is automatically precompiled for you, too.

Conclusion

In this chapter, you learned about advanced approaches to DOM manipulation with JST and ECO templates. You also learned to browserify templates with a transform. You then met build automation wiht Grunt.js and a very basic Gruntfile.js to support you with application development.

Now that we’ve applied better templating to it, our web application is almost ready for deployment. You still need to learn more about APIs, and how to connect those to your Backbone models and collections to build a fully working web application. As build processes are an important part of JavaScript web applications, you will also learn more about workflow automation in Chapter 10. In addition, you will be introduced to more Backbone plug-ins for special types of interactions in the following chapters.