From Backbone To Thorax - Full Stack Web Development with Backbone.js (2014)

Full Stack Web Development with Backbone.js (2014)

Chapter 11. From Backbone To Thorax

By now, the user interface of Munich Cinema has taken shape. You have used a number of patterns and plug-ins to build applications with Backbone.js. To simplify the way we manage JavaScript projects, we also saw different automations of programming tasks with Grunt and Yeoman. As a last step, let’s look at a framework on top of Backbone.js where a number of previous ideas come together.

Authored by Ryan Eastridge and Kevin Decker, Thorax.js is an open source framework on top of Backbone.js that combines Backbone with Handlebars and some additional helpers. Thorax.js was optimized for mobile application but is increasingly used for single-page web applications as an alternative to Ember or Angular. With the Thorax.js generator, you get a tool for workflow automation. Also, Thorax comes with a Layout view, a CollectionView, and advanced event management out of the box.

This chapter might be interesting for you too, if you want to learn more about the Handlebars.js templating approach and view helpers.

In overview, we tackle the following topics:

§ Productivity and scalability of an application

§ Using Thorax for better view rendering

§ Advanced interactions to select a movie

The Role of Frameworks

The philosophy of Backbone.js is based on simplicity and flexibility. As such, Backbone provides only a set of very basic components, such as views and collections.

While these components can be customized into any direction, we often start repeating a number of choices over and over. What we want in many situations are a number of blueprints that help us to make faster choices. This is especially useful if you regularly build new applications, or if your team has to maintain multiple projects.

Although frameworks help you with making development faster and easier, they often enforce a higher coupling between your business logic and implementing technology. In other words, if the framework changes, your application might need quite a few changes, too.

In the Backbone.js ecosystem, a number of frameworks exist:

Marionette

This framework by Derick Bailey provides components to render collections to combine multiple views and boilerplate to manage events from different parts of an application. Marionette is one of the more popular frameworks on top of Backbone.js.

Thorax

Thorax combines the Handlebars.js templating approach with Backbone.js. As such, there are a number of view helpers that simplify the construction views. Additionally, Thorax supports improved data binding. Thorax also comes with a Yeoman generator for workflow automation.

Giraffe

Giraffe was created by the team behind Barc, a widget to embed chats in a web application. The framework is very lightweight and provides a number of enhancements to manage events from the router or views. In Giraffe.Contrib, there are also a number of view components like a collection view.

Chaplin

Chaplin introduces a predefined structure on top of Backbone.js applications, supported by components such as ModelView, Controller, mediator, and Application. The Chaplin documentation illustrates how the components relate to one another.

Rendr

Rendr is a so-called isomorphic JavaScript framework, which looks somewhat similar to Thorax. The big difference, however, is that Rendr supports both server-side and client-side rendering, validation, and helper functions.

Junior

Junior integrates helpers for views and the router that are optimized for building mobile applications. This framework is not only about JavaScript, but also provides CSS support for UI widgets.

Besides the direct usage of building applications faster, frameworks are also a good source for inspiration and developing an advanced understanding of Backbone.js techniques.

Measured in revenues of a Backbone.js web application, Walmart’s shopping cart is among the largest Backbone applications around. This makes it interesting to look closer at Thorax.js.

In the following sections, you will learn some ways how Thorax.js provides a way toward mobile and single-page web applications.

Getting Started

Thorax comes out of the box with workflow automation based on Grunt, Yeoman, and Bower. As was discussed in Chapter 10, we can use Yeoman and install a generator that supports us creating applications with Thorax.

To install the Thorax generator, we run the following:

$ npm install -g generator-thorax

Next, we can scaffold a Thorax application with:

$ yo thorax cinema

You will be asked whether to use the “Chef’s Choice” application stack. This is another name for convention-over-configuration. With this we mean a set of default configuration options that proved helpful for others. We happily answer “yes” and you can watch the dependencies getting installed. The main result of this is obtaining a directory structure, some dependencies, and a Gruntfile. Now, we are ready to go.

First, the Gruntfile of Thorax comes with a number of options for build tasks. The tasks are nicely distributed over files in the /tasks directory, such that you can easily adapt the tasks to your personal setup:

styles.js

open-browser.js

ensure-installed.js

options

|

| watch.js

| thorax.js

| sass.js

| requirejs.js

| mocha_phantomjs.js

| karma.js

| jshint.js

| cssmin.js

| clean.js

| connect.js

| copy.js

NOTE

Thorax comes with very good support for testing, too. The scope of this book does not include testing, but the reader is advised to look at the book Backbone.js Testing by Ryan Roemer to get a good introduction. JavaScript Testing Recipes by James Coglan is also a good resource.

Apart from the Gruntfile and build tasks, we get support for Require.js, a bower.json file, and some basic project directory structure. However, we also get some support for Thorax-specific components. Let’s have a look into main.js, where we manage the JavaScript dependencies:

require.config({

deps: ['main'],

paths: {

'jquery': pathPrefix + 'bower_components/jquery/jquery',

'underscore': pathPrefix + 'bower_components/underscore/underscore',

'handlebars': pathPrefix + 'bower_components/handlebars/handlebars',

'backbone': pathPrefix + 'bower_components/backbone/backbone',

'thorax': pathPrefix + 'bower_components/thorax/thorax',

'coffee-script': pathPrefix + 'bower_components/coffee-script/index',

'cs': pathPrefix + 'bower_components/require-cs/cs',

'text': pathPrefix + 'bower_components/text/text',

'hbs': pathPrefix + 'bower_components/requirejs-hbs/hbs',

'obscura': pathPrefix + "bower_components/backbone.obscura/backbone.obscura"

},

shim: {

'handlebars': {

exports: 'Handlebars'

},

'backbone': {

exports: 'Backbone',

deps: ['jquery', 'underscore']

},

'underscore': {

exports: '_'

},

'thorax': {

exports: 'Thorax',

deps: ['handlebars', 'backbone']

}

}

});

By scanning the dependencies, you will see handlebars, the approach for templating in Thorax. We will discuss this in a moment, but let’s first prepare again a sandbox for mock data.

Prepare Mock Data

As in the previous chapters, we want to work with some data mocks first. In a setup with RequireJS, you can use Mockjax and include it in the Bower file with:

$ bower install jquery-mockjax

and run:

$ bower install

In a js/mocks.js file, we include:

define(['jquery', 'mockjax'], function($) {

'use strict';

var mock = function() {

$.ajaxSetup({

dataType: 'json'

});

$.mockjax({

url: '/api/movies',

dataType: 'json',

proxy: 'json/movies.json'

});

};

return {

start: mock

};

});

To later use the mock setup in the application, we need to provide a reference in require-config.js. Note that we have different RequireJS setups for development and production. Right now, we just set up the runtime dependencies for development:

'mockjax': pathPrefix + 'bower_components/jquery-mockjax/jquery.mockjax'

// shims

'mockjax': {

deps: ['jquery']

}

As previously, we create mock data in json/movies.json:

[

{

"id": 12,

"showtime": 1388770080,

"genres": [

"Drama",

"Comedy"

],

"rating": 0,

"description": "A silent movie star meets a young dancer, ...",

"title": "The Artist",

"director": "Michel Hazanavicius",

"year": 2009

},

{

"id": 5,

"showtime": 1388700300,

"rating": 0,

"description": "The film is set in New York, shortly after ...",

"title": "Taxi Driver",

"genres": [

"Drama",

"Action"

],

"director": "Martin Scorsese",

"year": 1974

},

]

and some data for genres in json/genres.json:

[

{

"id": 1,

"name": "Drama",

"count": 5

},

{

"id": 2,

"name": "Comedy",

"count": 3

},

{

"id": 3,

"name": "Action",

"count": 6

}

]

Let’s start building the application next.

Initializing the Application

As discussed in Chapter 4, the movies browser will be the main entry point to the application, and you can scaffold a router with Yeoman as follows:

$ yo thorax:router browser

The application template from the Thorax project generator comes with a HelloWorld router, so you must replace that router with the new browser by inserting the following into the js/main.js file:

require([

'jquery',

'backbone',

'views/root',

'routers/browser',

'helpers',

], function ($, Backbone, RootView, Browser) {

initialize(function(next) {

var browser = new Browser();

next();

});

function initialize(complete) {

$(function() {

Backbone.history.start({

pushState: false,

root: '/',

silent: true

});

RootView.getInstance(document.body);

complete(function() {

Backbone.history.loadUrl();

});

});

}

});

The important idea is to resolve the root view and start rendering the views hierarchy.

To get a better feeling for the events on the main view, you might want to add the following for the development of the application:

var root = RootView.getInstance(document.body);

root.on("all", function(ev) { console.log(ev) });

We then see these events:

change:view:start

activated

child

change:view:end

Apart from adding the logger for events to trace certain commands, Thorax provides a global Thorax module, from where you can inspect instances of Thorax components. For example, you can grab all view classes when you run this in the browser console:

> T = require('thorax')

> Views = T.Views

So far, there are no child views and no other views that can be activated. To do this, let’s continue building the router from where we set up the main application.

A Router Setup

For building the router, the setup from the Thorax generator is a good start. We can “borrow” ideas from the scaffolded setup in the HelloWorldRouter. As before, the task of the movies browser will be to show lists and details of movies. And as a start, you can add routes for the list of movies and the movies details. This results in:

define([

'backbone',

'mocks'

], function (Backbone, Mock) {

return Backbone.Router.extend({

routes: {

"movies/:id": "showMovie",

"": "index"

},

index: function() {

},

showMovie: function() {

}

});

});

Besides monitoring URL changes, the router must fetch data and render the views. For development, we also introduce a reference to our mock setup. Let’s start setting up the collections next.

Thorax.Collection

Once the router is in place, it is time to setup a movies collection. Similarly to the router, you can scaffold a Movies collection with the generator:

$ yo thorax:collection movies

which results in:

create js/collections/movies.js

create test/collections/movies.spec.js

We now fill in the collection, which is very similar to a Backbone.Collection, and all we do right now is set the URL:

define(['collection'], function (Collection) {

return Collection.extend({

name: 'movies',

url: '/api/movies'

});

});

A small difference between a Backbone collection and a Thorax collection is that a Thorax collection can guard multiple fetches with a FetchQueue. This prevents the browser from doing HTTP requests when we can still “live” with the current data. This can be important for mobile applications.

In the router, let’s fetch the mock data from the browser as a first test. For this, we include the collection as dependency in the Movies router. The router then becomes:

define([

'backbone',

'mocks',

'collections/movies'

], function (Backbone, Mock, MoviesCollection) {

var movies;

return Backbone.Router.extend({

routes: {

"movies/:id": "showMovie",

"": "index"

},

index: function() {

movies = new Movies();

movies.on("all", function(ev) { console.log(ev) });

movies.fetch({success: function(results) {

console.log(results);

}, fail: function() {

console.log("fallback");

}

});

},

showMovie: function() {

}

});

});

To see some action, let’s start up the application with Grunt:

$ grunt

Then, we can test that the collection works by going to http://0.0.0.0:8000/. The browser console reveals the following:

load:start

request

reset

load:end

sync

So, besides the Backbone.js reset and sync, we also see events load:start and load:end. These events are especially useful to show and hide loading messages and keep loading data with routes in sync.

NOTE

Thorax provides a number of enhancements to simplify synchronization of state with the DOM. On one side, you get some support for easier data binding, which is needed when dealing with inputs from HTML forms. On the other side, Thorax has helpers as bindToRoute, where slow (mobile) data connection can guard against state changes.

Now that we have movies data, let’s continue to rendering movies.

Rendering

Thorax.js supports helpers from Handlebars.js and a number of special view helpers, so rendering advanced views becomes very easy.

First, we prepare the root view, which gives us a basic layout for the application. We use a basic root layout derived from the HelloWorld example:

<header>

<a href="#">Munich Cinema</a>

</header>

<section class="thorax-container thorax-wrapper">

<div class="thorax-primary">

{{layout-element}}

</div>

</section>

<footer>

<ul>

<li><a href="credits">Some credits</a></li>

</ul>

</footer>

The layout-element that is enclosed in double curly braces is a Thorax view helper and acts as main point to rendering the views. If you inspect the source code of layout-view, you will see some Handlebars code applied behind the scenes.

The idea of a layout is similar to a master template that is shared across multiple views. We can also embed a header and view child with view helpers as follows:

<header>

{{view header}}

</header>

With this, we easily can render child views that are properties on the parent views. We reference these child views as follows:

var RootView = LayoutView.extend({

name: 'root',

template: rootTemplate,

initialize: function() {

this.header = new Header();

}

});

For the index and showMovie actions in the router, we need to render a list of movies and the details, so let’s add them:

$ yo thorax:view movies/index

$ yo thorax:view movies/show

Then, in the js/views/movies/index.hbs file, we render the collection of movies with:

<h2>The latest movies</h2>

{{#collection}}

{{#link "movies/{{id}}" expand-tokens=true}}

{{ title }}

{/link}

{{/collection}}

By using view helpers from Thorax and Handlebars, our views become more readable, and it’s easier to embed child views, too. In this example, you use the collection view helper, which can also facilitate filtering.

NOTE

Thorax has more strategies to improve working with Backbone views. For example, Thorax provides an extended event hash to simplify binding of common events from models and collections. Also, Thorax provides view helpers to deal with user input from forms.

In order to render the movies, we must connect the MovieView with a Movies collection in the router with:

define([

'backbone',

'mocks',

'collections/movies',

'views/root',

'views/movies/index'

], function (Backbone, Mock, MoviesCollection, RootView, IndexView) {

var collection;

if (!collection) {

collection = new MoviesCollection();

Mock.start();

console.log("*** Mock movies API ***");

}

return Backbone.Router.extend({

routes: {

"movies/;id": "showMovie",

"": "index"

},

index: function() {

var view;

collection.fetch({success: function(result) {

view = new IndexView({collection: collection});

RootView.getInstance().setView(view);

}});

},

showMovie: function() {

}

});

});

When we now go to the browser main page, you should see a working index view. When we click on a movie, nothing yet happens unless we add the rendering of showMovie.

So, we can wire up the show view route in app/routers/browser.js:

showMovie: function(id) {

var movie = collection.get(id);

var view = new ShowMovie({model: movie});

RootView.getInstance().setView(view);

}

Conclusion

This chapter covered the basics of the Yeoman generator for Thorax. Based on RequireJS, you saw how a large Backbone application can be structured and how using Handlebars view helpers of Thorax can simplify the setup of views. Importantly, you saw how the layout-element can be used to place a child view in a layout. Then, you saw how to place sub-views, such as a header and footer, with the view helper.

Thorax offers additional features, such as support for better loading of data and dealing with inputs from user forms. Also, Thorax can provide support for automated tests. And, as with any Backbone project, you can keep your options open to easily replace building blocks when the requirements on your application change.