Kleo Petrov
  • Home
  • About
  • Archive

Backbone.js Collections Explained

10 Dec 2015 • 12  min read

Collections in Backbone are set of Models created by extending Backbone.Collection.

When creating a collection, we also want to define a property specifying the type of model that the collection will contain and the instance properties required.

var Todo = Backbone.Model.extend({
    defaults: {
        title: '', 
        completed: false
    }
});

var TodosCollection = Backbone.Collection.extend({
    model: Todo
});

var myTodo = new Todo({ title: 'Sample todo', id: 1});

var todos = new TodosCollection({ myTodo });

console.log('The collection has ' + todos.length + ' item.') // will log "The collection has 1 item" 

Adding and removing Models

In the last example, the collection was populated using an array of models when it was instantiaded. After the collection has been created, models can be added and removed using add() and remove() methods.

var Todo = Backbone.Model.extend({
    defaults: {
        title: '',
        completed: false
    }
});

var TodoCollection = Backbone.Collection.extend({
    model: Todo
});

var todo1 = new Todo({ title: 'Write a blog post' });
var todo2 = new Todo({ title: 'Exercise' });
var todo3 = new Todo({ title: 'Create a simple Todo App '});

var todos = new TodoCollection([todo1, todo2]);
console.log('Collection size: ' + todos.length); // Will log Collection size: 2

todos.remove([todo1, todo2]);
console.log('Collection size: ' + todos.length); // Will log Collection size: 0

todo.add(todo3);
console.log('Collection size: ' + todos.length); // Will log Collection size: 1

Note that add() and remove() accept individual models and array of models.

Also, when using add(), passing {merge: true} causes duplicate models to have their attributes merged in to the existing models, instead of being ignored. By default the merge option is set to false.

var items = new Backbone.Collection();

items.add([ {id: 1, name: 'Dog', age: 3 }, { id: 2, name: 'Cat', age: 2 }]);
items.add([ {id: 1, name: 'Bear' } ], {merge: true});
items.add({id: 2, name: 'Lion'}); // { merge: false }

console.log(JSON.stringify(items.toJSON())); 
// Will log { {"id": 1, "name": "Bear", "age 3" }, {"id": 2, "name": "Cat", "age": 2} }

Retrieving Models

There are a few ways of retrieving a Model from a Collection. The most straight-forward is to use Collection.get(), which accepts a single id as follows:

var myTodo = new Todo({ title: 'Read a book', id: 2});

var todos = new TodoCollection([myTodo]);

var todo2 = todos.get(2);

console.log(todo2 === myTodo) // will return true

Each model in Backbone has an id, which is unique identifier that is either an integer or string. Models also have a cid (client id) which is automatically generated by Backbone when the model is created. Either identifier can be used to retrieve a model from collection.

The main difference between them is that cid is generated by Backbone.

The idAttribute is the identifying attribute name of the model returned from the server (i.e the id in your database). This tells Backbone which data field from a server should be used to populate the id property.

The value of a model’s idAttribute should be set by the server when the model is saved. After this point you shouldn’t need to set it manually, unless further control is required.

Backbone.Collection contains an array of models enumerated by their id property, if the model instances happen to have one. When collection.get(id) is called, the array is checked for existence of the model instance with the corresponding id.

var todoCid = todos.get(todo2.cid);

console.log(myTodo === todoCid) // will return true

Listening for Events

Collections represent a group of items, so we can listen for add or remove events. Here is an example:

var TodoCollection = new Backbone.Collection();

TodoCollection.on('add', function(todo) {
    console.log('I should ' + todo.get('title') + '. Have you done it before? ' + (todo.get('completed') ? 'Yes!' : 'No');
});

TodosCollection.add([
    { title: 'go to Australia', completedA: false },
    { title: 'go to London', completed: false },
    { title: 'go to Paris', completed: true }
]);

// Will log:
//I should go to Australia. Have I done it before: No
// I should go to London. Have I done it before: No
// I should go to Paris. Have I done it before: Yes

We can also bind a change event to listen for changes to any of the models in the collection.

TodoCollection.on('change:title', function(todo){
    console.log('Changed my mind.  I should ' + todo.get('title'));
});

jQuery-style event maps of the form object.on({event: action}) can also be used.

var myTodo = new Todo(); // instantiate a new Backbone Model

myTodo.on({
    'change:title': titleChanged,
    'change: completed': stateChanged
});

function titleChanged() {
    console.log('Title Changed');
}

function stateChanged() {
    console.log('State Changed');
}

myTodo.set({ title: 'Get to the market' }) // Will log "Title Changed"

Resetting/Refreshing Collections

Rather than adding or removing models individually, we can update an entire collection at once. Collection.set() takes an array of models and performs the necessary add, remove or change operations.

var TodoCollection = new Backbone.Collection();

TodoCollections.add([
    { id: 1, title: 'go to Jamaica.', completed: false },
    { id: 2, title: 'go to China.', completed: false },
    { id: 3, title: 'go to Disneyland.', completed: true }
]);

TodoCollection.on('add', function(model) {
    console.log('New Model Added');
});

TodoCollection.on('remove', function(model) {
    console.log('Model Removed');
});

Todos.on('change:completed', function(model) {
    console.log('Completed ' + model.get('title'));
})

If we want to simply replace the entire content of the collection we can use Collection.reset().

Using reset with no arguments will clear out the collection completely. This is useful then dynamically loading a new page of results where you want to blank out the current page of results.

myCollection.reset()

Underscore utility functions

Backbone takes full advantage of its hard dependency on Underscore by making many of its utilities directly available on collections:

  • forEach: itarate over collections
var todos = new Backbone.Collection();

todos.add([
    {title: 'clean the room', completed: false}, 
    {title: 'make breakfast', completed: false},
    {title: 'read a book', completed: true}
]);

todo.forEach(function(model) {
    console.log(model.get('title'))
});

// Will log:
// "clean the room"
// "make breakfast"
// "read a book"
  • sortBy(): sort a collection on a specific attribute
  • map(): iterate through a collection, mapping each value through a transformation function
  • min()/ max(): retrieve item with the max or min value of an attribute
  • pluck(): extract a specific attribute
var captions = todos.pluck('caption');
console.log(captions) // will return a list of captions
  • filter(): filter a collection
  • indexOf(): return the index of a particular item within a collection
  • any(): confirm if any of the values in a collection pass an iterator truth test
  • size(): return the size of a collection
  • isEmpty(): determine whether a collection is empty
  • groupBy(): group a collection into groups of like items
  • pick(): extract a set of attributes from a model
  • omit(): extract all attributes from a model except those listed
  • keys(), values(): get list of attribute names and values
  • pairs(): get list of attributes as [key, value] pairs
  • invert(): create object in which the values are keys and the attributes are values

Chainable API

Another great feature in Backbone is its support for Underscore’s chain() method. Chaining is common idiom on object-oriented languages.

A chain is a sequence of method calls on the same objects that are performed is a single statement.

The chain() method returns an object that has all of the Underscore array operations attached as methods which return that object. The chain ends with a call to the value() method which simply returns the resulting array value.

var collection = new Backbone.Collection({
    { name: 'Ivan', age: 24 },
    { name: 'George', age: 18 },
    { name: 'Chris', age: 28 }
});

var filteredNames = collection.chain() // start chain
    .filter(function(model) { return model.get('age') > 20); })
    .map(function(model) { return item.get('name') })
    .value();
    
    console.log(filteredNames) // Will log ['Ivan', 'Chris']

Made with ♥ by Kliment Petrov
Github Icon Twitter Icon LinkedIn Icon