This new Backbone.js tutorial series will walk you through building a single page web application that has a customised Backbone.sync implementation. I started building the application that these tutorials are based on back in August, and it’s been running smoothly for a few months now so I thought it was safe enough to unleash it.
Gmail to-do lists: not cool enough!
Backbone.sync
method that works with Google’s APIs and stuck a little Bootstrap interface on top. As part of these tutorials I’ll also make a few suggestions on how to customise Bootstrap – there’s no excuse for releasing vanilla Bootstrap sites!The app we’ll be making won’t feature everything that Google’s to-do lists support: I haven’t yet added support for indenting items for example. However, it serves my needs very well so hopefully it’ll be something you’ll actually want to use.
Roadmap
Over the next few weeks I’ll cover the following topics:
- Creating a new Node project for building the single page app
- Using RequireJS with Backbone.js
- Google’s APIs
- Writing and running tests
- Creating the Backbone.js app itself
- Techniques for customising Bootstrap
- Deploying to Dropbox, Amazon S3, and potentially other services
Creating a Build Environment
If your focus is on client-side scripting, then I think this will be useful to you. Our goal is to create a development environment that can do the following:
- Allow the client-side code to be written as separate files
- Combine separate files into something suitable for deployment
- Run the app locally using separate files (to make development and debugging easier)
- Manage supporting Node modules
- Run tests
- Support Unix and Windows
To do this we’ll need a few tools and libraries:
Make sure your system has Node installed. The easiest way to install it is by using one of the Node packages for your system.
Step 1: Installing the Node Modules
Create a new directory for this project, and create a new file inside it called
package.json
that contains this JSON:{
"name": "btask"
, "version": "0.0.1"
, "private": true
, "dependencies": {
"requirejs": "latest"
, "connect": "2.7.0"
}
, "devDependencies": {
"mocha": "latest"
, "chai": "latest"
, "grunt": "latest"
, "grunt-exec": "latest"
}
, "scripts": {
"grunt": "node_modules/.bin/grunt"
}
}
npm install
. These modules along with their dependencies will be installed in ./node_modules
.The
private
property prevents accidentally publicly releasing this module. This is useful for closed source commercial projects, or projects that aren’t suitable for release through npm.Even if you’re not a server-side developer, managing dependencies with npm is useful because it makes it easier for other developers to work on your project. When a new developer joins your project, they can just type
npm install
instead of figuring out what downloads to grab.Step 2: Local Web Server
Create a directory called
app
and a file called app/index.html
:<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>bTask</title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script type="text/javascript" src="js/lib/require.js"></script>
</head>
<body>
</body>
</html>
server.js
in the top-level directory:var connect = require('connect')
, http = require('http')
, app
;
app = connect()
.use(connect.static('app'))
.use('/js/lib/', connect.static('node_modules/requirejs/'))
.use('/node_modules', connect.static('node_modules'))
;
http.createServer(app).listen(8080, function() {
console.log('Running on http://localhost:8080');
});
app/
. You can add new paths to it by copying the .use(connect.static('app'))
line and changing app
to something else.Notice how I’ve mapped the web path for
/js/lib/
to node_modules/requirejs/
on the file system – rather than copying RequireJS to where the client-side scripts are stored we can map it using Connect. Later on the build scripts will copy node_modules/requirejs/require.js
to build/js/lib
so the index.html
file won’t have to change. This will enable the project to run on a suitable web server, or a hosting service like Amazon S3 for static sites.To run this Node server, type
npm start
(or node server.js
) and visit http://localhost:8080
. It should display an empty page with no client-side errors.Step 3: Configuring RequireJS
This project will consist of modules written in the AMD format. Each Backbone collection, model, view, and so on will exist in its own file, with a list of dependencies so RequireJS can load them as needed.
RequireJS projects that work this way are usually structured around a “main” file that loads the necessary dependencies to boot up the app. Create a file called
app/js/main.js
that contains the following skeleton RequireJS config:requirejs.config({
baseUrl: 'js',
paths: {
},
shim: {
}
});
require(['app'],
function(App) {
window.bTask = new App();
});
require(['app']
will load app/js/app.js
. Create this file with the following contents:define([], function() {
var App = function() {
};
App.prototype = {
};
return App;
});
define
function is provided by RequireJS and in future will contain all of the internal dependencies for the project.To finish off this step, the
main.js
should be loaded. Add some suitable script tags near the bottom of app/index.html
, before the </body>
tag.<script type="text/javascript" src="js/main.js"></script>
http://localhost:8080
in your browser and open the JavaScript console, you should see that bTask
has been instantiated.Step 4: Testing
Everything you’ve learned in the previous three steps can be reused to create a unit testing suite. Mocha has already been installed by npm, so let’s create a suitable test harness.
Create a new directory called
test/
that contains a file called index.html
:<html>
<head>
<meta charset="utf-8">
<title>bTask Tests</title>
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
<style>
.toast-message, #main { display: none }
</style>
</head>
<body>
<div id="mocha"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="/node_modules/chai/chai.js"></script>
<script src="/node_modules/mocha/mocha.js"></script>
<script src="/js/lib/require.js"></script>
<script src="/js/main.js"></script>
<script src="setup.js"></script>
<script src="app.test.js"></script>
<script>require(['app'], function() { mocha.run(); });</script>
</body>
</html>
require
near the end just makes sure mocha.run
only runs when /js/app.js
has been loaded.Create another file called
test/setup.js
:var assert = chai.assert;
mocha.setup({
ui: 'tdd'
, globals: ['bTask']
});
assert
, which is how I usually write my tests. I’ve also told Mocha that bTask
is an expected global variable.With all this in place we can write a quick test. This file goes in
test/app.test.js
:suite('App', function() {
test('Should be present', function() {
assert.ok(window.bTask);
});
});
window.bTask
has been defined – it proves RequireJS has loaded the app.Restart the web server (from step 2), and visit
http://localhost:8080/test
. Mocha should display that a single test has passed.Step 5: Making Builds
Create a file called
grunt.js
for our “gruntfile”:module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-exec');
grunt.initConfig({
exec: {
build: {
command: 'node node_modules/requirejs/bin/r.js -o require-config.js'
}
}
});
grunt.registerTask('copy-require', function() {
grunt.file.mkdir('build/js/lib');
grunt.file.copy('node_modules/requirejs/require.js', 'build/js/lib/require.js');
});
grunt.registerTask('default', 'exec copy-require');
};
app/
directory. To tell RequireJS what to build, create a file called require-config.js
:({
appDir: 'app/'
, baseUrl: 'js'
, paths: {}
, dir: 'build/'
, modules: [{ name: 'main' }]
})
build/js/lib/require.js
, because our local Connect server was mapping this for us. Why bother? Well, it means whenever we update RequireJS through npm the app and builds will automatically get the latest version.To run Grunt, type
npm run-script grunt
. The npm command run-script
is used to invoke scripts that have been added to the package.json
file. The package.json
created in step 1 contained "grunt": "node_modules/.bin/grunt"
, which makes this work. I prefer this to installing Grunt globally.I wouldn’t usually use Grunt for my own projects because I prefer Makefiles. In fact, a Makefile for the above would be very simple. However, this makes things more awkward for Windows-based developers, so I’ve included Grunt in an effort to support Windows. Also, if you typically work as a client-side developer, you might find Grunt easier to understand than learning GNU Make or writing the equivalent Node code (Node has a good file system module).
Summary
In this tutorial you’ve created a Grunt and RequireJS build environment for Backbone.js projects that use Mocha for testing. You’ve also seen how to use Connect to provide a convenient local web server.
This is basically how I build and manage all of my Backbone.js single page web applications. Although we haven’t written much code yet, as you’ll see over the coming weeks this approach works well for using Backbone.js and RequireJS together.
The code for this project can be found here: dailyjs-backbone-tutorial (2a8517).