PageRenderTime 4ms CodeModel.GetById 5ms app.highlight 67ms RepoModel.GetById 1ms app.codeStats 0ms

/chapters/06-extensions.md

https://github.com/bbt123/backbone-fundamentals
Markdown | 1178 lines | 848 code | 330 blank | 0 comment | 0 complexity | 4625b7c8df867e8825ba2c40fea51aa6 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1# Backbone Extensions
   2
   3Backbone is flexible, simple, and powerful. However, you may find that the complexity of the application you are working on requires more than what it provides out of the box. There are certain concerns which it just doesn't address directly as one of its goals is to be minimalist.
   4
   5Take for example Views, which provide a default `render` method which does nothing and produces no real results when called, despite most implementations using it to generate the HTML that the view manages. Also, Models and Collections have no built-in way of handling nested hierarchies - if you require this functionality, you need to write it yourself or use a plugin.
   6
   7In these cases, there are many existing Backbone plugins which can provide advanced solutions for large-scale Backbone apps. A fairly complete list of plugins and frameworks available can be found on the Backbone [wiki](https://github.com/documentcloud/backbone/wiki/Extensions%2C-Plugins%2C-Resources). Using these add-ons, there is enough for applications of most sizes to be completed successfully.
   8
   9In this section of the book we will look at two popular Backbone add-ons: MarionetteJS and Thorax.
  10
  11## MarionetteJS (Backbone.Marionette)
  12
  13*By Derick Bailey & Addy Osmani*
  14
  15As we've seen, Backbone provides a great set of building blocks for our JavaScript applications. It gives us the core constructs that are needed to build small to mid-sized apps, organize jQuery DOM events, or create single page apps that support mobile devices and large scale enterprise needs. But Backbone is not a complete framework. It's a set of building blocks that leaves much of the application design, architecture, and scalability to the developer, including memory management, view management, and more.
  16
  17[MarionetteJS](http://marionettejs.com) (a.k.a. Backbone.Marionette) provides many of the features that the non-trivial application developer needs, above what Backbone itself provides. It is a composite application library that aims to simplify the construction of large scale applications. It does this by providing a collection of common design and implementation patterns found in the applications that the creator, [Derick Bailey](http://lostechies.com/derickbailey/), and many other [contributors](https://github.com/marionettejs/backbone.marionette/graphs/contributors) have been using to build Backbone apps.
  18 
  19
  20Marionette's key benefits include:
  21
  22* Scaling applications out with modular, event driven architecture
  23* Sensible defaults, such as using Underscore templates for view rendering
  24* Easy to modify to make it work with your application's specific needs
  25* Reducing boilerplate for views, with specialized view types
  26* Build on a modular architecture with an Application and modules that attach to it
  27* Compose your application's visuals at runtime, with Region and Layout
  28* Nested views and layouts within visual regions
  29* Built-in memory management and zombie killing in views, regions, and layouts
  30* Built-in event clean up with the EventBinder
  31* Event-driven architecture with the EventAggregator
  32* Flexible, "as-needed" architecture allowing you to pick and choose what you need
  33* And much, much more
  34
  35Marionette follows a similar philosophy to Backbone in that it provides a suite of components that can be used independently of each other, or used together to create significant advantages for us as developers. But it steps above the structural components of Backbone and provides an application layer, with more than a dozen components and building blocks.
  36
  37Marionette's components range greatly in the features they provide, but they all work together to create a composite application layer that can both reduce boilerplate code and provide a much needed application structure. Its core components include various and specialized view types that take the boilerplate out of rendering common Backbone.Model and Backbone.Collection scenarios; an Application object and Module architecture to scale applications across sub-applications, features and files; integration of a command pattern, event aggregator, and request/response mechanism; and many more object types that can be extended in a myriad of ways to create an architecture that facilitates an application's specific needs. 
  38
  39In spite of the large number of constructs that Marionette provides, though, you're not required to use all of it just because you want to use some of it. Much like Backbone itself, you can pick and choose which features you want to use and when. This allows you to work with other Backbone frameworks and plugins very easily. It also means that you are not required to engage in an all-or-nothing migration to begin using Marionette.
  40
  41### Boilerplate Rendering Code
  42
  43Consider the code that it typically requires to render a view with Backbone and Underscore template. We need a template to render, which can be placed in the DOM directly, and we need the JavaScript that defines a view that uses the template and populates it with data from a model.
  44
  45```
  46<script type="text/html" id="my-view-template">
  47  <div class="row">
  48    <label>First Name:</label>
  49    <span><%= firstName %></span>
  50  </div>
  51  <div class="row">
  52    <label>Last Name:</label>
  53    <span><%= lastName %></span>
  54  </div>
  55  <div class="row">
  56    <label>Email:</label>
  57    <span><%= email %></span>
  58  </div>
  59</script>
  60```
  61
  62```javascript
  63var MyView = Backbone.View.extend({
  64  template: $('#my-view-template').html(),
  65
  66  render: function() {
  67
  68    // compile the Underscore.js template
  69    var compiledTemplate = _.template(this.template);
  70
  71    // render the template with the model data
  72    var data = _.clone(this.model.attributes);
  73    var html = compiledTemplate(data);
  74
  75    // populate the view with the rendered html
  76    this.$el.html(html);
  77  }
  78});
  79```
  80
  81Once this is in place, you need to create an instance of your view and pass your model into it. Then you can take the view's `el` and append it to the DOM in order to display the view.
  82
  83```javascript
  84var Derick = new Person({
  85  firstName: 'Derick',
  86  lastName: 'Bailey',
  87  email: 'derickbailey@example.com'
  88});
  89
  90var myView = new MyView({
  91  model: Derick
  92});
  93
  94myView.setElement("#content");
  95myView.render();
  96
  97```
  98
  99This is a standard set up for defining, building, rendering, and displaying a view with Backbone. This is also what we call "boilerplate code" - code that is repeated over and over and over again, across every project and every implementation with the same functionality. It gets to be tedious and repetitious very quickly.
 100
 101Enter Marionette's `ItemView` - a simple way to reduce the boilerplate of defining a view.
 102
 103### Reducing Boilerplate With Marionette.ItemView
 104
 105All of Marionette's view types - with the exception of `Marionette.View` - include a built-in `render` method that handles the core rendering logic for you. We can take advantage of this by changing the `MyView` instance to inherit from one of these rather than `Backbone.View`. Instead of having to provide our own `render` method for the view, we can let Marionette render it for us. We'll still use the same Underscore.js template and rendering mechanism, but the implementation of this is hidden behind the scenes. Thus, we can reduce the amount of code needed for this view.
 106
 107
 108```javascript
 109var MyView = Marionette.ItemView.extend({
 110  template: '#my-view-template'
 111});
 112```
 113
 114And that's it - that's all you need to get the exact same behaviour as the previous view implementation. Just replace `Backbone.View.extend` with `Marionette.ItemView.extend`, then get rid of the `render` method. You can still create the view instance with a `model`, call the `render` method on the view instance, and display the view in the DOM the same way that we did before. But the view definition has been reduced to a single line of configuration for the template.
 115
 116### Memory Management
 117
 118In addition to the reduction of code needed to define a view, Marionette includes some advanced memory management in all of its views, making the job of cleaning up a view instance and its event handlers easy.
 119
 120Consider the following view implementation:
 121
 122```javascript
 123var ZombieView = Backbone.View.extend({
 124  template: '#my-view-template',
 125
 126  initialize: function() {
 127
 128    // bind the model change to re-render this view
 129    this.model.on('change', this.render, this);
 130
 131  },
 132
 133  render: function() {
 134
 135    // This alert is going to demonstrate a problem
 136    alert('We`re rendering the view');
 137
 138  }
 139});
 140```
 141
 142If we create two instances of this view using the same variable name for both instances, and then change a value in the model, how many times will we see the alert box?
 143
 144
 145```javascript
 146
 147var Person = Backbone.Model.extend({
 148  defaults: {
 149    "firstName": "Jeremy",
 150    "lastName": "Ashkenas",
 151    "email":    "jeremy@example.com"
 152  }
 153});
 154
 155var Derick = new Person({
 156  firstName: 'Derick',
 157  lastName: 'Bailey',
 158  email: 'derick@example.com'
 159});
 160
 161
 162// create the first view instance
 163var zombieView = new ZombieView({
 164  model: Derick
 165});
 166
 167// create a second view instance, re-using
 168// the same variable name to store it
 169zombieView = new ZombieView({
 170  model: Derick
 171});
 172
 173Derick.set('email', 'derickbailey@example.com');
 174```
 175
 176Since we're re-using the same `zombieView` variable for both instances, the first instance of the view will fall out of scope immediately after the second is created. This allows the JavaScript garbage collector to come along and clean it up, which should mean the first view instance is no longer active and no longer going to respond to the model's "change" event.
 177
 178But when we run this code, we end up with the alert box showing up twice!
 179
 180The problem is caused by the model event binding in the view's `initialize` method. Whenever we pass `this.render` as the callback method to the model's `on` event binding, the model itself is being given a direct reference to the view instance. Since the model is now holding a reference to the view instance, replacing the `zombieView` variable with a new view instance is not going to let the original view fall out of scope. The model still has a reference, therefore the view is still in scope.
 181
 182Since the original view is still in scope, and the second view instance is also in scope, changing data on the model will cause both view instances to respond.
 183
 184Fixing this is easy, though. You just need to call `stopListening` when the view is done with its work and ready to be closed. To do this, add a `close` method to the view.
 185
 186```javascript
 187var ZombieView = Backbone.View.extend({
 188  template: '#my-view-template',
 189
 190  initialize: function() {
 191    // bind the model change to re-render this view
 192    this.model.on('change', this.render, this);
 193  },
 194
 195  close: function() {
 196    // unbind the events that this view is listening to
 197    this.stopListening();
 198  },
 199
 200  render: function() {
 201
 202    // This alert is going to demonstrate a problem
 203    alert('We`re rendering the view');
 204
 205  }
 206});
 207```
 208
 209Then call `close` on the first instance when it is no longer needed, and only one view instance will remain alive. For more information about the `listenTo` and `stopListening` functions, see the earlier Backbone Basics chapter and Derick's post on [Managing Events As Relationships, Not Just Resources](http://lostechies.com/derickbailey/2013/02/06/managing-events-as-relationships-not-just-references/).
 210
 211```javascript
 212var Jeremy = new Person({
 213  firstName: 'Jeremy',
 214  lastName: 'Ashkenas',
 215  email: 'jeremy@example.com'
 216});
 217
 218// create the first view instance
 219var zombieView = new ZombieView({
 220  model: Person
 221})
 222zombieView.close(); // double-tap the zombie
 223
 224// create a second view instance, re-using
 225// the same variable name to store it
 226zombieView = new ZombieView({
 227  model: Person
 228})
 229
 230Person.set('email', 'jeremyashkenas@example.com');
 231```
 232
 233Now we only see one alert box when this code runs. 
 234
 235Rather than having to manually remove these event handlers, though, we can let Marionette do it for us.
 236
 237```javascript
 238var ZombieView = Marionette.ItemView.extend({
 239  template: '#my-view-template',
 240
 241  initialize: function() {
 242
 243    // bind the model change to re-render this view
 244    this.listenTo(this.model, 'change', this.render);
 245
 246  },
 247
 248  render: function() {
 249
 250    // This alert is going to demonstrate a problem
 251    alert('We`re rendering the view');
 252
 253  }
 254});
 255```
 256
 257Notice in this case we are using a method called `listenTo`. This method comes from Backbone.Events, and is available in all objects that mix in Backbone.Events - including most Marionette objects. The `listenTo` method signature is similar to that of the `on` method, with the exception of passing the object that triggers the event as the first parameter. 
 258
 259Marionette's views also provide a `close` event, in which the event bindings that are set up with the `listenTo` are automatically removed. This means we no longer need to define a `close` method directly, and when we use the `listenTo` method, we know that our events will be removed and our views will not turn into zombies.
 260
 261But how do we automate the call to `close` on a view, in the real application? When and where do we call that? Enter the `Marionette.Region` - an object that manages the lifecycle of an individual view.
 262
 263### Region Management
 264
 265After a view is created, it typically needs to be placed in the DOM so that it becomes visible. This is usually done with a jQuery selector and setting the `html()` of the resulting object:
 266
 267```javascript
 268var Joe = new Person({
 269  firstName: 'Joe',
 270  lastName: 'Bob',
 271  email: 'joebob@example.com'
 272});
 273
 274var myView = new MyView({
 275  model: Joe
 276})
 277
 278myView.render();
 279
 280// show the view in the DOM
 281$('#content').html(myView.el)
 282```
 283
 284This, again, is boilerplate code. We shouldn't have to manually call `render` and manually select the DOM elements to show the view. Furthermore, this code doesn't lend itself to closing any previous view instance that might be attached to the DOM element we want to populate. And we've seen the danger of zombie views already.
 285
 286To solve these problems, Marionette provides a `Region` object - an object that manages the lifecycle of individual views, displayed in a particular DOM element.
 287
 288```javascript
 289// create a region instance, telling it which DOM element to manage
 290var myRegion = new Marionette.Region({
 291  el: '#content'
 292});
 293
 294// show a view in the region
 295var view1 = new MyView({ /* ... */ });
 296myRegion.show(view1);
 297
 298// somewhere else in the code,
 299// show a different view
 300var view2 = new MyView({ /* ... */ });
 301myRegion.show(view2);
 302```
 303
 304There are several things to note, here. First, we're telling the region what DOM element to manage by specifying an `el` in the region instance. Second, we're no longer calling the `render` method on our views. And lastly, we're not calling `close` on our view, either, though this is getting called for us.
 305
 306When we use a region to manage the lifecycle of our views, and display the views in the DOM, the region itself handles these concerns. By passing a view instance into the `show` method of the region, it will call the render method on the view for us. It will then take the resulting `el` of the view and populate the DOM element.
 307
 308The next time we call the `show` method of the region, the region remembers that it is currently displaying a view. The region calls the `close` method on the view, removes it from the DOM, and then proceeds to run the render & display code for the new view that was passed in.
 309
 310Since the region handles calling `close` for us, and we're using the `listenTo` event binder in our view instance, we no longer have to worry about zombie views in our application.
 311
 312Regions are not limited to just Marionette views, though. Any valid Backbone.View can be managed by a Marionette.Region. If your view happens to have a `close` method, it will be called when the view is closed. If not, the Backbone.View built-in method, `remove`, will be called instead. 
 313
 314### Marionette Todo app
 315
 316Having learned about Marionette's high-level concepts, let's explore refactoring the Todo application we created in our first exercise to use it. The complete code for this application can be found in Derick's TodoMVC [fork](https://github.com/derickbailey/todomvc/tree/marionette/labs/architecture-examples/backbone_marionette/js).
 317
 318Our final implementation will be visually and functionally equivalent to the original app, as seen below.
 319
 320![](img/marionette_todo0.png)
 321
 322First, we define an application object representing our base TodoMVC app. This will contain initialization code and define the default layout regions for our app. 
 323
 324**TodoMVC.js:**
 325
 326```javascript
 327var TodoMVC = new Backbone.Marionette.Application();
 328
 329TodoMVC.addRegions({
 330  header: '#header',
 331  main: '#main',
 332  footer: '#footer'
 333});
 334
 335TodoMVC.on('initialize:after', function() {
 336  Backbone.history.start();
 337});
 338```
 339
 340Regions are used to manage the content that's displayed within specific elements, and the `addRegions` method on the `TodoMVC` object is just a shortcut for creating `Region` objects. We supply a jQuery selector for each region to manage (e.g., `#header`, `#main`, and `#footer`) and then tell the region to show various Backbone views within that region.
 341
 342Once the application object has been initialized, we call `Backbone.history.start()` to route the initial URL.
 343
 344Next, we define our Layouts. A layout is a specialized type of view that directly extends `Marionette.ItemView`. This means it's intended to render a single template and may or may not have a model (or `item`) associated with the template.
 345
 346One of the main differences between a Layout and an `ItemView` is that the layout contains regions. When defining a Layout, we supply it with both a `template` and the regions that the template contains. After rendering the layout, we can display other views within the layout using the regions that were defined.
 347
 348In our TodoMVC Layout module below, we define Layouts for:
 349
 350* Header: where we can create new Todos
 351* Footer: where we summarize how many Todos are remaining/have been completed
 352
 353
 354This captures some of the view logic that was previously in our `AppView` and `TodoView`.
 355
 356Note that Marionette modules (such as the below) offer a simple module system which is used to create privacy and encapsulation in Marionette apps. These certainly don't have to be used however, and later on in this section we'll provide links to alternative implementations using RequireJS + AMD instead.
 357
 358
 359**TodoMVC.Layout.js:**
 360
 361```javascript
 362TodoMVC.module('Layout', function(Layout, App, Backbone, Marionette, $, _) {
 363  
 364  // Layout Header View
 365  // ------------------
 366
 367  Layout.Header = Backbone.Marionette.ItemView.extend({
 368    template: '#template-header',
 369
 370    // UI Bindings create cached attributes that
 371    // point to jQuery selected objects.
 372    ui: {
 373      input: '#new-todo'
 374    },
 375
 376    events: {
 377      'keypress #new-todo': 'onInputKeypress',
 378      'blur #new-todo': 'onTodoBlur'
 379    },
 380
 381    onTodoBlur: function(){
 382      var todoText = this.ui.input.val().trim();
 383      this.createTodo(todoText);
 384    },
 385
 386    onInputKeypress: function(e) {
 387      var ENTER_KEY = 13;
 388      var todoText = this.ui.input.val().trim();
 389
 390      if ( e.which === ENTER_KEY && todoText ) {
 391        this.createTodo(todoText);
 392        }
 393      },
 394
 395    completeAdd: function() {
 396      this.ui.input.val('');
 397    },
 398
 399    createTodo: function(todoText) {
 400      if (todoText.trim() === ""){ return; }
 401
 402      this.collection.create({
 403        title: todoText
 404      });
 405
 406      this.completeAdd();
 407    }
 408  });
 409
 410  // Layout Footer View
 411  // ------------------
 412
 413  Layout.Footer = Marionette.Layout.extend({
 414    template: '#template-footer',
 415
 416    // UI Bindings create cached attributes that
 417    // point to jQuery selected objects.
 418    ui: {
 419      todoCount: '#todo-count .count',
 420      todoCountLabel: '#todo-count .label',
 421      clearCount: '#clear-completed .count',
 422      filters: "#filters a"
 423    },
 424
 425    events: {
 426      "click #clear-completed": "onClearClick"
 427    },
 428
 429    initialize: function() {
 430      this.bindTo( App.vent, "todoList: filter", this.updateFilterSelection, this );
 431      this.bindTo( this.collection, 'all', this.updateCount, this );
 432    },
 433
 434    onRender: function() {
 435      this.updateCount();
 436    },
 437
 438    updateCount: function() {
 439      var activeCount = this.collection.getActive().length,
 440      completedCount = this.collection.getCompleted().length;
 441      this.ui.todoCount.html(activeCount);
 442
 443      this.ui.todoCountLabel.html(activeCount === 1 ? 'item' : 'items');
 444      this.ui.clearCount.html(completedCount === 0 ? '' : '(' + completedCount + ')');
 445    },
 446
 447    updateFilterSelection: function( filter ) {
 448      this.ui.filters
 449        .removeClass('selected')
 450        .filter( '[href="#' + filter + '"]')
 451        .addClass( 'selected' );
 452    },
 453
 454    onClearClick: function() {
 455      var completed = this.collection.getCompleted();
 456      completed.forEach(function destroy(todo) {
 457        todo.destroy();
 458      });
 459    }
 460  });
 461
 462});
 463
 464```
 465
 466Next, we tackle application routing and workflow, such as controlling Layouts in the page which can be shown or hidden.
 467
 468Recall how Backbone routes trigger methods within the Router as shown below in our original Workspace router from our first exercise:
 469
 470```javascript
 471  var Workspace = Backbone.Router.extend({
 472    routes: {
 473      '*filter': 'setFilter'
 474    },
 475
 476    setFilter: function(param) {
 477      // Set the current filter to be used
 478      window.app.TodoFilter = param.trim() || '';
 479
 480      // Trigger a collection filter event, causing hiding/unhiding
 481      // of Todo view items
 482      window.app.Todos.trigger('filter');
 483    }
 484  });
 485
 486```
 487
 488Marionette uses the concept of an AppRouter to simplify routing. This reduces the boilerplate for handling route events and allows routers to be configured to call methods on an object directly. We configure our AppRouter using `appRoutes` which replaces the `'*filter': 'setFilter'` route defined in our original router and invokes a method on our Controller.
 489
 490The TodoList Controller, also found in this next code block, handles some of the remaining visibility logic originally found in `AppView` and `TodoView`, albeit using very readable Layouts.
 491
 492**TodoMVC.TodoList.js:**
 493
 494```javascript
 495TodoMVC.module('TodoList', function(TodoList, App, Backbone, Marionette, $, _) {
 496
 497  // TodoList Router
 498  // ---------------
 499  //
 500  // Handle routes to show the active vs complete todo items
 501
 502  TodoList.Router = Marionette.AppRouter.extend({
 503    appRoutes: {
 504      '*filter': 'filterItems'
 505    }
 506  });
 507
 508  // TodoList Controller (Mediator)
 509  // ------------------------------
 510  //
 511  // Control the workflow and logic that exists at the application
 512  // level, above the implementation detail of views and models
 513  
 514  TodoList.Controller = function() {
 515    this.todoList = new App.Todos.TodoList();
 516  };
 517
 518  _.extend(TodoList.Controller.prototype, {
 519
 520    // Start the app by showing the appropriate views
 521    // and fetching the list of todo items, if there are any
 522    start: function() {
 523      this.showHeader(this.todoList);
 524      this.showFooter(this.todoList);
 525      this.showTodoList(this.todoList);
 526      
 527  App.bindTo(this.todoList, 'reset add remove', this.toggleFooter, this);
 528      this.todoList.fetch();
 529    },
 530
 531    showHeader: function(todoList) {
 532      var header = new App.Layout.Header({
 533        collection: todoList
 534      });
 535      App.header.show(header);
 536    },
 537
 538    showFooter: function(todoList) {
 539      var footer = new App.Layout.Footer({
 540        collection: todoList
 541      });
 542      App.footer.show(footer);
 543    },
 544
 545    showTodoList: function(todoList) {
 546      App.main.show(new TodoList.Views.ListView({
 547        collection: todoList
 548      }));
 549    },
 550    
 551    toggleFooter: function() {
 552      App.footer.$el.toggle(this.todoList.length);
 553    },
 554
 555    // Set the filter to show complete or all items
 556    filterItems: function(filter) {
 557      App.vent.trigger('todoList:filter', filter.trim() || '');
 558    }
 559  });
 560
 561  // TodoList Initializer
 562  // --------------------
 563  //
 564  // Get the TodoList up and running by initializing the mediator
 565  // when the the application is started, pulling in all of the
 566  // existing Todo items and displaying them.
 567  
 568  TodoList.addInitializer(function() {
 569
 570    var controller = new TodoList.Controller();
 571    new TodoList.Router({
 572      controller: controller
 573    });
 574
 575    controller.start();
 576
 577  });
 578
 579});
 580
 581```
 582
 583####Controllers
 584
 585In this particular app, note that Controllers don't add a great deal to the overall workflow. In general, Marionette's philosophy on routers is that they should be an afterthought in the implementation of applications. Quite often, we've seen developers abuse Backbone's routing system by making it the sole controller of the entire application workflow and logic.
 586
 587This inevitably leads to mashing every possible combination of code into the router methods - view creation, model loading, coordinating different parts of the app, etc. Developers such as Derick view this as a violation of the [single-responsibility principle](http://en.wikipedia.org/wiki/Single_responsibility_principle) (SRP) and separation of concerns.
 588
 589
 590Backbone's router and history exist to deal with a specific aspect of browsers - managing the forward and back buttons. Marionette's philosophy is that it should be limited to that, with the code that gets executed by the navigation being somewhere else. This allows the application to be used with or without a router. We can call a controller's "show" method from a button click, from an application event handler, or from a router, and we will end up with the same application state no matter how we called that method.
 591
 592Derick has written extensively about his thoughts on this topic, which you can read more about on his blog:
 593
 594* [http://lostechies.com/derickbailey/2011/12/27/the-responsibilities-of-the-various-pieces-of-backbone-js/](http://lostechies.com/derickbailey/2011/12/27/the-responsibilities-of-the-various-pieces-of-backbone-js/)
 595* [http://lostechies.com/derickbailey/2012/01/02/reducing-backbone-routers-to-nothing-more-than-configuration/](http://lostechies.com/derickbailey/2012/01/02/reducing-backbone-routers-to-nothing-more-than-configuration/)
 596* [http://lostechies.com/derickbailey/2012/02/06/3-stages-of-a-backbone-applications-startup/](http://lostechies.com/derickbailey/2012/02/06/3-stages-of-a-backbone-applications-startup/)
 597
 598#### CompositeView
 599
 600Our next task is defining the actual views for individual Todo items and lists of items in our TodoMVC application. For this, we make use of Marionette's `CompositeView`s. The idea behind a CompositeView is that it represents a visualization of a composite or hierarchical structure of leaves (or nodes) and branches.
 601
 602Think of these views as being a hierarchy of parent-child models, and recursive by default. The same CompositeView type will be used to render each item in a collection that is handled by the composite view. For non-recursive hierarchies, we are able to override the item view by defining an `itemView` attribute.
 603
 604For our Todo List Item View, we define it as an ItemView, then our Todo List View is a CompositeView where we override the `itemView` setting and tell it to use the Todo List Item View for each item in the collection.
 605
 606
 607TodoMVC.TodoList.Views.js
 608
 609```javascript
 610TodoMVC.module('TodoList.Views', function(Views, App, Backbone, Marionette, $, _) {
 611
 612  // Todo List Item View
 613  // -------------------
 614  //
 615  // Display an individual todo item, and respond to changes
 616  // that are made to the item, including marking completed.
 617
 618  Views.ItemView = Marionette.ItemView.extend({
 619      tagName: 'li',
 620      template: '#template-todoItemView',
 621
 622      ui: {
 623        edit: '.edit'
 624      },
 625
 626      events: {
 627        'click .destroy': 'destroy',
 628        'dblclick label': 'onEditClick',
 629        'keypress .edit': 'onEditKeypress',
 630        'blur .edit'    : 'onEditBlur',
 631        'click .toggle' : 'toggle'
 632      },
 633
 634      initialize: function() {
 635        this.bindTo(this.model, 'change', this.render, this);
 636      },
 637
 638      onRender: function() {
 639        this.$el.removeClass( 'active completed' );
 640      
 641        if ( this.model.get( 'completed' )) {
 642          this.$el.addClass( 'completed' );
 643        } else { 
 644          this.$el.addClass( 'active' );
 645        }
 646      },
 647
 648      destroy: function() {
 649        this.model.destroy();
 650      },
 651
 652      toggle: function() {
 653        this.model.toggle().save();
 654      },
 655
 656      onEditClick: function() {
 657        this.$el.addClass('editing');
 658        this.ui.edit.focus();
 659      },
 660      
 661      updateTodo : function() {
 662        var todoText = this.ui.edit.val();
 663        if (todoText === '') {
 664          return this.destroy();
 665        }
 666        this.setTodoText(todoText);
 667        this.completeEdit();
 668      },
 669
 670      onEditBlur: function(e){
 671        this.updateTodo();
 672      },
 673
 674      onEditKeypress: function(e) {
 675        var ENTER_KEY = 13;
 676        var todoText = this.ui.edit.val().trim();
 677
 678        if ( e.which === ENTER_KEY && todoText ) {
 679          this.model.set('title', todoText).save();
 680          this.$el.removeClass('editing');
 681        }
 682      },
 683      
 684      setTodoText: function(todoText){
 685        if (todoText.trim() === ""){ return; }
 686        this.model.set('title', todoText).save();
 687      },
 688
 689      completeEdit: function(){
 690        this.$el.removeClass('editing');
 691      }
 692  });
 693
 694  // Item List View
 695  // --------------
 696  //
 697  // Controls the rendering of the list of items, including the
 698  // filtering of active vs completed items for display.
 699
 700  Views.ListView = Backbone.Marionette.CompositeView.extend({
 701      template: '#template-todoListCompositeView',
 702      itemView: Views.ItemView,
 703      itemViewContainer: '#todo-list',
 704
 705      ui: {
 706        toggle: '#toggle-all'
 707      },
 708
 709      events: {
 710        'click #toggle-all': 'onToggleAllClick'
 711      },
 712
 713      initialize: function() {
 714        this.bindTo(this.collection, 'all', this.update, this);
 715      },
 716
 717      onRender: function() {
 718        this.update();
 719      },
 720
 721      update: function() {
 722        function reduceCompleted(left, right) { 
 723          return left && right.get('completed'); 
 724        }
 725        
 726        var allCompleted = this.collection.reduce(reduceCompleted,true);
 727        this.ui.toggle.prop('checked', allCompleted);
 728        this.$el.parent().toggle(!!this.collection.length);
 729      },
 730
 731      onToggleAllClick: function(e) {
 732        var isChecked = e.currentTarget.checked;
 733        this.collection.each(function(todo) {
 734          todo.save({'completed': isChecked});
 735        });
 736      }
 737  });
 738
 739  // Application Event Handlers
 740  // --------------------------
 741  //
 742  // Handler for filtering the list of items by showing and
 743  // hiding through the use of various CSS classes
 744  
 745  App.vent.on('todoList:filter',function(filter) {
 746    filter = filter || 'all';
 747    $('#todoapp').attr('class', 'filter-' + filter);
 748  });
 749
 750});
 751
 752```
 753
 754At the end of the last code block, you will also notice an event handler using `vent`. This is an event aggregator that allows us to handle `filterItem` triggers from our TodoList controller.
 755
 756Finally, we define the model and collection for representing our Todo items. These are semantically not very different from the original versions we used in our first exercise and have been re-written to better fit in with Derick's preferred style of coding.
 757
 758**TodoMVC.Todos.js:**
 759
 760```javascript
 761TodoMVC.module('Todos', function(Todos, App, Backbone, Marionette, $, _) {
 762
 763  // Local Variables
 764  // ---------------
 765
 766  var localStorageKey = 'todos-backbone-marionettejs';
 767
 768  // Todo Model
 769  // ----------
 770  
 771  Todos.Todo = Backbone.Model.extend({
 772    localStorage: new Backbone.LocalStorage(localStorageKey),
 773
 774    defaults: {
 775      title: '',
 776      completed: false,
 777      created: 0
 778    },
 779
 780    initialize: function() {
 781      if (this.isNew()) {
 782        this.set('created', Date.now());
 783      }
 784    },
 785
 786    toggle: function() {
 787      return this.set('completed', !this.isCompleted());
 788    },
 789
 790    isCompleted: function() { 
 791      return this.get('completed'); 
 792    }
 793  });
 794
 795  // Todo Collection
 796  // ---------------
 797
 798  Todos.TodoList = Backbone.Collection.extend({
 799    model: Todos.Todo,
 800
 801    localStorage: new Backbone.LocalStorage(localStorageKey),
 802
 803    getCompleted: function() {
 804      return this.filter(this._isCompleted);
 805    },
 806
 807    getActive: function() {
 808      return this.reject(this._isCompleted);
 809    },
 810
 811    comparator: function(todo) {
 812      return todo.get('created');
 813    },
 814
 815    _isCompleted: function(todo) {
 816      return todo.isCompleted();
 817    }
 818  });
 819
 820});
 821
 822```
 823
 824We finally kick-start everything off in our application index file, by calling `start` on our main application object:
 825
 826Initialization:
 827
 828```javascript
 829      $(function() {
 830        // Start the TodoMVC app (defined in js/TodoMVC.js)
 831        TodoMVC.start();
 832      });
 833```
 834
 835And that's it!
 836
 837### Is the Marionette implementation of the Todo app more maintainable?
 838
 839Derick feels that maintainability largely comes down to modularity, separating responsibilities (Single Responsibility Principle and Separation of Concerns) by using patterns to keep concerns from being mixed together. It can, however, be difficult to simply extract things into separate modules for the sake of extraction, abstraction, or dividing the concept down into its simplest parts.
 840
 841The Single Responsibility Principle (SRP) tells us quite the opposite - that we need to understand the context in which things change. What parts always change together, in _this_ system? What parts can change independently? Without knowing this, we won't know what pieces should be broken out into separate components and modules versus put together into the same module or object.
 842
 843The way Derick organizes his apps into modules is by creating a breakdown of concepts at each level. A higher level module is a higher level of concern - an aggregation of responsibilities. Each responsibility is broken down into an expressive API set that is implemented by lower level modules (Dependency Inversion Principle). These are coordinated through a mediator - which he typically refers to as the Controller in a module.
 844
 845
 846The way Derick organizes his files also plays directly into maintainability and he has also written posts about the importance of keeping a sane application folder structure that I recommend reading:
 847
 848* [http://lostechies.com/derickbailey/2012/02/02/javascript-file-folder-structures-just-pick-one/](http://lostechies.com/derickbailey/2012/02/02/javascript-file-folder-structures-just-pick-one/)
 849* [http://hilojs.codeplex.com/discussions/362875#post869640](http://hilojs.codeplex.com/discussions/362875#post869640)
 850
 851### Marionette And Flexibility
 852
 853Marionette is a flexible framework, much like Backbone itself. It offers a wide variety of tools to help create and organize an application architecture on top of Backbone, but like Backbone itself, it doesn't dictate that you have to use all of its pieces in order to use any of them.
 854
 855The flexibility and versatility in Marionette is easiest to understand by examining three variations of TodoMVC implemented with it that have been created for comparison purposes:
 856
 857* [Simple](https://github.com/jsoverson/todomvc/tree/master/labs/architecture-examples/backbone_marionette) - by Jarrod Overson
 858* [RequireJS](https://github.com/jsoverson/todomvc/tree/master/labs/dependency-examples/backbone_marionette_require) - also by Jarrod
 859* [Marionette modules](https://github.com/derickbailey/todomvc/tree/marionette/labs/architecture-examples/backbone_marionette/js) - by Derick Bailey
 860
 861**The simple version**: This version of TodoMVC shows some raw use of Marionette's various view types, an application object, and the event aggregator. The objects that are created are added directly to the global namespace and are fairly straightforward. This is a great example of how Marionette can be used to augment existing code without having to re-write everything around Marionette.
 862
 863**The RequireJS version**: Using Marionette with RequireJS helps to create a modularized application architecture - a tremendously important concept in scaling JavaScript applications. RequireJS provides a powerful set of tools that can be leveraged to great advantage, making Marionette even more flexible than it already is.
 864
 865**The Marionette module version**: RequireJS isn't the only way to create a modularized application architecture, though. For those that wish to build applications in modules and namespaces, Marionette provides a built-in module and namespacing structure. This example application takes the simple version of the application and re-writes it into a namespaced application architecture, with an application controller (mediator / workflow object) that brings all of the pieces together.
 866
 867Marionette certainly provides its share of opinions on how a Backbone application should be architected. The combination of modules, view types, event aggregator, application objects, and more, can be used to create a very powerful and flexible architecture based on these opinions.
 868
 869But as you can see, Marionette isn't a completely rigid, "my way or the highway" framework. It provides many elements of an application foundation that can be mixed and matched with other architectural styles, such as AMD or namespacing, or provide simple augmentation to existing projects by reducing boilerplate code for rendering views.
 870
 871
 872This flexibility creates a much greater opportunity for Marionette to provide value to you and your projects, as it allows you to scale the use of Marionette with your application's needs.
 873
 874### And So Much More
 875
 876This is just the tip of the proverbial iceberg for Marionette, even for the `ItemView` and `Region` objects that we've explored. There is far more functionality, more features, and more flexibility and customizability that can be put to use in both of these objects. Then we have the other dozen or so components that Marionette provides, each with their own set of behaviors built in, customization and extension points, and more.
 877
 878To learn more about Marionette's components, the features they provide and how to use them, check out the Marionette documentation, links to the wiki, to the source code, the project core contributors, and much more at [http://marionettejs.com](http://marionettejs.com).
 879
 880
 881<p>&nbsp;</p>
 882<p>&nbsp;</p>
 883
 884
 885
 886## Thorax
 887
 888*By Ryan Eastridge & Addy Osmani*
 889
 890Part of Backbone's appeal is that it provides structure but is generally un-opinionated, in particular when it comes to views. Thorax makes an opinionated decision to use Handlebars as its templating solution. Some of the patterns found in Marionette are found in Thorax as well. Marionette exposes most of these patterns as JavaScript APIs while in Thorax they are often exposed as template helpers. This chapter assumes the reader has knowledge of Handlebars.
 891
 892
 893Thorax was created by Ryan Eastridge and Kevin Decker to create Walmart's mobile web application. This chapter is limited to Thorax's templating features and patterns implemented in Thorax that you can utilize in your application regardless of whether you choose to adopt Thorax. To learn more about other features implemented in Thorax and to download boilerplate projects visit the [Thorax website](http://thoraxjs.org).
 894
 895### Hello World
 896
 897In Backbone, when creating a new view, options passed are merged into any default options already present on a view and are exposed via `this.options` for later reference.
 898
 899`Thorax.View` differs from `Backbone.View` in that there is no `options` object. All arguments passed to the constructor become properties of the view, which in turn become available to the `template`:
 900
 901```javascript
 902    var view = new Thorax.View({
 903        greeting: 'Hello',
 904        template: Handlebars.compile('{{greeting}} World!')
 905    });
 906    view.appendTo('body');
 907```
 908
 909In most examples in this chapter a `template` property will be specified. In larger projects including the boilerplate projects provided on the Thorax website a `name` property would instead be used and a `template` of the same file name in your project would automatically be assigned to the view.
 910
 911 If a `model` is set on a view, its attributes also become available to the template:
 912
 913    var view = new Thorax.View({
 914        model: new Thorax.Model({key: 'value'}),
 915        template: Handlebars.compile('{{key}}')
 916    });
 917
 918### Embedding child views
 919
 920The view helper allows you to embed other views within a view. Child views can be specified as properties of the view:
 921
 922```javascript
 923    var parent = new Thorax.View({
 924        child: new Thorax.View(...),
 925        template: Handlebars.compile('{{view child}}')
 926    });
 927```
 928
 929Or the name of a child view to initialize as well as any optional properties you wish to pass. In this case the child view must have previously been created with `extend` and given a `name` property:
 930
 931```javascript
 932    var ChildView = Thorax.View.extend({
 933        name: 'child',
 934        template: ...
 935    });
 936  
 937    var parent = new Thorax.View({
 938        template: Handlebars.compile('{{view "child" key="value"}}')
 939    });
 940```
 941
 942The view helper may also be used as a block helper, in which case the block will be assigned as the `template` property of the child view:
 943
 944```handlebars
 945    {{#view child}}
 946        child will have this block
 947        set as its template property
 948    {{/view}}
 949```
 950
 951Handlebars is string based, while `Backbone.View` instances have a DOM `el`. Since we are mixing metaphors, the embedding of views works via a placeholder mechanism where the `view` helper in this case adds the view passed to the helper to a hash of `children`, then injects placeholder HTML into the template such as:
 952
 953```html
 954    <div data-view-placeholder-cid="view2"></div>
 955```
 956
 957Then once the parent view is rendered, we walk the DOM in search of all the placeholders we created, replacing them with the child views' `el`s:
 958
 959```javascript
 960    this.$el.find('[data-view-placeholder-cid]').forEach(function(el) {
 961        var cid = el.getAttribute('data-view-placeholder-cid'),
 962            view = this.children[cid];
 963        view.render();
 964        $(el).replaceWith(view.el);
 965    }, this);
 966```
 967
 968### View helpers
 969
 970One of the most useful constructs in Thorax is `Handlebars.registerViewHelper` (not to be confused with `Handlebars.registerHelper`). This method will register a new block helper that will create and embed a `HelperView` instance with its `template` set to the captured block. A `HelperView` instance is different from that of a regular child view in that its context will be that of the parent's in the template. Like other child views it will have a `parent` property set to that of the declaring view. Many of the built-in helpers in Thorax including the `collection` helper are created in this manner.
 971
 972A simple example would be an `on` helper that re-rendered the generated `HelperView` instance each time an event was triggered on the declaring / parent view:
 973
 974    Handlebars.registerViewHelper('on', function(eventName, helperView) {
 975        helperView.parent.on(eventName, function() {
 976            helperView.render();
 977        });
 978    });
 979
 980An example use of this would be to have a counter that would increment each time a button was clicked. This example makes use of the `button` helper in Thorax which simply makes a button that triggers a view event when clicked:
 981
 982```handlebars
 983    {{#on "incremented"}}{{i}}{{/on}}
 984    {{#button trigger="incremented"}}Add{{/button}}
 985```
 986
 987And the corresponding view class:
 988
 989```javascript
 990    new Thorax.View({
 991        events: {
 992            incremented: function() {
 993                ++this.i;
 994            }
 995        },
 996        initialize: function() {
 997            this.i = 0;
 998        },
 999        template: ...
1000    });
1001```
1002
1003### collection helper
1004
1005The `collection` helper creates and embeds a `CollectionView` instance, creating a view for each item in a collection, updating when items are added, removed, or changed in the collection. The simplest usage of the helper would look like:
1006
1007```handlebars
1008    {{#collection kittens}}
1009      <li>{{name}}</li>
1010    {{/collection}}
1011```
1012
1013And the corresponding view:
1014
1015```javascript
1016    new Thorax.View({
1017      kittens: new Thorax.Collection(...),
1018      template: ...
1019    });
1020```
1021
1022The block in this case will be assigned as the `template` for each item view created, and the context will be the `attributes` of the given model. This helper accepts options that can be arbitrary HTML attributes, a `tag` option to specify the type of tag containing the collection, or any of the following:
1023
1024- `item-template` - A template to display for each model. If a block is specified it will become the item-template
1025- `item-view` - A view class to use when each item view is created
1026- `empty-template` - A template to display when the collection is empty. If an inverse / else block is specified it will become the empty-template
1027- `empty-view` - A view to display when the collection is empty
1028
1029Options and blocks can be used in combination, in this case creating a `KittenView` class with a `template` set to the captured block for each kitten in the collection:
1030
1031```handlebars
1032    {{#collection kittens item-view="KittenView" tag="ul"}}
1033      <li>{{name}}</li>
1034    {{else}}
1035      <li>No kittens!</li>
1036    {{/collection}}
1037```
1038
1039Note that multiple collections can be used per view, and collections can be nested. This is useful when there are models that contain collections that contain models that contain...
1040
1041```handlebars
1042    {{#collection kittens}}
1043      <h2>{{name}}</h2>
1044      <p>Kills:</p>
1045      {{#collection miceKilled tag="ul"}}
1046        <li>{{name}}</li>
1047      {{/collection}}
1048    {{/collection}}
1049```
1050
1051### Custom HTML data attributes
1052
1053Thorax makes heavy use of custom HTML data attributes to operate. While some make sense only within the context of Thorax, several are quite useful to have in any Backbone project for writing other functions against, or for general debugging. In order to add some to your views in non-Thorax projects, override the `setElement` method in your base view class:
1054
1055```javascript
1056  MyApplication.View = Backbone.View.extend({
1057    setElement: function() {
1058        var response = Backbone.View.prototype.setElement.apply(this, arguments);
1059        this.name && this.$el.attr('data-view-name', this.name);
1060        this.$el.attr('data-view-cid', this.cid);
1061        this.collection && this.$el.attr('data-collection-cid', this.collection.cid);
1062        this.model && this.$el.attr('data-model-cid', this.model.cid);
1063        return response;
1064    }
1065  });
1066```
1067
1068In addition to making your application more immediately comprehensible in the inspector, it's now possible to extend jQuery / Zepto with functions to lookup the closest view, model or collection to a given element. In order to make it work you have to save references to each view created in your base view class by overriding the `_configure` method:
1069
1070
1071```javascript
1072    MyApplication.View = Backbone.View.extend({
1073        _configure: function() {
1074            Backbone.View.prototype._configure.apply(this, arguments);
1075            Thorax._viewsIndexedByCid[this.cid] = this;
1076        },
1077        dispose: function() {
1078            Backbone.View.prototype.dispose.apply(this, arguments);
1079            delete Thorax._viewsIndexedByCid[this.cid];
1080        }
1081    });
1082```
1083
1084Then we can extend jQuery / Zepto:
1085
1086```javascript
1087    $.fn.view = function() {
1088        var el = $(this).closest('[data-view-cid]');
1089        return el && Thorax._viewsIndexedByCid[el.attr('data-view-cid')];
1090    };
1091
1092    $.fn.model = function(view) {
1093        var $this = $(this),
1094            modelElement = $this.closest('[data-model-cid]'),
1095            modelCid = modelElement && modelElement.attr('[data-model-cid]');
1096        if (modelCid) {
1097            var view = $this.view();
1098            return view && view.model;
1099        }
1100        return false;
1101    };
1102```
1103
1104Now instead of storing references to models randomly throughout your application to lookup when a given DOM event occurs you can use `$(element).model()`. In Thorax, this can particularly useful in conjunction with the `collection` helper which generates a view class (with a `model` property) for each `model` in the collection. An example template:
1105
1106```handlebars
1107    {{#collection kittens tag="ul"}}
1108      <li>{{name}}</li>
1109    {{/collection}}
1110```
1111
1112And the corresponding view class:
1113
1114```javascript
1115    Thorax.View.extend({
1116      events: {
1117        'click li': function(event) {
1118          var kitten = $(event.target).model();
1119          console.log('Clicked on ' + kitten.get('name'));
1120        }
1121      },
1122      kittens: new Thorax.Collection(...),
1123      template: ...
1124    });  
1125```
1126
1127A common anti-pattern in Backbone applications is to assign a `className` to a single view class. Consider using the `data-view-name` attribute as a CSS selector instead, saving CSS classes for things that will be used multiple times:
1128
1129
1130```css
1131  [data-view-name="child"] {
1132
1133  }
1134```
1135
1136### Thorax Resources
1137
1138No Backbone related tutorial would be complete without a todo application. A [Thorax implementation of TodoMVC](http://todomvc.com/labs/architecture-examples/thorax/) is available, in addition to this far simpler example composed of this single Handlebars template:
1139
1140
1141```handlebars
1142  {{#collection todos tag="ul"}}
1143    <li{{#if done}} class="done"{{/if}}>
1144      <input type="checkbox" name="done"{{#if done}} checked="checked"{{/if}}>
1145      <span>{{item}}</span>
1146    </li>
1147  {{/collection}}
1148  <form>
1149    <input type="text">
1150    <input type="submit" value="Add">
1151  </form>
1152```
1153
1154and the corresponding JavaScript:
1155
1156```javascript
1157  var todosView = Thorax.View({
1158      todos: new Thorax.Collection(),
1159      events: {
1160          'change input[type="checkbox"]': function(event) {
1161              var target = $(event.target);
1162              target.model().set({done: !!target.attr('checked')});
1163          },
1164          'submit form': function(event) {
1165              event.preventDefault();
1166              var input = this.$('input[type="text"]');
1167              this.todos.add({item: i…

Large files files are truncated, but you can click here to view the full file