Backbone - Why doesn't a collection.reset trigger a model event?

Julian Krispel-Samsel picture Julian Krispel-Samsel · Aug 2, 2012 · Viewed 7.8k times · Source

I'm curious to find out why resetting a backbone collection doesn't fire a model event. However, it seems only logical to fire a model event when a model is physically being removed from a collection.

Is this intentional or am I missing something? If backbone doesn't do this sort of thing what's a good practice for delegating events like so.

Why does backbone not trigger a model event when its collection resets?

var TicketModel = Backbone.Model.extend({
    defaults: {
        name: 'crafty',
        email: '[email protected]'
    },
    initialize: function(){
        this.on("all", function(event){
            console.log(event)
        });
    }

});

var TicketCollection = Backbone.Collection.extend({
    model: TicketModel,

    });


var tickets = new TicketCollection([
    {
        name: 'halldwq'
    },
    {
        name: 'dascwq'
    },
    {
        name: 'dsacwqe'
    }

]);

tickets.reset();

Answer

jakee picture jakee · Aug 2, 2012

This is the backbone reset function:

reset: function(models, options) {
  models  || (models = []);
  options || (options = {});
  for (var i = 0, l = this.models.length; i < l; i++) {
    this._removeReference(this.models[i]);
  }
  this._reset();
  this.add(models, _.extend({silent: true}, options));
  if (!options.silent) this.trigger('reset', this, options);
  return this;
},

We can ignore the last 3 lines because you don't supply any models to the reset-function. Also let's ignore the first 2 lines as well. So first we loop through the models in this collection and call the collection's _removeReference(model) method, it looks like this:

_removeReference: function(model) {
  if (this == model.collection) {
    delete model.collection;
  }
  model.off('all', this._onModelEvent, this);
},

What happens here is that we're removing the collection-property from the model object altogether and also remove the binding to this model's events. Next up we call the collection's _reset()-function, that looks like this:

_reset: function(options) {
  this.length = 0;
  this.models = [];
  this._byId  = {};
  this._byCid = {};
}, 

It just outright removes any reference to any models the collection has ever had.

What can we make from this? Well the collection reset -function in Backbone basically just circumvents all the official channels of removing models and does it all in hush hush secrecy, causing no other events than reset to be fired. So you want to fire the model's remove event for every model removed from a collection during reset? Easy! Just overwrite Backbone.Collection's reset-function like this:

var Collection = Backbone.Collection.extend({
  reset: function(models, options) {
    models  || (models = []);
    options || (options = {});

    for (var i = 0, l = this.models.length; i < l; i++) {
      this._removeReference(this.models[i]);
      // trigger the remove event for the model manually
      this.models[i].trigger('remove', this.models[i], this);
    }

    this._reset();
    this.add(models, _.extend({silent: true}, options));
    if (!options.silent) this.trigger('reset', this, options);
    return this;
  }
});

Hope this helps!