PageRenderTime 55ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/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. Boilerplates 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.
  3. That 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.
  4. This 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.
  5. [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.
  6. [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.
  7. ![](img/bbb.png)
  8. Out of the box, BB and Grunt-BBB provide provide us with:
  9. * 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
  10. * Boilerplate and scaffolding support, allowing us to spend minimal time writing boilerplate for modules, collections and so on.
  11. * A build tool for template pre-compilation and, concatenation & minification of all our libraries, application code and stylesheets
  12. * A Lightweight node.js webserver
  13. Notes on build tool steps:
  14. * 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.
  15. * 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.
  16. * 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.
  17. ## Getting Started
  18. ### Backbone Boilerplate and Grunt-BBB
  19. To 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.
  20. We can install Grunt-bBB via NPM by running:
  21. ```shell
  22. npm install -g bbb
  23. ```
  24. That's it. We should now be good to go.
  25. A typical workflow for using grunt-bbb, which we will use later on is:
  26. * Initialize a new project (`bbb init`)
  27. * Add new modules and templates (`bbb init:module`)
  28. * Preview changes using the built in server (`bbb server`)
  29. * Run the build tool (`bbb build`)
  30. * Lint JavaScript, compile templates, build your application using r.js, minify CSS and JavaScript (using `bbb release`)
  31. ## Creating a new project
  32. Let'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:
  33. ```shell
  34. $ bbb init
  35. Running "init" task
  36. This task will create one or more files in the current directory, based on the
  37. environment and the answers to a few questions. Note that answering "?" to any
  38. question will show question-specific help and answering "none" to most questions
  39. will leave its value blank.
  40. "bbb" template notes:
  41. This tool will help you install, configure, build, and maintain your Backbone
  42. Boilerplate project.
  43. Writing app/app.js...OK
  44. Writing app/config.js...OK
  45. Writing app/main.js...OK
  46. Writing app/router.js...OK
  47. Writing app/styles/index.css...OK
  48. Writing favicon.ico...OK
  49. Writing grunt.js...OK
  50. Writing index.html...OK
  51. Writing package.json...OK
  52. Writing readme.md...OK
  53. Writing test/jasmine/index.html...OK
  54. Writing test/jasmine/spec/example.js...OK
  55. Writing test/jasmine/vendor/jasmine-html.js...OK
  56. Writing test/jasmine/vendor/jasmine.css...OK
  57. Writing test/jasmine/vendor/jasmine.js...OK
  58. Writing test/jasmine/vendor/jasmine_favicon.png...OK
  59. Writing test/jasmine/vendor/MIT.LICENSE...OK
  60. Writing test/qunit/index.html...OK
  61. Writing test/qunit/tests/example.js...OK
  62. Writing test/qunit/vendor/qunit.css...OK
  63. Writing test/qunit/vendor/qunit.js...OK
  64. Writing vendor/h5bp/css/main.css...OK
  65. Writing vendor/h5bp/css/normalize.css...OK
  66. Writing vendor/jam/backbone/backbone.js...OK
  67. Writing vendor/jam/backbone/package.json...OK
  68. Writing vendor/jam/backbone.layoutmanager/backbone.layoutmanager.js...OK
  69. Writing vendor/jam/backbone.layoutmanager/package.json...OK
  70. Writing vendor/jam/jquery/jquery.js...OK
  71. Writing vendor/jam/jquery/package.json...OK
  72. Writing vendor/jam/lodash/lodash.js...OK
  73. Writing vendor/jam/lodash/lodash.min.js...OK
  74. Writing vendor/jam/lodash/lodash.underscore.min.js...OK
  75. Writing vendor/jam/lodash/package.json...OK
  76. Writing vendor/jam/require.config.js...OK
  77. Writing vendor/jam/require.js...OK
  78. Writing vendor/js/libs/almond.js...OK
  79. Writing vendor/js/libs/require.js...OK
  80. Initialized from template "bbb".
  81. Done, without errors.
  82. ```
  83. Let's review what has been generated.
  84. ### index.html
  85. This 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.
  86. ```html
  87. <!doctype html>
  88. <html lang="en">
  89. <head>
  90. <meta charset="utf-8">
  91. <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  92. <meta name="viewport" content="width=device-width,initial-scale=1">
  93. <title>Backbone Boilerplate</title>
  94. <!-- Application styles. -->
  95. <!--(if target dummy)><!-->
  96. <link rel="stylesheet" href="/app/styles/index.css">
  97. <!--<!(endif)-->
  98. </head>
  99. <body>
  100. <!-- Application container. -->
  101. <main role="main" id="main"></main>
  102. <!-- Application source. -->
  103. <!--(if target dummy)><!-->
  104. <script data-main="/app/config" src="/vendor/js/libs/require.js"></script>
  105. <!--<!(endif)-->
  106. </body>
  107. </html>
  108. ```
  109. RequireJS - 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:
  110. ```
  111. <script data-main="/app/config" src="/vendor/js/libs/require.js"></script>
  112. ```
  113. The `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.
  114. ### config.js
  115. A 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.
  116. This is what the config file in Backbone Boilerplate looks like:
  117. ```javascript
  118. // Set the require.js configuration for your application.
  119. require.config({
  120. // Initialize the application with the main application file and the JamJS
  121. // generated configuration file.
  122. deps: ["../vendor/jam/require.config", "main"],
  123. paths: {
  124. // Put paths here.
  125. },
  126. shim: {
  127. // Put shims here.
  128. }
  129. });
  130. ```
  131. The 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.
  132. 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.
  133. The 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.
  134. After 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.
  135. Going back to `deps`, the contents of our `require.config` file can be seen below.
  136. ```javascript
  137. var jam = {
  138. "packages": [
  139. {
  140. "name": "backbone",
  141. "location": "../vendor/jam/backbone",
  142. "main": "backbone.js"
  143. },
  144. {
  145. "name": "backbone.layoutmanager",
  146. "location": "../vendor/jam/backbone.layoutmanager",
  147. "main": "backbone.layoutmanager.js"
  148. },
  149. {
  150. "name": "jquery",
  151. "location": "../vendor/jam/jquery",
  152. "main": "jquery.js"
  153. },
  154. {
  155. "name": "lodash",
  156. "location": "../vendor/jam/lodash",
  157. "main": "./lodash.js"
  158. }
  159. ],
  160. "version": "0.2.11",
  161. "shim": {
  162. "backbone": {
  163. "deps": [
  164. "jquery",
  165. "lodash"
  166. ],
  167. "exports": "Backbone"
  168. },
  169. "backbone.layoutmanager": {
  170. "deps": [
  171. "jquery",
  172. "backbone",
  173. "lodash"
  174. ],
  175. "exports": "Backbone.LayoutManager"
  176. }
  177. }
  178. };
  179. ```
  180. The `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.
  181. Under the `packages` array, a number of dependencies are specified for inclusion, such as Backbone, the Backbone.LayoutManager plugin, jQuery and Lo-dash.
  182. For 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.
  183. Additional packages you install using Jam will have a corresponding entry added to `packages`.
  184. ### main.js
  185. Next, 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.
  186. The 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.
  187. ```javascript
  188. require([
  189. // Application.
  190. "app",
  191. // Main Router.
  192. "router"
  193. ],
  194. function(app, Router) {
  195. // Define your master router on the application namespace and trigger all
  196. // navigation from this instance.
  197. app.router = new Router();
  198. // Trigger the initial route and enable HTML5 History API support, set the
  199. // root folder to '/' by default. Change in app.js.
  200. Backbone.history.start({ pushState: true, root: app.root });
  201. // All navigation that is relative should be passed through the navigate
  202. // method, to be processed by the router. If the link has a `data-bypass`
  203. // attribute, bypass the delegation completely.
  204. $(document).on("click", "a[href]:not([data-bypass])", function(evt) {
  205. // Get the absolute anchor href.
  206. var href = { prop: $(this).prop("href"), attr: $(this).attr("href") };
  207. // Get the absolute root.
  208. var root = location.protocol + "//" + location.host + app.root;
  209. // Ensure the root is part of the anchor href, meaning it's relative.
  210. if (href.prop.slice(0, root.length) === root) {
  211. // Stop the default event to ensure the link will not cause a page
  212. // refresh.
  213. evt.preventDefault();
  214. // `Backbone.history.navigate` is sufficient for all Routers and will
  215. // trigger the correct events. The Router's internal `navigate` method
  216. // calls this anyways. The fragment is sliced from the root.
  217. Backbone.history.navigate(href.attr, true);
  218. }
  219. });
  220. });
  221. ```
  222. Inline, 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.
  223. ### app.js
  224. Let 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.
  225. In 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.
  226. ```javascript
  227. define([
  228. "backbone.layoutmanager"
  229. ], function() {
  230. // Provide a global location to place configuration settings and module
  231. // creation.
  232. var app = {
  233. // The root path to run the application.
  234. root: "/"
  235. };
  236. // Localize or create a new JavaScript Template object.
  237. var JST = window.JST = window.JST || {};
  238. // Configure LayoutManager with Backbone Boilerplate defaults.
  239. Backbone.LayoutManager.configure({
  240. // Allow LayoutManager to augment Backbone.View.prototype.
  241. manage: true,
  242. prefix: "app/templates/",
  243. fetch: function(path) {
  244. // Concatenate the file extension.
  245. path = path + ".html";
  246. // If cached, use the compiled template.
  247. if (JST[path]) {
  248. return JST[path];
  249. }
  250. // Put fetch into `async-mode`.
  251. var done = this.async();
  252. // Seek out the template asynchronously.
  253. $.get(app.root + path, function(contents) {
  254. done(JST[path] = _.template(contents));
  255. });
  256. }
  257. });
  258. // Mix Backbone.Events, modules, and layout management into the app object.
  259. return _.extend(app, {
  260. // Create a custom object with a nested Views object.
  261. module: function(additionalProps) {
  262. return _.extend({ Views: {} }, additionalProps);
  263. },
  264. // Helper for using layouts.
  265. useLayout: function(name, options) {
  266. // Enable variable arity by allowing the first argument to be the options
  267. // object and omitting the name argument.
  268. if (_.isObject(name)) {
  269. options = name;
  270. }
  271. // Ensure options is an object.
  272. options = options || {};
  273. // If a name property was specified use that as the template.
  274. if (_.isString(name)) {
  275. options.template = name;
  276. }
  277. // Create a new Layout with options.
  278. var layout = new Backbone.Layout(_.extend({
  279. el: "#main"
  280. }, options));
  281. // Cache the refererence.
  282. return this.layout = layout;
  283. }
  284. }, Backbone.Events);
  285. });
  286. ```
  287. Note: 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.
  288. ### Creating Backbone Boilerplate Modules
  289. Not to be confused with simply being just an AMD module, a Backbone Boilerplate `module` is a script composed of a:
  290. * Model
  291. * Collection
  292. * Views (optional)
  293. We can easily create a new Boilerplate module using `grunt-bbb` once again using `init`:
  294. ```shell
  295. # Create a new module
  296. $ bbb init:module
  297. # Grunt prompt
  298. Please answer the following:
  299. [?] Module Name foo
  300. [?] Do you need to make any changes to the above before continuing? (y/N)
  301. Writing app/modules/foo.js...OK
  302. Writing app/styles/foo.styl...OK
  303. Writing app/templates/foo.html...OK
  304. Initialized from template "module".
  305. Done, without errors.
  306. ```
  307. This will generate a module `foo.js` as follows:
  308. ```javascript
  309. // Foo module
  310. define([
  311. // Application.
  312. "app"
  313. ],
  314. // Map dependencies from above array.
  315. function(app) {
  316. // Create a new module.
  317. var Foo = app.module();
  318. // Default Model.
  319. Foo.Model = Backbone.Model.extend({
  320. });
  321. // Default Collection.
  322. Foo.Collection = Backbone.Collection.extend({
  323. model: Foo.Model
  324. });
  325. // Default View.
  326. Foo.Views.Layout = Backbone.Layout.extend({
  327. template: "foo"
  328. });
  329. // Return the module for AMD compliance.
  330. return Foo;
  331. });
  332. ```
  333. Notice how boilerplate code for a model, collection and view have been scaffolded out for us.
  334. Optionally, 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:
  335. ```javascript
  336. // Foo module
  337. define([
  338. // Application.
  339. "app",
  340. // Plugins
  341. 'plugins/backbone-localstorage'
  342. ],
  343. // Map dependencies from above array.
  344. function(app) {
  345. // Create a new module.
  346. var Foo = app.module();
  347. // Default Model.
  348. Foo.Model = Backbone.Model.extend({
  349. // Save all of the items under the `"foo"` namespace.
  350. localStorage: new Store('foo-backbone'),
  351. });
  352. // Default Collection.
  353. Foo.Collection = Backbone.Collection.extend({
  354. model: Foo.Model
  355. });
  356. // Default View.
  357. Foo.Views.Layout = Backbone.Layout.extend({
  358. template: "foo"
  359. });
  360. // Return the module for AMD compliance.
  361. return Foo;
  362. });
  363. ```
  364. ### router.js
  365. Finally, 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.
  366. ```javascript
  367. define([
  368. // Application.
  369. "app"
  370. ],
  371. function(app) {
  372. // Defining the application router, you can attach sub routers here.
  373. var Router = Backbone.Router.extend({
  374. routes: {
  375. "": "index"
  376. },
  377. index: function() {
  378. }
  379. });
  380. return Router;
  381. });
  382. ```
  383. If 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:
  384. ```javascript
  385. define([
  386. // Application.
  387. 'app',
  388. // Modules
  389. 'modules/foo'
  390. ],
  391. function(app, Foo) {
  392. // Defining the application router, you can attach sub routers here.
  393. var Router = Backbone.Router.extend({
  394. routes: {
  395. '': 'index'
  396. },
  397. index: function() {
  398. // Create a new Collection
  399. var collection = new Foo.Collection();
  400. // Use and configure a 'main' layout
  401. app.useLayout('main').setViews({
  402. // Attach the bar View into the content View
  403. '.bar': new Foo.Views.Bar({
  404. collection: collection
  405. })
  406. }).render();
  407. }
  408. });
  409. // Fetch data (e.g., from localStorage)
  410. collection.fetch();
  411. return Router;
  412. });
  413. ```
  414. ## Other Useful Tools & Projects
  415. When 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.
  416. ### Yeoman
  417. If 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).
  418. ![](img/yeoman.png)
  419. Yeoman 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).
  420. Where 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.
  421. ![](img/bower.png)
  422. You may also be interested in [Brunch](http://brunch.io/), a similar project which uses skeleton boilerplates to generate new applications.
  423. ### Backbone DevTools
  424. When building an application with Backbone, there's some additional tooling available for your day-to-day debugging workflow.
  425. Backbone 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.
  426. A 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.
  427. ![](img/bbdevtools.jpg)
  428. At the time of writing, the project is currently available on [GitHub](https://github.com/spect88/backbone-devtools).
  429. ## Conclusions
  430. In this section we reviewed Backbone Boilerplate and learned how to use the `bbb` tool to help us scaffold out our application.
  431. If 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.
  432. These 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.
  433. For 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.