PageRenderTime 2ms CodeModel.GetById 4ms app.highlight 29ms RepoModel.GetById 1ms app.codeStats 0ms

/chapters/11-bbb.md

https://github.com/bicccio/backbone-fundamentals
Markdown | 594 lines | 433 code | 161 blank | 0 comment | 0 complexity | ff80e95b3abd6c2486cda59f8fde5347 MD5 | raw file
  1# Backbone Boilerplate And Grunt-BBB
  2
  3Boilerplates provide us a starting point for working on projects. They're a base for building upon using the minimum required code to get something functional put together. When you're working on a new Backbone application, a new Model typically only takes a few lines of code to get working. 
  4
  5That alone probably isn't enough however, as you'll need a Collection to group those models, a View to render them and perhaps a router if you're looking to making specific views of your Collection data bookmarkable. If you're starting on a completely fresh project, you may also need a build process in place to produce an optimized version of your app that can be pushed to production.
  6
  7This is where boilerplate solutions are useful. Rather than having to manually write out the initial code for each piece of your Backbone app, a boilerplate could do this for you, also ideally taking care of the build process.
  8
  9[Backbone Boilerplate](https://github.com/tbranyen/backbone-boilerplate/) (or just BB) provides just this. It is an excellent set of best practices and utilities for building Backbone.js applications, created by Backbone contributor [Tim Branyen](https://github.com/tbranyen). He took the the gotchas, pitfalls and common tasks he ran into while heavily using Backbone to build apps and crafted BB as a result of this experience.
 10
 11[Grunt-BBB or Boilerplate Build Buddy](https://github.com/backbone-boilerplate/grunt-bbb) is the companion tool to BB, which offers scaffolding, file watcher and build capabilities. Used together with BB it provides an excellent base for quickly starting new Backbone applications.
 12
 13![](img/bbb.png)
 14
 15Out of the box, BB and Grunt-BBB provide provide us with:
 16
 17* Backbone, [Lodash](https://github.com/bestiejs/lodash) (an [Underscore.js](http://underscorejs.org/) alternative) and [jQuery](http://jquery.com) with an [HTML5 Boilerplate](http://html5boilerplate.com) foundation
 18* Boilerplate and scaffolding support, allowing us to spend minimal time writing boilerplate for modules, collections and so on.
 19* A build tool for template pre-compilation and, concatenation & minification of all our libraries, application code and stylesheets
 20* A Lightweight node.js webserver
 21
 22Notes on build tool steps:
 23
 24* Template pre-compilation: using a template library such as Underscore micro-templating or Handlebars.js generally involves three steps: (1) reading a raw template, (2) compiling it into a JavaScript function and (3) running the compiled template with your desired data. Precompiling eliminates the second step from runtime, by moving this process into a build step.
 25* Concatenation is the process of combining a number of assets (in our case, script files) into a single (or fewer number) of files to reduce the number of HTTP requests required to obtain them.
 26* Minification is the process of removing unnecessary characters (e.g white space, new lines, comments) from code and compressing it to reduce the overall size of the scripts being served.
 27
 28## Getting Started
 29
 30### Backbone Boilerplate and Grunt-BBB
 31
 32To get started we're going to install Grunt-BBB, which will include Backbone Boilerplate and any third-party dependencies it might need such as the Grunt build tool. 
 33
 34We can install Grunt-bBB via NPM by running:
 35
 36```shell
 37npm install -g bbb
 38```
 39
 40That's it. We should now be good to go.
 41
 42A typical workflow for using grunt-bbb, which we will use later on is:
 43
 44* Initialize a new project (`bbb init`)
 45* Add new modules and templates (`bbb init:module`)
 46* Preview changes using the built in server (`bbb server`)
 47* Run the build tool (`bbb build`)
 48* Lint JavaScript, compile templates, build your application using r.js, minify CSS and JavaScript (using `bbb release`)
 49
 50## Creating a new project
 51
 52Let's create a new directory for our project and run `bbb init` to kick things off. A number of project sub-directories and files will be stubbed out for us, as shown below:
 53
 54```shell
 55$ bbb init
 56Running "init" task
 57This task will create one or more files in the current directory, based on the
 58environment and the answers to a few questions. Note that answering "?" to any
 59question will show question-specific help and answering "none" to most questions
 60will leave its value blank.
 61
 62"bbb" template notes:
 63This tool will help you install, configure, build, and maintain your Backbone
 64Boilerplate project.
 65Writing app/app.js...OK
 66Writing app/config.js...OK
 67Writing app/main.js...OK
 68Writing app/router.js...OK
 69Writing app/styles/index.css...OK
 70Writing favicon.ico...OK
 71Writing grunt.js...OK
 72Writing index.html...OK
 73Writing package.json...OK
 74Writing readme.md...OK
 75Writing test/jasmine/index.html...OK
 76Writing test/jasmine/spec/example.js...OK
 77Writing test/jasmine/vendor/jasmine-html.js...OK
 78Writing test/jasmine/vendor/jasmine.css...OK
 79Writing test/jasmine/vendor/jasmine.js...OK
 80Writing test/jasmine/vendor/jasmine_favicon.png...OK
 81Writing test/jasmine/vendor/MIT.LICENSE...OK
 82Writing test/qunit/index.html...OK
 83Writing test/qunit/tests/example.js...OK
 84Writing test/qunit/vendor/qunit.css...OK
 85Writing test/qunit/vendor/qunit.js...OK
 86Writing vendor/h5bp/css/main.css...OK
 87Writing vendor/h5bp/css/normalize.css...OK
 88Writing vendor/jam/backbone/backbone.js...OK
 89Writing vendor/jam/backbone/package.json...OK
 90Writing vendor/jam/backbone.layoutmanager/backbone.layoutmanager.js...OK
 91Writing vendor/jam/backbone.layoutmanager/package.json...OK
 92Writing vendor/jam/jquery/jquery.js...OK
 93Writing vendor/jam/jquery/package.json...OK
 94Writing vendor/jam/lodash/lodash.js...OK
 95Writing vendor/jam/lodash/lodash.min.js...OK
 96Writing vendor/jam/lodash/lodash.underscore.min.js...OK
 97Writing vendor/jam/lodash/package.json...OK
 98Writing vendor/jam/require.config.js...OK
 99Writing vendor/jam/require.js...OK
100Writing vendor/js/libs/almond.js...OK
101Writing vendor/js/libs/require.js...OK
102
103Initialized from template "bbb".
104
105Done, without errors.
106```
107
108Let's review what has been generated.
109
110### index.html
111
112This is a fairly standard stripped-down HTML5 Boilerplate foundation with the notable exception of including [RequireJS](http://requirejs.org) at the bottom of the page.
113
114```html
115<!doctype html>
116<html lang="en">
117<head>
118  <meta charset="utf-8">
119  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
120  <meta name="viewport" content="width=device-width,initial-scale=1">
121
122  <title>Backbone Boilerplate</title>
123
124  <!-- Application styles. -->
125  <!--(if target dummy)><!-->
126  <link rel="stylesheet" href="/app/styles/index.css">
127  <!--<!(endif)-->
128</head>
129<body>
130  <!-- Application container. -->
131  <main role="main" id="main"></main>
132
133  <!-- Application source. -->
134  <!--(if target dummy)><!-->
135  <script data-main="/app/config" src="/vendor/js/libs/require.js"></script>
136  <!--<!(endif)-->
137
138</body>
139</html>
140```
141
142RequireJS - the [AMD](https://github.com/amdjs/amdjs-api/wiki/AMD) (Asynchronous Module Definition) module and script loader - will assist us with managing the modules in our application. We've already covered it in the last chapter, but let's recap what this particular block does in terms of the Boilerplate:
143
144```
145<script data-main="/app/config" src="/vendor/js/libs/require.js"></script>
146```
147
148The `data-main` attribute is used to inform RequireJS to load `app/config.js` (a configuration object) after it has finished loading itself. You'll notice that we've omitted the `.js` extension here as RequireJS can automatically add this for us, however it will respect your paths if we do choose to include it regardless. Let's now look at the config file being referenced.
149
150### config.js
151
152A RequireJS configuration object allows us to specify aliases and paths for dependencies we're likely to reference often (e.g., jQuery), bootstrap properties like our base application URL, and `shim` libraries that don't support AMD natively.
153
154This is what the config file in Backbone Boilerplate looks like:
155
156```javascript
157// Set the require.js configuration for your application.
158require.config({
159
160  // Initialize the application with the main application file and the JamJS
161  // generated configuration file.
162  deps: ["../vendor/jam/require.config", "main"],
163
164  paths: {
165    // Put paths here.
166  },
167
168  shim: {
169    // Put shims here.
170  }
171
172});
173```
174
175The first option defined in the above config is `deps: ["../vendor/jam/require.config", "main"]`. This informs RequireJS to load up additonal RequireJS configuration as well a a main.js file, which is considered the entry point for our application.
176
177 You may notice that we haven't specified any other path information for `main`. Require will infer the default `baseUrl` using the path from our `data-main` attribute in index.html. In other words, our `baseUrl` is `app/` and any scripts we require will be loaded relative to this location. We could use the `baseUrl` option to override this default if we wanted to use a different location.
178
179The next block is `paths`, which we can use to specify paths relative to the `baseUrl` as well as the paths/aliases to dependencies we're likely to regularly reference.
180
181After this comes `shim`, an important part of our RequireJS configuration which allows us to load libraries which are not AMD compliant. The basic idea here is that rather than requiring all libraries to implement support for AMD, the `shim` takes care of the hard work for us.
182
183Going back to `deps`, the contents of our `require.config` file can be seen below. 
184
185```javascript
186var jam = {
187    "packages": [
188        {
189            "name": "backbone",
190            "location": "../vendor/jam/backbone",
191            "main": "backbone.js"
192        },
193        {
194            "name": "backbone.layoutmanager",
195            "location": "../vendor/jam/backbone.layoutmanager",
196            "main": "backbone.layoutmanager.js"
197        },
198        {
199            "name": "jquery",
200            "location": "../vendor/jam/jquery",
201            "main": "jquery.js"
202        },
203        {
204            "name": "lodash",
205            "location": "../vendor/jam/lodash",
206            "main": "./lodash.js"
207        }
208    ],
209    "version": "0.2.11",
210    "shim": {
211        "backbone": {
212            "deps": [
213                "jquery",
214                "lodash"
215            ],
216            "exports": "Backbone"
217        },
218        "backbone.layoutmanager": {
219            "deps": [
220                "jquery",
221                "backbone",
222                "lodash"
223            ],
224            "exports": "Backbone.LayoutManager"
225        }
226    }
227};
228
229```
230
231The `jam` object is to support configuration of [Jam](http://jamjs.org/) - a package manager for the front-end which helps instal, upgrade and configurate the dependencies used by your project. It is currently the package manager of choice for Backbone Boilerplate.
232
233Under the `packages` array, a number of dependencies are specified for inclusion, such as Backbone, the Backbone.LayoutManager plugin, jQuery and Lo-dash. 
234
235For those curious about [Backbone.LayoutManager](https://github.com/tbranyen/backbone.layoutmanager), it's a Backbone plugin that provides a foundation for assembling layouts and views within Backbone.
236
237Additional packages you install using Jam will have a corresponding entry added to `packages`.
238
239### main.js
240
241Next, we have `main.js`, which defines the entry point for our application. We use a global `require()` method to load an array containing any other scripts needed, such as our application `app.js` and our main router `router.js`. Note that most of the time, we will only use `require()` for bootstrapping an application and a similar method called `define()` for all other purposes.
242
243The function defined after our array of dependencies is a callback which doesn't fire until these scripts have loaded. Notice how we're able to locally alias references to "app" and "router" as `app` and `Router` for convenience.
244
245```javascript
246require([
247  // Application.
248  "app",
249
250  // Main Router.
251  "router"
252],
253
254function(app, Router) {
255
256  // Define your master router on the application namespace and trigger all
257  // navigation from this instance.
258  app.router = new Router();
259
260  // Trigger the initial route and enable HTML5 History API support, set the
261  // root folder to '/' by default.  Change in app.js.
262  Backbone.history.start({ pushState: true, root: app.root });
263
264  // All navigation that is relative should be passed through the navigate
265  // method, to be processed by the router. If the link has a `data-bypass`
266  // attribute, bypass the delegation completely.
267  $(document).on("click", "a[href]:not([data-bypass])", function(evt) {
268    // Get the absolute anchor href.
269    var href = { prop: $(this).prop("href"), attr: $(this).attr("href") };
270    // Get the absolute root.
271    var root = location.protocol + "//" + location.host + app.root;
272
273    // Ensure the root is part of the anchor href, meaning it's relative.
274    if (href.prop.slice(0, root.length) === root) {
275      // Stop the default event to ensure the link will not cause a page
276      // refresh.
277      evt.preventDefault();
278
279      // `Backbone.history.navigate` is sufficient for all Routers and will
280      // trigger the correct events. The Router's internal `navigate` method
281      // calls this anyways.  The fragment is sliced from the root.
282      Backbone.history.navigate(href.attr, true);
283    }
284  });
285
286});
287
288```
289
290Inline, Backbone Boilerplate includes boilerplate code for initializing our router with HTML5 History API support and handling other navigation scenarios, so we don't have to.
291
292### app.js
293
294Let us now look at our `app.js` module. Typically, in non-Backbone Boilerplate applications, an `app.js` file may contain the core logic or module references needed to kick start an app.
295
296In this case however, this file is used to define templating and layout configuration options as well as utilities for consuming layouts. To a beginner, this might look like a lot of code to comprehend, but the good news is that for basic apps, you're unlikely to need to heavily modify this. Instead, you'll be more concerned with modules for your app, which we'll look at next.
297
298```javascript
299define([
300  "backbone.layoutmanager"
301], function() {
302
303  // Provide a global location to place configuration settings and module
304  // creation.
305  var app = {
306    // The root path to run the application.
307    root: "/"
308  };
309
310  // Localize or create a new JavaScript Template object.
311  var JST = window.JST = window.JST || {};
312
313  // Configure LayoutManager with Backbone Boilerplate defaults.
314  Backbone.LayoutManager.configure({
315    // Allow LayoutManager to augment Backbone.View.prototype.
316    manage: true,
317
318    prefix: "app/templates/",
319
320    fetch: function(path) {
321      // Concatenate the file extension.
322      path = path + ".html";
323
324      // If cached, use the compiled template.
325      if (JST[path]) {
326        return JST[path];
327      }
328
329      // Put fetch into `async-mode`.
330      var done = this.async();
331
332      // Seek out the template asynchronously.
333      $.get(app.root + path, function(contents) {
334        done(JST[path] = _.template(contents));
335      });
336    }
337  });
338
339  // Mix Backbone.Events, modules, and layout management into the app object.
340  return _.extend(app, {
341    // Create a custom object with a nested Views object.
342    module: function(additionalProps) {
343      return _.extend({ Views: {} }, additionalProps);
344    },
345
346    // Helper for using layouts.
347    useLayout: function(name, options) {
348      // Enable variable arity by allowing the first argument to be the options
349      // object and omitting the name argument.
350      if (_.isObject(name)) {
351        options = name;
352      }
353
354      // Ensure options is an object.
355      options = options || {};
356
357      // If a name property was specified use that as the template.
358      if (_.isString(name)) {
359        options.template = name;
360      }
361
362      // Create a new Layout with options.
363      var layout = new Backbone.Layout(_.extend({
364        el: "#main"
365      }, options));
366
367      // Cache the refererence.
368      return this.layout = layout;
369    }
370  }, Backbone.Events);
371
372});
373
374```
375
376Note: JST stands for JavaScript templates and generally refers to templates which have been (or will be) precompiled as part of a build step. When running `bbb release` or `bbb debug`, Underscore/Lo-dash templates will be precompiled to avoid the need to compile them at runtime within the browser.
377
378### Creating Backbone Boilerplate Modules
379
380Not to be confused with simply being just an AMD module, a Backbone Boilerplate `module` is a script composed of a:
381
382* Model
383* Collection
384* Views (optional)
385
386We can easily create a new Boilerplate module using `grunt-bbb` once again using `init`:
387
388```shell
389# Create a new module
390$ bbb init:module
391
392# Grunt prompt
393Please answer the following:
394[?] Module Name foo
395[?] Do you need to make any changes to the above before continuing? (y/N) 
396
397Writing app/modules/foo.js...OK
398Writing app/styles/foo.styl...OK
399Writing app/templates/foo.html...OK
400
401Initialized from template "module".
402
403Done, without errors.
404```
405
406This will generate a module `foo.js` as follows:
407
408```javascript
409// Foo module
410define([
411  // Application.
412  "app"
413],
414
415// Map dependencies from above array.
416function(app) {
417
418  // Create a new module.
419  var Foo = app.module();
420
421  // Default Model.
422  Foo.Model = Backbone.Model.extend({
423  
424  });
425
426  // Default Collection.
427  Foo.Collection = Backbone.Collection.extend({
428    model: Foo.Model
429  });
430
431  // Default View.
432  Foo.Views.Layout = Backbone.Layout.extend({
433    template: "foo"
434  });
435
436  // Return the module for AMD compliance.
437  return Foo;
438
439});
440```
441
442Notice how boilerplate code for a model, collection and view have been scaffolded out for us.
443
444Optionally, we may also wish to include references to plugins such as the Backbone LocalStorage or Offline adapters. One clean way of including a plugin in the above boilerplate could be:
445
446```javascript
447// Foo module
448define([
449  // Application.
450  "app",
451  // Plugins
452  'plugins/backbone-localstorage'
453],
454
455// Map dependencies from above array.
456function(app) {
457
458  // Create a new module.
459  var Foo = app.module();
460
461  // Default Model.
462  Foo.Model = Backbone.Model.extend({
463    // Save all of the items under the `"foo"` namespace.
464    localStorage: new Store('foo-backbone'),
465  });
466
467  // Default Collection.
468  Foo.Collection = Backbone.Collection.extend({
469    model: Foo.Model
470  });
471
472  // Default View.
473  Foo.Views.Layout = Backbone.Layout.extend({
474    template: "foo"
475  });
476
477  // Return the module for AMD compliance.
478  return Foo;
479
480});
481```
482
483### router.js
484
485Finally, let's look at our application router which is used for handling navigation. The default router Backbone Boilerplate generates for us includes sane defaults out of the box and can be easily extended.
486
487```javascript
488define([
489  // Application.
490  "app"
491],
492
493function(app) {
494
495  // Defining the application router, you can attach sub routers here.
496  var Router = Backbone.Router.extend({
497    routes: {
498      "": "index"
499    },
500
501    index: function() {
502
503    }
504  });
505
506  return Router;
507
508});
509```
510
511If however we would like to execute some module-specific logic, when the page loads (i.e when a user hits the default route), we can pull in a module as a dependency and optionally use the Backbone LayoutManager to attach Views to our layout as follows:
512
513```javascript
514define([
515  // Application.
516  'app',
517
518  // Modules
519  'modules/foo'
520],
521
522function(app, Foo) {
523
524  // Defining the application router, you can attach sub routers here.
525  var Router = Backbone.Router.extend({
526    routes: {
527      '': 'index'
528    },
529
530    index: function() {
531            // Create a new Collection
532            var collection = new Foo.Collection();
533
534            // Use and configure a 'main' layout
535            app.useLayout('main').setViews({
536                    // Attach the bar View into the content View
537                    '.bar': new Foo.Views.Bar({
538                            collection: collection
539                    })
540             }).render();
541    }
542  });
543
544  // Fetch data (e.g., from localStorage)
545  collection.fetch();
546
547  return Router;
548
549});
550```
551
552## Other Useful Tools & Projects
553
554When working with Backbone, you usually need to write a number of different classes and files for your application. Scaffolding tools such as Grunt-BBB can help automate this process by generating basic boilerplates for the files you need for you.
555
556### Yeoman
557
558If you appreciated Grunt-BBB but would like to explore a tool for assisting with your broader development workflow, I'm happy to recommend a tool I've been helping with called [Yeoman](http://yeoman.io).
559
560![](img/yeoman.png)
561
562Yeoman is a workflow comprised of a collection of tools and best practices for helping you develop more efficiently. It's comprised of yo (a scaffolding tool), [Grunt](http://gruntjs.com)(a build tool) and [Bower](http://bower.io) (a client-side package manager). 
563
564Where Grunt-BBB focuses on offering an opionated start for Backbone projects, Yeoman allows you to scaffold apps using Backbone (or other frameworks), get Backbone plugins directly from the command-line and compile your CoffeeScript, Sass or other abstractions without additional effort.
565
566![](img/bower.png)
567
568You may also be interested in [Brunch](http://brunch.io/), a similar project which uses skeleton boilerplates to generate new applications.
569
570### Backbone DevTools
571
572When building an application with Backbone, there's some additional tooling available for your day-to-day debugging workflow.
573
574Backbone DevTools was created to help with this and is a Chrome DevTools extension allowing you to inspect events, syncs, View-DOM bindings and what objects have been instantiated. 
575
576A useful View hierarchy is displayed in the Elements panel. Also, when you inspect a DOM element the closest View will be exposed via $view in the console.
577
578![](img/bbdevtools.jpg)
579
580At the time of writing, the project is currently available on [GitHub](https://github.com/spect88/backbone-devtools).
581
582## Conclusions
583
584In this section we reviewed Backbone Boilerplate and learned how to use the `bbb` tool to help us scaffold out our application.
585
586If you would like to learn more about how this project helps structure your app, BBB includes some built-in boilerplate sample apps that can be easily generated for review.
587
588These include a boilerplate tutorial project (`bbb init:tutorial`) and an implementation of my [TodoMVC](http://todomvc) project (`bbb init:todomvc`). I recommend checking these out as they'll provide you with a more complete picture of how Backbone Boilerplate, its templates, and so on fit into the overall setup for a web app.
589
590For more about Grunt-BBB, remember to take a look at the official project [repository](https://github.com/backbone-boilerplate/grunt-bbb). There is also a related [slide-deck](https://dl.dropbox.com/u/79007/talks/Modern_Web_Applications/slides/index.html) available for those interested in reading more.
591
592
593
594