Write the App
Everyone knows that large scale JavaScript applications can get out of control really quickly really fast. Tools like jQuery are helpful in providing easy access to the DOM, but provide little to no help when piecing together a full fledge application. You’ll begin writing a few selectors here and there, manipulating some data, making some Ajax requests, and next thing you know your knee deep in a big bowl of Spaghetti code!
Bring on RequireJS.
RequireJS is a framework that adheres to the ComonJS specification for Asynchronous Modular Definitions (AMD). See this post by a fellow brewer, Derek Greer for some more info on getting started with Requirejs.
The basic idea behind RequireJS and AMD is to separate your code into Modules, and by Modules, I mean this definition found by Nicholas Zakas…
Module: An independent self-contained unit of a spacecraft.
Here, each module can exist separately, or together with the rest of the space ship.
This is a common way to solve JavaScript architecture as well.
To build a BackboneJS app with RequireJS…
- Download RequireJS (I used the Sample Require + jQuery App)
- Download the Order plugin
- Download UnderscoreJS (Backbone depends on Underscore)
- Get one of the AMD versions of Backbone from @jrburke off of one of his optamd branches. I used this one https://github.com/jrburke/backbone/blob/optamd/backbone.js
- Create a folder structure, and files like… (Bold means create a new empty file)
/r.js (Copy from the RequireJS download)
/app/index.htm
/app/scripts/
/app/scripts/require-jquery.js (Copy from the RequireJS download)
/app/scripts/main.js
/app/scripts/app.js
/app/scripts/order.js
/app/scripts/lib/underscore.js
/app/scripts/lib/backbone.js
/app/scripts/routers/home.js
/app/scripts/views/welcome.js
/app/scripts/models/movie.js
/app/scripts/collections/movies.js - Build models, views, routers, and collections at your leisure
First up is the index.htm file…
<!DOCTYPE html> <html> <head> <title>BackboneJS Modular app with RequireJS</title> <script data-main="scripts/main" src="scripts/require-jquery.js"></script> </head> <body> <h3>BackboneJS is awesome</h3> <div id="main"></div> <script id="moviesTemplate&qout; type="text/html"> <div id="<%= model.name %>"></div> </script> </body> </html>
Then there’s main.js. This is where you’ll define the base of how your application will load…
require.config({ 'paths': { "underscore": "libs/underscore", "backbone": "libs/backbone"
} }); require([ 'order!libs/underscore', 'order!libs/backbone', 'order!app' ], function(_,Backbone,app){ app.init(); });
The require.config set’s up some paths for the modules that are defined with a name. Next, the require function takes in 2 arguments. First is an array of dependencies that main.js will load in. The order plugin makes sure that Underscore and Backbone get loaded in order as Require typically loads things in asynch which is bad for Backbone since it requires Underscore. Using the require-jquery verion includes jQuery 1.7 which now has AMD built in. app references the app.js file you will create in a second. The function callback takes the loaded dependencies and passes them as arguments in the order you loaded them in, _, Backbone, and app.
Now for the app.js…
define(['routers/home'], function(router){ var init = function(){ console.log("App Started..."); }; return { init: init}; });
Here we use define to create a new module. The first argument is an array of dependencies, and the second is a callback like the one on main.js which will fire once the dependencies have loaded. For now I am just requiring the home.js router.
The home.js router looks like…
define(['backbone','views/welcome'],function(Backbone,welcome){ var homeRouter = Backbone.Router.extend({ initialize: function(){ Backbone.history.start(); }, routes: { '': 'home' // Default route }, 'home': function(){ welcome.render(); } }); return new homeRouter(); });
Here we load in Backbone, and the welcome.js view…
define(['jquery','backbone','underscore', 'collections/movies'], function($, Backbone, _, movies){ var welcomeView = Backbone.View.extend({ el: "#main", initialize: function(){ this.movies = new movies(); this.template = _.template($("#moviesTemplate").html()); this.movies.bind('add', this.addMovie, this); }, render: function(){ this.movies.add({ name: 'Empire Strikes Back'}); this.movies.add({ name: 'Jurrasic Park'}); this.movies.add({ name: 'The Last Crusade'}); } addMovie: function(model){ $(this.el).append(this.template({ model: model.toJSON() }); } }); return new welcomeView(); });
This one loads in jQuery, backbone, underscore, and our movies collection which looks like…
define(['backbone','underscore','models/movie'],function(Backbone,_,movie){ var movies = Backbone.Collection.extend({ model: movie, }); return movies; });
From here we get down to the model, movie…
define(['backbone','underscore'],function(Backbone,_){ var movie = Backbone.Model.extend({}); return movie; });
So that’s how to create a basic app with a router, view, collection, and model.
Build with Node or Java
One of the cool parts about Require is it’s ability to build and compress your application for you. It requires an app.build.js file, r.js(which you download with the RequireJS) jQuery app, and if you want to compile with Java, you’ll need Rhino and Clojure (Thanks to @jrburke again for this help page on r.js) or if you want to use NodeJS you’ll just need Node for your OSOC (Operating System of Choice) . The app.build.js file will typically look something like…
({ appDir: "../", baseUrl: "scripts/", dir: "../../app-build/", //Comment out the optimize line if you want //the code minified by UglifyJS //optimize: "none", paths: { "jquery": "require-jquery", "underscore": "libs/underscore", "backbone": "libs/backbone" }, modules: [ //Optimize the require-jquery.js file by applying any minification //that is desired via the optimize: setting above. { name: "require-jquery" }, //Optimize the application files. Exclude jQuery since it is //included already in require-jquery.js { name: "main", exclude: ["jquery"] } ] })
This file is used to tell r.js how to build the application. There is help on http://requirejs.org/docs/optimization.html about the file. Basically r.js will look at the main module, and trace all of it’s dependencies, minify them, and combine them for you into one fun happy little minified package at your dir from the app.build.js file.
To run the optimizer in Java, go to the file above the root of app where r.js is located and use the command…
java -cp c:/path/to/rhino/js.jar;c:/path/to/compiler/compiler.jar; org.mozilla.javascript.tools.shell.Main r.js -o "c:/path/to/your/app/scripts/app.build.js"
If you are using node, then run this command…
node r.js app/scripts/app.build.js
And next thing you know, you’ll have an app-build folder with a shiny new minifed version of your app. The node optimizer is quite a bit faster than the java one, so I’d use that one.
So, that’s basically it! If you have any questions, let me know as I spent many hours figuring this thing out. The biggest help was when jQuery and UnderscoreJS both added AMD, and then once the Backbone AMD branches came out, it was much easier!
I have a simple working app called Savefavs up here at js.jcreamerlive.com and the source for it on Github. So take a look at that for a little more advanced stuff
I also have a Starter application here https://github.com/jcreamer898/RequireJS-Backbone-Starter that you can fork and use to build your own app.