Nested Collections in Backbone.js

JS

Having come from a Ruby / Rails background, I was initially offput by Backbone.js' paradigm of models, views, and collections. Now that I've had a chance to build a few small projects with the versatile framework, I have a much better understanding of how to structure complicated relationships in Backbone.

As the list of models in any project grows, you will almost certainly encounter a temptation to make a collection of collections. Consider a simple HTML5 audio player that can play or queue individual songs. We might represent it like this:

    var SongModel = Backbone.Model.extend({

  defaults: {
    playCount: 0,
  },

  play: function(){
    this.trigger('play', this);
  },

  enqueue: function(){
    this.trigger('enqueue', this);
  },

  dequeue: function(){
    this.trigger('dequeue', this);
  }
  // some functions omitted
});

var SongQueue = Backbone.Collection.extend({  
  playFirst: function(){
    var firstSong = this.at(0);
    firstSong.set('playCount', firstSong.get('playCount') + 1);
    firstSong.play();
  }
 // some functions omitted
});

So far, simple enough. We have a Song model that keeps track of its play count and can fire events that will be caught elsewhere and cause the song to be played or added/removed to/from the queue. We also have a SongQueue collection that can play the first Song it contains.

What if we wanted to add support for multiple playlists, each of which is a SongQueue? This wouldn't work:

    var Playlists = Backbone.Collection.extend({});
new Playlists({collection: SongQueue});  

As you may have guessed, a collection in Backbone.js can't contain other collections. Therefore, we represent the underlying collections as models. Here's how this works:

    var Playlist = Backbone.Model.extend({

  initialize: function(){
    this.set('songQueue', new SongQueue());
  },

  remove: function(){
    this.trigger('delete', this);
  }
});

var Playlists = Backbone.Collection.extend({

  model: Playlist,

  initialize: function(){
    this.on('delete', function(model){
      this.remove(model);
    });
  }
});

That's all there is to it! With each Playlist model having a property songQueue, which is in turn set to an instance of a SongQueue collection, we can manipulate the underlying Songs at will and fire / listen to the appropriate events at each level.

See the complete source code or play with the demo.

Valentyn Boginskey
Valentyn Boginskey

Valentyn is a system administrator turning web developer. He is passionate about privacy and virtual currencies. In his spare time, he enjoys mountain biking, skiing, backpacking, and racing go-karts.