Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When to use bindTo, bind or on with Backbone.Marionette?

Tags:

This time I'm struggling with the different methods to bind events. I've all the mentioned methods in my code. I just don't know, if I'm on the right way. Maybe I should always use bindTo to ensure that my views are closed completely after a change (at present this would often yield errors)? Are there any best practices which will help me to get in the right direction?

To illustrate my current understanding of Marionette, here's one module from my app. As always, every hint is very welcome.

PP.module('Grid', function(Grid, PP, Backbone, Marionette, $, _){

  Grid.Product = Backbone.Model.extend({});

  Grid.ProductCollection = Backbone.Collection.extend({
    model: Grid.Product,
    url: '/products/query'
  });

  Grid.ProductView = Backbone.Marionette.ItemView.extend({
    className: 'grid',
    template: 'public/templates/grid-template'
  });

  // Helper Methods
  // -----------------------

  var getGenderCode = function(genderName){
    var genderMap = {
      'men': 'M',
      'women': 'W',
      'unisex': 'A'
    }

    return genderMap.genderName;
  }

  // Public API
  // -------------------

  Grid.renderGrid = function(productCollection){
    Grid.productView = new Grid.ProductView({
      collection: productCollection
    });

    Grid.productView.bind('show', function(){
      $('#isotope-container').isotope({
        itemSelector : '.item',
        containerStyle: {
          position: 'relative',
          zIndex: 1
        }
      });
    });

    PP.Layout.mainRegion.show(Grid.productView);
  }

  // Event Handlers
  // -----------------------

  PP.vent.bind('grid:requested', function(categoryData){
    // use bootstrapped data on first start
    if (PP.App.bootstrappedCategoryName !== categoryData.categoryName) {
      Grid.productCollection.fetch({
        data: {
          gender: getGenderCode(categoryData.categoryName),
          category: categoryData.categoryId
        }
      });
    }
    else {
      PP.vent.trigger('mainview:ready', {
        categoryName: PP.App.bootstrappedCategoryName
      });
    }
  });

  // Initializer
  // --------------------

  PP.addInitializer(function(options){
    Grid.productCollection = new Grid.ProductCollection();

    Grid.productCollection.on('reset', function(){
      Grid.renderGrid(Grid.productCollection);
    });

    Grid.productCollection.reset(options.newArrivalsList);
  });
});
like image 288
rzschoch Avatar asked Aug 02 '12 18:08

rzschoch


1 Answers

the general guidelines is that any time you have an object that is created and destroyed throughout the life of the application, and that object needs to bind to events from some other object, you should use the EventBinder.

Views And Memory Leaks

Views are the perfect example of this. Views get created and destroyed all the time. They also bind to a lot of different events from both the model and collection on the view. When the view is destroyed, those events need to be cleaned up. By using the built-in bindTo method on the view, the event handlers will be cleaned up for you. If you don't use bindTo and instead use on directly, you'll have to manually call off to unbind the events when the view is closed / destroyed. If you don't, you'll end up with zombies (memory leaks).

If you haven't read them yet, check out these articles:

http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/

http://lostechies.com/derickbailey/2012/03/19/backbone-js-and-javascript-garbage-collection/

Custom Event Groupings

Views are not the only place that this applies, though, and not the only use case for EventBinder.

If you are working with handful of objects, binding to their events, and those objects can be replaced with other object instances, then an EventBinder would be useful. In this case, think of the EventBinder as a collection or group of events for related objects.

Let's say you have ObjA, ObjB and ObjC. Each of these fires some events, and you want to make sure you clean up the event handlers when your code is done. This is easy with an EventBinder:



doStuff = {

  start: function(a, b, c){
    this.events = new Marionette.EventBinder();
    this.events.bindTo(a, "foo", this.doSomething, this);
    this.events.bindTo(b, "bar", this.anotherThing, this);
    this.events.bindTo(c, "baz", this.whatever, this);
  },

  doSomething: function(){ /* ... */ },
  anotherThing: function(){ /* ... */ },
  whatever: function(){ /* ... */ },

  stop: function(){
    this.events.unbindAll();
  }

}

doStuff.start(ObjA, ObjB, ObjC);

// ... some time later in the code, stop it

doStuff.stop();

Calling stop in this code will properly clean up all the event handlers for this use.

When To Use on/off Directly

The converse to all of this, is to say that you don't always need to use an EventBinder. You can get away without it, always. But you need to remember to clean up your events when necessary.

In situations that do not need event handlers to be cleaned up, there is no need to use an EventBinder, as well. This may be an event that a router triggers, or that the Marionette.Application object triggers. For those application lifecycle events, or events that need to live throughout the entire life of the app, don't bother with an EventBinder. Instead, just let the event handlers live on. When the person refreshes the browser window, or navigates to a different site, the handlers will be cleaned up at that point.

But when you need to manage your memory and event handlers, cleaning up references and closing things down without refreshing the page or navigating away from it, then EventBinder becomes important as it simplifies event management.

like image 151
Derick Bailey Avatar answered Oct 03 '22 21:10

Derick Bailey