backbone-nested

A plugin to make Backbone.js keep track of nested attributes.

Backbone-Nested

A plugin to make Backbone.js keep track of nested attributes. Download the latest version and see the changelog/history/release notes on the Releases page. Supports Backbone 0.9.x and 1.x.

Suppose you have a Backbone Model with nested attributes, perhaps to remain consistent with your document-oriented database. Updating the nested attribute won't cause the model's "change" event to fire, which is confusing.

var user = new Backbone.Model({
  name: {
    first: 'Aidan',
    last: 'Feldman'
  }
});
 
user.bind('change', function(){
  // this is never reached! 
});
 
user.get('name').first = 'Bob';
user.save();

Wouldn't it be awesome if you could do this?

user.bind('change:name.first', function(){ ... });

Bower

Recommended.

  1. Install the latest version:

    bower install backbone backbone-nested-model jquery underscore --save
  2. Add backbone-nested.js to your HTML <head>:

    <!-- must loaded in this order -->
    <script type="text/javascript" src="/bower_components/jquery/jquery.js"></script>
    <script type="text/javascript" src="/bower_components/underscore/underscore.js"></script>
    <script type="text/javascript" src="/bower_components/backbone/backbone.js"></script>
    <script type="text/javascript" src="/bower_components/backbone-nested-model/backbone-nested.js"></script>

Download the latest release and the dependencies listed above, then include with script tags in your HTML.

  1. Change your models to extend from Backbone.NestedModel, e.g.

    var Person = Backbone.Model.extend({ ... });
     
    // becomes 
     
    var Person = Backbone.NestedModel.extend({ ... });
  2. Change your getters and setters to not access nested attributes directly, e.g.

    user.get('name').first = 'Bob';
     
    // becomes 
     
    user.set({'name.first': 'Bob'});

Best of all, Backbone.NestedModel is designed to be a backwards-compatible, drop-in replacement of Backbone.Model, so the switch can be made painlessly.

get() and set() will work as before, but nested attributes should be accessed using the Backbone-Nested string syntax:

// dot syntax 
user.set({
  'name.first': 'Bob',
  'name.middle.initial': 'H'
});
user.get('name.first') // returns 'Bob' 
user.get('name.middle.initial') // returns 'H' 
 
// object syntax 
user.set({
  'name': {
    first: 'Barack',
    last: 'Obama'
  }
});
// object syntax 
user.set({
  'addresses': [
    {city: 'Brooklyn', state: 'NY'},
    {city: 'Oak Park', state: 'IL'}
  ]
});
user.get('addresses[0].state') // returns 'NY' 
 
// square bracket syntax 
user.set({
  'addresses[1].state': 'MI'
});

"change" events can be bound to nested attributes in the same way, and changing nested attributes will fire up the chain:

// all of these will fire when 'name.middle.initial' is set or changed 
user.bind('change', function(modelnewVal){ ... });
user.bind('change:name', function(modelnewName){ ... });
user.bind('change:name.middle', function(modelnewMiddleName){ ... });
user.bind('change:name.middle.initial', function(modelnewInitial){ ... });
 
// all of these will fire when the first address is added or changed 
user.bind('change', function(modelnewVal){ ... });
user.bind('change:addresses', function(modeladdrs){ ... });
user.bind('change:addresses[0]', function(modelnewAddr){ ... });
user.bind('change:addresses[0].city', function(modelnewCity){ ... });

Additionally, nested arrays fire "add" and "remove" events:

user.bind('add:addresses', function(modelnewAddr){ ... });
user.bind('remove:addresses', function(modeloldAddr){ ... });

Acts like set(), but appends the item to the nested array. For example:

user.get('addresses').length; //=> 2 
user.add('addresses', {
  city: 'Seattle',
  state: 'WA'
});
user.get('addresses').length; //=> 3 

Acts like unset(), but if the unset item is an element in a nested array, the array will be compacted. For example:

user.get('addresses').length; //=> 2 
user.remove('addresses[0]');
user.get('addresses').length; //=> 1 

Note, this plugin does not handle non-embedded relations, which keeps it relatively simple. If you support for more complex relationships between models, see the Backbone plugin wiki page.

Pull requests are more than welcome - please add tests, which can be run by opening test/index.html. They can also be run from the command-line (requires PhantomJS):

$ npm install
$ grunt

See also: live tests for latest release.