PageRenderTime 92ms CodeModel.GetById 14ms app.highlight 67ms RepoModel.GetById 1ms app.codeStats 0ms

/chapters/06-extensions.md

https://github.com/1st/backbone-fundamentals
Markdown | 1179 lines | 848 code | 331 blank | 0 comment | 0 complexity | 5cf5eaf3e856c9a51734ec1925f56901 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
  84
  85var Person = Backbone.Model.extend({
  86  defaults: {
  87    "firstName": "Jeremy",
  88    "lastName": "Ashkenas",
  89    "email":    "jeremy@example.com"
  90  }
  91});
  92
  93var Derick = new Person({
  94  firstName: 'Derick',
  95  lastName: 'Bailey',
  96  email: 'derickbailey@example.com'
  97});
  98
  99var myView = new MyView({
 100  model: Derick
 101});
 102
 103myView.setElement("#content");
 104myView.render();
 105
 106```
 107
 108This 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.
 109
 110Enter Marionette's `ItemView` - a simple way to reduce the boilerplate of defining a view.
 111
 112### Reducing Boilerplate With Marionette.ItemView
 113
 114All 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.
 115
 116
 117```javascript
 118var MyView = Marionette.ItemView.extend({
 119  template: '#my-view-template'
 120});
 121```
 122
 123And 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.
 124
 125### Memory Management
 126
 127In 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.
 128
 129Consider the following view implementation:
 130
 131```javascript
 132var ZombieView = Backbone.View.extend({
 133  template: '#my-view-template',
 134
 135  initialize: function() {
 136
 137    // bind the model change to re-render this view
 138    this.listenTo(this.model, 'change', this.render);
 139
 140  },
 141
 142  render: function() {
 143
 144    // This alert is going to demonstrate a problem
 145    alert('We`re rendering the view');
 146
 147  }
 148});
 149```
 150
 151If 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?
 152
 153
 154```javascript
 155
 156var Derick = new Person({
 157  firstName: 'Derick',
 158  lastName: 'Bailey',
 159  email: 'derick@example.com'
 160});
 161
 162
 163// create the first view instance
 164var zombieView = new ZombieView({
 165  model: Derick
 166});
 167
 168// create a second view instance, re-using
 169// the same variable name to store it
 170zombieView = new ZombieView({
 171  model: Derick
 172});
 173
 174Derick.set('email', 'derickbailey@example.com');
 175```
 176
 177Since 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.
 178
 179But when we run this code, we end up with the alert box showing up twice!
 180
 181The 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.
 182
 183Since 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.
 184
 185Fixing 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.
 186
 187```javascript
 188var ZombieView = Backbone.View.extend({
 189  template: '#my-view-template',
 190
 191  initialize: function() {
 192    // bind the model change to re-render this view
 193    this.listenTo(this.model, 'change', this.render);
 194  },
 195
 196  close: function() {
 197    // unbind the events that this view is listening to
 198    this.stopListening();
 199  },
 200
 201  render: function() {
 202
 203    // This alert is going to demonstrate a problem
 204    alert('We`re rendering the view');
 205
 206  }
 207});
 208```
 209
 210Then 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/).
 211
 212```javascript
 213var Jeremy = new Person({
 214  firstName: 'Jeremy',
 215  lastName: 'Ashkenas',
 216  email: 'jeremy@example.com'
 217});
 218
 219// create the first view instance
 220var zombieView = new ZombieView({
 221  model: Jeremy
 222})
 223zombieView.close(); // double-tap the zombie
 224
 225// create a second view instance, re-using
 226// the same variable name to store it
 227zombieView = new ZombieView({
 228  model: Jeremy
 229})
 230
 231Jeremy.set('email', 'jeremyashkenas@example.com');
 232```
 233
 234Now we only see one alert box when this code runs. 
 235
 236Rather than having to manually remove these event handlers, though, we can let Marionette do it for us.
 237
 238```javascript
 239var ZombieView = Marionette.ItemView.extend({
 240  template: '#my-view-template',
 241
 242  initialize: function() {
 243
 244    // bind the model change to re-render this view
 245    this.listenTo(this.model, 'change', this.render);
 246
 247  },
 248
 249  render: function() {
 250
 251    // This alert is going to demonstrate a problem
 252    alert('We`re rendering the view');
 253
 254  }
 255});
 256```
 257
 258Notice 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. 
 259
 260Marionette'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.
 261
 262But 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.
 263
 264### Region Management
 265
 266After 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:
 267
 268```javascript
 269var Joe = new Person({
 270  firstName: 'Joe',
 271  lastName: 'Bob',
 272  email: 'joebob@example.com'
 273});
 274
 275var myView = new MyView({
 276  model: Joe
 277})
 278
 279myView.render();
 280
 281// show the view in the DOM
 282$('#content').html(myView.el)
 283```
 284
 285This, 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.
 286
 287To solve these problems, Marionette provides a `Region` object - an object that manages the lifecycle of individual views, displayed in a particular DOM element.
 288
 289```javascript
 290// create a region instance, telling it which DOM element to manage
 291var myRegion = new Marionette.Region({
 292  el: '#content'
 293});
 294
 295// show a view in the region
 296var view1 = new MyView({ /* ... */ });
 297myRegion.show(view1);
 298
 299// somewhere else in the code,
 300// show a different view
 301var view2 = new MyView({ /* ... */ });
 302myRegion.show(view2);
 303```
 304
 305There 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.
 306
 307When 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.
 308
 309The 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.
 310
 311Since 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.
 312
 313Regions 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. 
 314
 315### Marionette Todo app
 316
 317Having 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).
 318
 319Our final implementation will be visually and functionally equivalent to the original app, as seen below.
 320
 321![](img/marionette_todo0.png)
 322
 323First, we define an application object representing our base TodoMVC app. This will contain initialization code and define the default layout regions for our app. 
 324
 325**TodoMVC.js:**
 326
 327```javascript
 328var TodoMVC = new Backbone.Marionette.Application();
 329
 330TodoMVC.addRegions({
 331  header: '#header',
 332  main: '#main',
 333  footer: '#footer'
 334});
 335
 336TodoMVC.on('start', function() {
 337  Backbone.history.start();
 338});
 339```
 340
 341Regions 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.
 342
 343Once the application object has been initialized, we call `Backbone.history.start()` to route the initial URL.
 344
 345Next, 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.
 346
 347One 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.
 348
 349In our TodoMVC Layout module below, we define Layouts for:
 350
 351* Header: where we can create new Todos
 352* Footer: where we summarize how many Todos are remaining/have been completed
 353
 354
 355This captures some of the view logic that was previously in our `AppView` and `TodoView`.
 356
 357Note 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.
 358
 359
 360**TodoMVC.Layout.js:**
 361
 362```javascript
 363TodoMVC.module('Layout', function(Layout, App, Backbone, Marionette, $, _) {
 364  
 365  // Layout Header View
 366  // ------------------
 367
 368  Layout.Header = Backbone.Marionette.ItemView.extend({
 369    template: '#template-header',
 370
 371    // UI Bindings create cached attributes that
 372    // point to jQuery selected objects.
 373    ui: {
 374      input: '#new-todo'
 375    },
 376
 377    events: {
 378      'keypress #new-todo': 'onInputKeypress',
 379      'blur #new-todo': 'onTodoBlur'
 380    },
 381
 382    onTodoBlur: function(){
 383      var todoText = this.ui.input.val().trim();
 384      this.createTodo(todoText);
 385    },
 386
 387    onInputKeypress: function(e) {
 388      var ENTER_KEY = 13;
 389      var todoText = this.ui.input.val().trim();
 390
 391      if ( e.which === ENTER_KEY && todoText ) {
 392        this.createTodo(todoText);
 393        }
 394      },
 395
 396    completeAdd: function() {
 397      this.ui.input.val('');
 398    },
 399
 400    createTodo: function(todoText) {
 401      if (todoText.trim() === ""){ return; }
 402
 403      this.collection.create({
 404        title: todoText
 405      });
 406
 407      this.completeAdd();
 408    }
 409  });
 410
 411  // Layout Footer View
 412  // ------------------
 413
 414  Layout.Footer = Marionette.Layout.extend({
 415    template: '#template-footer',
 416
 417    // UI Bindings create cached attributes that
 418    // point to jQuery selected objects.
 419    ui: {
 420      todoCount: '#todo-count .count',
 421      todoCountLabel: '#todo-count .label',
 422      clearCount: '#clear-completed .count',
 423      filters: "#filters a"
 424    },
 425
 426    events: {
 427      "click #clear-completed": "onClearClick"
 428    },
 429
 430    initialize: function() {
 431      this.bindTo( App.vent, "todoList: filter", this.updateFilterSelection, this );
 432      this.bindTo( this.collection, 'all', this.updateCount, this );
 433    },
 434
 435    onRender: function() {
 436      this.updateCount();
 437    },
 438
 439    updateCount: function() {
 440      var activeCount = this.collection.getActive().length,
 441      completedCount = this.collection.getCompleted().length;
 442      this.ui.todoCount.html(activeCount);
 443
 444      this.ui.todoCountLabel.html(activeCount === 1 ? 'item' : 'items');
 445      this.ui.clearCount.html(completedCount === 0 ? '' : '(' + completedCount + ')');
 446    },
 447
 448    updateFilterSelection: function( filter ) {
 449      this.ui.filters
 450        .removeClass('selected')
 451        .filter( '[href="#' + filter + '"]')
 452        .addClass( 'selected' );
 453    },
 454
 455    onClearClick: function() {
 456      var completed = this.collection.getCompleted();
 457      completed.forEach(function destroy(todo) {
 458        todo.destroy();
 459      });
 460    }
 461  });
 462
 463});
 464
 465```
 466
 467Next, we tackle application routing and workflow, such as controlling Layouts in the page which can be shown or hidden.
 468
 469Recall how Backbone routes trigger methods within the Router as shown below in our original Workspace router from our first exercise:
 470
 471```javascript
 472  var Workspace = Backbone.Router.extend({
 473    routes: {
 474      '*filter': 'setFilter'
 475    },
 476
 477    setFilter: function(param) {
 478      // Set the current filter to be used
 479      window.app.TodoFilter = param.trim() || '';
 480
 481      // Trigger a collection filter event, causing hiding/unhiding
 482      // of Todo view items
 483      window.app.Todos.trigger('filter');
 484    }
 485  });
 486
 487```
 488
 489Marionette 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.
 490
 491The 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.
 492
 493**TodoMVC.TodoList.js:**
 494
 495```javascript
 496TodoMVC.module('TodoList', function(TodoList, App, Backbone, Marionette, $, _) {
 497
 498  // TodoList Router
 499  // ---------------
 500  //
 501  // Handle routes to show the active vs complete todo items
 502
 503  TodoList.Router = Marionette.AppRouter.extend({
 504    appRoutes: {
 505      '*filter': 'filterItems'
 506    }
 507  });
 508
 509  // TodoList Controller (Mediator)
 510  // ------------------------------
 511  //
 512  // Control the workflow and logic that exists at the application
 513  // level, above the implementation detail of views and models
 514  
 515  TodoList.Controller = function() {
 516    this.todoList = new App.Todos.TodoList();
 517  };
 518
 519  _.extend(TodoList.Controller.prototype, {
 520
 521    // Start the app by showing the appropriate views
 522    // and fetching the list of todo items, if there are any
 523    start: function() {
 524      this.showHeader(this.todoList);
 525      this.showFooter(this.todoList);
 526      this.showTodoList(this.todoList);
 527      
 528  App.bindTo(this.todoList, 'reset add remove', this.toggleFooter, this);
 529      this.todoList.fetch();
 530    },
 531
 532    showHeader: function(todoList) {
 533      var header = new App.Layout.Header({
 534        collection: todoList
 535      });
 536      App.header.show(header);
 537    },
 538
 539    showFooter: function(todoList) {
 540      var footer = new App.Layout.Footer({
 541        collection: todoList
 542      });
 543      App.footer.show(footer);
 544    },
 545
 546    showTodoList: function(todoList) {
 547      App.main.show(new TodoList.Views.ListView({
 548        collection: todoList
 549      }));
 550    },
 551    
 552    toggleFooter: function() {
 553      App.footer.$el.toggle(this.todoList.length);
 554    },
 555
 556    // Set the filter to show complete or all items
 557    filterItems: function(filter) {
 558      App.vent.trigger('todoList:filter', filter.trim() || '');
 559    }
 560  });
 561
 562  // TodoList Initializer
 563  // --------------------
 564  //
 565  // Get the TodoList up and running by initializing the mediator
 566  // when the the application is started, pulling in all of the
 567  // existing Todo items and displaying them.
 568  
 569  TodoList.addInitializer(function() {
 570
 571    var controller = new TodoList.Controller();
 572    new TodoList.Router({
 573      controller: controller
 574    });
 575
 576    controller.start();
 577
 578  });
 579
 580});
 581
 582```
 583
 584####Controllers
 585
 586In 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.
 587
 588This 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.
 589
 590
 591Backbone'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.
 592
 593Derick has written extensively about his thoughts on this topic, which you can read more about on his blog:
 594
 595* [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/)
 596* [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/)
 597* [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/)
 598
 599#### CompositeView
 600
 601Our 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.
 602
 603Think 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.
 604
 605For 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.
 606
 607
 608TodoMVC.TodoList.Views.js
 609
 610```javascript
 611TodoMVC.module('TodoList.Views', function(Views, App, Backbone, Marionette, $, _) {
 612
 613  // Todo List Item View
 614  // -------------------
 615  //
 616  // Display an individual todo item, and respond to changes
 617  // that are made to the item, including marking completed.
 618
 619  Views.ItemView = Marionette.ItemView.extend({
 620      tagName: 'li',
 621      template: '#template-todoItemView',
 622
 623      ui: {
 624        edit: '.edit'
 625      },
 626
 627      events: {
 628        'click .destroy': 'destroy',
 629        'dblclick label': 'onEditClick',
 630        'keypress .edit': 'onEditKeypress',
 631        'blur .edit'    : 'onEditBlur',
 632        'click .toggle' : 'toggle'
 633      },
 634
 635      initialize: function() {
 636        this.bindTo(this.model, 'change', this.render, this);
 637      },
 638
 639      onRender: function() {
 640        this.$el.removeClass( 'active completed' );
 641      
 642        if ( this.model.get( 'completed' )) {
 643          this.$el.addClass( 'completed' );
 644        } else { 
 645          this.$el.addClass( 'active' );
 646        }
 647      },
 648
 649      destroy: function() {
 650        this.model.destroy();
 651      },
 652
 653      toggle: function() {
 654        this.model.toggle().save();
 655      },
 656
 657      onEditClick: function() {
 658        this.$el.addClass('editing');
 659        this.ui.edit.focus();
 660      },
 661      
 662      updateTodo : function() {
 663        var todoText = this.ui.edit.val();
 664        if (todoText === '') {
 665          return this.destroy();
 666        }
 667        this.setTodoText(todoText);
 668        this.completeEdit();
 669      },
 670
 671      onEditBlur: function(e){
 672        this.updateTodo();
 673      },
 674
 675      onEditKeypress: function(e) {
 676        var ENTER_KEY = 13;
 677        var todoText = this.ui.edit.val().trim();
 678
 679        if ( e.which === ENTER_KEY && todoText ) {
 680          this.model.set('title', todoText).save();
 681          this.$el.removeClass('editing');
 682        }
 683      },
 684      
 685      setTodoText: function(todoText){
 686        if (todoText.trim() === ""){ return; }
 687        this.model.set('title', todoText).save();
 688      },
 689
 690      completeEdit: function(){
 691        this.$el.removeClass('editing');
 692      }
 693  });
 694
 695  // Item List View
 696  // --------------
 697  //
 698  // Controls the rendering of the list of items, including the
 699  // filtering of active vs completed items for display.
 700
 701  Views.ListView = Backbone.Marionette.CompositeView.extend({
 702      template: '#template-todoListCompositeView',
 703      childView: Views.ItemView,
 704      childViewContainer: '#todo-list',
 705
 706      ui: {
 707        toggle: '#toggle-all'
 708      },
 709
 710      events: {
 711        'click #toggle-all': 'onToggleAllClick'
 712      },
 713
 714      initialize: function() {
 715        this.bindTo(this.collection, 'all', this.update, this);
 716      },
 717
 718      onRender: function() {
 719        this.update();
 720      },
 721
 722      update: function() {
 723        function reduceCompleted(left, right) { 
 724          return left && right.get('completed'); 
 725        }
 726        
 727        var allCompleted = this.collection.reduce(reduceCompleted,true);
 728        this.ui.toggle.prop('checked', allCompleted);
 729        this.$el.parent().toggle(!!this.collection.length);
 730      },
 731
 732      onToggleAllClick: function(e) {
 733        var isChecked = e.currentTarget.checked;
 734        this.collection.each(function(todo) {
 735          todo.save({'completed': isChecked});
 736        });
 737      }
 738  });
 739
 740  // Application Event Handlers
 741  // --------------------------
 742  //
 743  // Handler for filtering the list of items by showing and
 744  // hiding through the use of various CSS classes
 745  
 746  App.vent.on('todoList:filter',function(filter) {
 747    filter = filter || 'all';
 748    $('#todoapp').attr('class', 'filter-' + filter);
 749  });
 750
 751});
 752
 753```
 754
 755At 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.
 756
 757Finally, 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.
 758
 759**TodoMVC.Todos.js:**
 760
 761```javascript
 762TodoMVC.module('Todos', function(Todos, App, Backbone, Marionette, $, _) {
 763
 764  // Local Variables
 765  // ---------------
 766
 767  var localStorageKey = 'todos-backbone-marionettejs';
 768
 769  // Todo Model
 770  // ----------
 771  
 772  Todos.Todo = Backbone.Model.extend({
 773    localStorage: new Backbone.LocalStorage(localStorageKey),
 774
 775    defaults: {
 776      title: '',
 777      completed: false,
 778      created: 0
 779    },
 780
 781    initialize: function() {
 782      if (this.isNew()) {
 783        this.set('created', Date.now());
 784      }
 785    },
 786
 787    toggle: function() {
 788      return this.set('completed', !this.isCompleted());
 789    },
 790
 791    isCompleted: function() { 
 792      return this.get('completed'); 
 793    }
 794  });
 795
 796  // Todo Collection
 797  // ---------------
 798
 799  Todos.TodoList = Backbone.Collection.extend({
 800    model: Todos.Todo,
 801
 802    localStorage: new Backbone.LocalStorage(localStorageKey),
 803
 804    getCompleted: function() {
 805      return this.filter(this._isCompleted);
 806    },
 807
 808    getActive: function() {
 809      return this.reject(this._isCompleted);
 810    },
 811
 812    comparator: function(todo) {
 813      return todo.get('created');
 814    },
 815
 816    _isCompleted: function(todo) {
 817      return todo.isCompleted();
 818    }
 819  });
 820
 821});
 822
 823```
 824
 825We finally kick-start everything off in our application index file, by calling `start` on our main application object:
 826
 827Initialization:
 828
 829```javascript
 830      $(function() {
 831        // Start the TodoMVC app (defined in js/TodoMVC.js)
 832        TodoMVC.start();
 833      });
 834```
 835
 836And that's it!
 837
 838### Is the Marionette implementation of the Todo app more maintainable?
 839
 840Derick 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.
 841
 842The 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.
 843
 844The 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.
 845
 846
 847The 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:
 848
 849* [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/)
 850* [http://hilojs.codeplex.com/discussions/362875#post869640](http://hilojs.codeplex.com/discussions/362875#post869640)
 851
 852### Marionette And Flexibility
 853
 854Marionette 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.
 855
 856The 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:
 857
 858* [Simple](https://github.com/jsoverson/todomvc/tree/master/labs/architecture-examples/backbone_marionette) - by Jarrod Overson
 859* [RequireJS](https://github.com/jsoverson/todomvc/tree/master/labs/dependency-examples/backbone_marionette_require) - also by Jarrod
 860* [Marionette modules](https://github.com/derickbailey/todomvc/tree/marionette/labs/architecture-examples/backbone_marionette/js) - by Derick Bailey
 861
 862**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.
 863
 864**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.
 865
 866**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.
 867
 868Marionette 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.
 869
 870But 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.
 871
 872
 873This 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.
 874
 875### And So Much More
 876
 877This 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.
 878
 879To 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).
 880
 881
 882<p>&nbsp;</p>
 883<p>&nbsp;</p>
 884
 885
 886
 887## Thorax
 888
 889*By Ryan Eastridge & Addy Osmani*
 890
 891Part 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.
 892
 893
 894Thorax 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).
 895
 896### Hello World
 897
 898In 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.
 899
 900`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`:
 901
 902```javascript
 903    var view = new Thorax.View({
 904        greeting: 'Hello',
 905        template: Handlebars.compile('{{greeting}} World!')
 906    });
 907    view.appendTo('body');
 908```
 909
 910In 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.
 911
 912 If a `model` is set on a view, its attributes also become available to the template:
 913
 914    var view = new Thorax.View({
 915        model: new Thorax.Model({key: 'value'}),
 916        template: Handlebars.compile('{{key}}')
 917    });
 918
 919### Embedding child views
 920
 921The view helper allows you to embed other views within a view. Child views can be specified as properties of the view:
 922
 923```javascript
 924    var parent = new Thorax.View({
 925        child: new Thorax.View(...),
 926        template: Handlebars.compile('{{view child}}')
 927    });
 928```
 929
 930Or 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:
 931
 932```javascript
 933    var ChildView = Thorax.View.extend({
 934        name: 'child',
 935        template: ...
 936    });
 937  
 938    var parent = new Thorax.View({
 939        template: Handlebars.compile('{{view "child" key="value"}}')
 940    });
 941```
 942
 943The 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:
 944
 945```handlebars
 946    {{#view child}}
 947        child will have this block
 948        set as its template property
 949    {{/view}}
 950```
 951
 952Handlebars 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:
 953
 954```html
 955    <div data-view-placeholder-cid="view2"></div>
 956```
 957
 958Then 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:
 959
 960```javascript
 961    this.$el.find('[data-view-placeholder-cid]').forEach(function(el) {
 962        var cid = el.getAttribute('data-view-placeholder-cid'),
 963            view = this.children[cid];
 964        view.render();
 965        $(el).replaceWith(view.el);
 966    }, this);
 967```
 968
 969### View helpers
 970
 971One 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.
 972
 973A 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:
 974
 975    Handlebars.registerViewHelper('on', function(eventName, helperView) {
 976        helperView.parent.on(eventName, function() {
 977            helperView.render();
 978        });
 979    });
 980
 981An 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:
 982
 983```handlebars
 984    {{#on "incremented"}}{{i}}{{/on}}
 985    {{#button trigger="incremented"}}Add{{/button}}
 986```
 987
 988And the corresponding view class:
 989
 990```javascript
 991    new Thorax.View({
 992        events: {
 993            incremented: function() {
 994                ++this.i;
 995            }
 996        },
 997        initialize: function() {
 998            this.i = 0;
 999        },
1000        template: ...
1001    });
1002```
1003
1004### collection helper
1005
1006The `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:
1007
1008```handlebars
1009    {{#collection kittens}}
1010      <li>{{name}}</li>
1011    {{/collection}}
1012```
1013
1014And the corresponding view:
1015
1016```javascript
1017    new Thorax.View({
1018      kittens: new Thorax.Collection(...),
1019      template: ...
1020    });
1021```
1022
1023The 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:
1024
1025- `item-template` - A template to display for each model. If a block is specified it will become the item-template
1026- `item-view` - A view class to use when each item view is created
1027- `empty-template` - A template to display when the collection is empty. If an inverse / else block is specified it will become the empty-template
1028- `empty-view` - A view to display when the collection is empty
1029
1030Options 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:
1031
1032```handlebars
1033    {{#collection kittens item-view="KittenView" tag="ul"}}
1034      <li>{{name}}</li>
1035    {{else}}
1036      <li>No kittens!</li>
1037    {{/collection}}
1038```
1039
1040Note 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...
1041
1042```handlebars
1043    {{#collection kittens}}
1044      <h2>{{name}}</h2>
1045      <p>Kills:</p>
1046      {{#collection miceKilled tag="ul"}}
1047        <li>{{name}}</li>
1048      {{/collection}}
1049    {{/collection}}
1050```
1051
1052### Custom HTML data attributes
1053
1054Thorax 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:
1055
1056```javascript
1057  MyApplication.View = Backbone.View.extend({
1058    setElement: function() {
1059        var response = Backbone.View.prototype.setElement.apply(this, arguments);
1060        this.name && this.$el.attr('data-view-name', this.name);
1061        this.$el.attr('data-view-cid', this.cid);
1062        this.collection && this.$el.attr('data-collection-cid', this.collection.cid);
1063        this.model && this.$el.attr('data-model-cid', this.model.cid);
1064        return response;
1065    }
1066  });
1067```
1068
1069In 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:
1070
1071
1072```javascript
1073    MyApplication.View = Backbone.View.extend({
1074        _configure: function() {
1075            Backbone.View.prototype._configure.apply(this, arguments);
1076            Thorax._viewsIndexedByCid[this.cid] = this;
1077        },
1078        dispose: function() {
1079            Backbone.View.prototype.dispose.apply(this, arguments);
1080            delete Thorax._viewsIndexedByCid[this.cid];
1081        }
1082    });
1083```
1084
1085Then we can extend jQuery / Zepto:
1086
1087```javascript
1088    $.fn.view = function() {
1089        var el = $(this).closest('[data-view-cid]');
1090        return el && Thorax._viewsIndexedByCid[el.attr('data-view-cid')];
1091    };
1092
1093    $.fn.model = function(view) {
1094        var $this = $(this),
1095            modelElement = $this.closest('[data-model-cid]'),
1096            modelCid = modelElement && modelElement.attr('[data-model-cid]');
1097        if (modelCid) {
1098            var view = $this.view();
1099            return view && view.model;
1100        }
1101        return false;
1102    };
1103```
1104
1105Now 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:
1106
1107```handlebars
1108    {{#collection kittens tag="ul"}}
1109      <li>{{name}}</li>
1110    {{/collection}}
1111```
1112
1113And the corresponding view class:
1114
1115```javascript
1116    Thorax.View.extend({
1117      events: {
1118        'click li': function(event) {
1119          var kitten = $(event.target).model();
1120          console.log('Clicked on ' + kitten.get('name'));
1121        }
1122      },
1123      kittens: new Thorax.Collection(...),
1124      template: ...
1125    });  
1126```
1127
1128A 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:
1129
1130
1131```css
1132  [data-view-name="child"] {
1133
1134  }
1135```
1136
1137### Thorax Resources
1138
1139No Backbone related tutorial would be complete without a todo application. A [Thorax implementation of TodoMVC](http://todomvc.com/examples/thorax/) is available, in addition to this far simpler example composed of this single Handlebars template:
1140
1141
1142```handlebars
1143  {{#collection todos tag="ul"}}
1144    <li{{#if done}} class="done"{{/if}}>
1145      <input type="checkbox" name="done"{{#if done}} checked="checked"{{/if}}>
1146      <span>{{item}}</span>
1147    </li>
1148  {{/collection}}
1149  <form>
1150    <input type="text">
1151    <input type="submit" value="Add">
1152  </form>
1153```
1154
1155and the corresponding JavaScript:
1156
1157```javascript
1158  var todosView = Thorax.View({
1159      todos: new Thorax.Collection(),
1160      events: {
1161          'change input[type="checkbox"]': function(event) {
1162              var target = $(event.target);
1163              target.model().set({done: !!target.attr('checked')});
1164          },
1165          'submit form': function(event) {
1166              event.preventDefault();
1167              var input = this.$('input[type="text"]');
1168              this.todos.add({item: input.val()});
1169

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