11

I have a Backbone app that uses nested collections (at least that's how I think they're called).

In my particular case there are tabs and subtabs, and each tab (model) contains a collection of subtab (model).

For those who're more familiar with code, I'll write bellow my models and collections, and how subtabs are nested inside the tab model:

// Subtab Model
var Subtab = Backbone.Model.extend({
    defaults: { label: undefined }
});

// Subtabs Collection
var Subtabs = Backbone.Collection.extend({
    model: Subtab
});

// Tab Model
var Tab = Backbone.Model.extend({
    defaults: { label: undefined, subtabs: new Subtabs}
});

// Tabs Collection
var Tabs = Backbone.Collection.extend({
    model: Tab
});

Now, when I change a tab's attribute it fires the change event on the Tab model and also on the Tabs collection (quite normal, right?), but when I change a subtab's attribute, it fires the change event on the Subtab model and Subtabs collection (this is also normal) but it doesn't bubble up to the Tab model (and to the Tabs collection).

At least, I guess it should because a collection inside a model was changed and so the model was changed (but maybe I'm wrong and I'm not getting it).

Any suggestion on how can I achieve this behavior with Backbone?

Chris X
  • 901
  • 3
  • 9
  • 19
  • trigger the event manually when you receive it – Cory Danielson Apr 06 '13 at 23:38
  • @CoryDanielson I imagined that the event needed to be triggered 'manually' but it didn't work, I got _RangeError: Maximum call stack size exceeded_. – Chris X Apr 07 '13 at 17:06
  • Are you doing something with setTimeout? That's not an error I've ever gotten before while using Backbone – Cory Danielson Apr 07 '13 at 18:22
  • @CoryDanielson No, not at all! – Chris X Apr 07 '13 at 20:04
  • I've never seen a call stack error happen from something non-asynchronous... something async has to be going on... is the event triggering a bunch of ajax calls? do you have a setInterval running at the same time? How big is the collection? What does this.subtabsChanged that you mentioned in the other comment do? – Cory Danielson Apr 07 '13 at 22:07
  • 1
    @ChrisX Usually "Maximum call stack size exceeded" is a strong indicator that you have infinite recursion going on. Could a change listener on your outer model be doing something that would trigger change on an inner model? Then the change events would loop around forever... – peterflynn Dec 11 '13 at 01:57

1 Answers1

7

A change event is triggered by Backbone when an attribute changes via set. The event is then also triggered on the collection, as you have seen. But, the value of your subtabs attribute is not changing at all, it is still the same object you created in defaults. If you used tab.set('subtabs', new Subtabs); you would get a change:subtabs event, but you don't want to do that.

I think you would have to do something in your code to trigger a new event on your Tab model.

// Tab Model
var Tab = Backbone.Model.extend({
    defaults: { label: undefined, subtabs: new Subtabs},
    initialize: function(){
        // listen for change in subtabs collection.
        this.get('subtabs').on('change', this.subtabsChanged, this);
    },
    subtabsChanged: function(model) {
        // trigger new event.
        this.trigger('subtab:change', this, model);
    }
});

Then you could listen for the event:

tabs.on('subtab:change', function(tab, subtab) { 
    console.log(tab.get('label'));
    console.log(subtab.get('label'));
});

This will work, I think. But I guess I am wondering why you would listen to your Tabs collection for a change in the Subtabs. In most cases, it might be better to just listen for the change event on your Subtabs collection itself.

tab.get('subtabs').on('change', function(model){
    // do something with model, which is a Subtab.
});

EDIT: http://jsfiddle.net/phoenecke/DvHqH/1/

Paul Hoenecke
  • 5,060
  • 1
  • 20
  • 20
  • Based on your answer and with some changes to your code, I managed to trigger the original `change` event on the model. I was doing wrong binding the `change` event without any context `this.get('subtabs').on('change', this.subtabsChanged, this);` and I was getting an error _RangeError: Maximum call stack size exceeded_. So, `this` is the key. – Chris X Apr 07 '13 at 14:27
  • what does this.subtabsChanged do? – Cory Danielson Apr 07 '13 at 21:47
  • @CoryDanielson Trigger an event on the Tab when something in the subtabs child collection changed... – Paul Hoenecke Apr 08 '13 at 00:52
  • @PaulHoenecke sorry, I was asking Chris X trying to figure out why he's getting the RangeError – Cory Danielson Apr 08 '13 at 05:03
  • @CoryDanielson as you can see, the `.subtabsChanged` is the handler for the change event when is triggered by the subtabs collection. This handler triggers the custom event `subtab:change` for a tab (model). – Chris X Apr 08 '13 at 12:13
  • @CoryDanielson. Updated with jsFiddle. The stack size error is not in this code. – Paul Hoenecke Apr 08 '13 at 13:35
  • note, `listenTo()` might be preferred to `on()`. http://backbonejs.org/#Events-listenTo – ericsoco Jul 08 '13 at 16:57
  • re: "I am wondering why you would listen to your Tabs collection for a change in the Subtabs", one example might be when you have one View representing the contents of the Tabs and Subtabs collection... – ericsoco Jul 08 '13 at 17:25