Wednesday, July 2, 2014

Filtering Models When Cloning Backbone Collections

Filtering collections is fundamental to most Backbone applications. This is generally done within the context of a view. The view needs to display some subset of the collection. It selects the models it needs, by calling the where() method on the collection. This gives us an array of model elements. However, the downside with where() is that once you call it, you're now working with an array of models — not a collection of models.

The distinction is important because sometimes, you want to listen for events on the filtered version of the collection. Plain arrays don't have a listenTo() method. This can be remedied by creating a new instance of the collection, passing it the array of models. Now you have a collection that only contains the models you care about, and you can listen to events on it.

Here's an example that shows a more integrated solution to the problem. I really like the clone() method of Backbone.Collection. It encapsulates the creation of a new collection, and by default, passes in the exact same models. Every collection has a clone() method, so it's not necessary to know which type of collection we're working with ahead of time. This bodes well for implementing generic functions that work with collections.

var Collection = Backbone.Collection,
    BaseCollection = Collection.extend({
        
        clone: function( where ) {
            if ( where ) {
                return new this.constructor(
                    this.where( where ),
                    { model: this.model,
                      comparator: this.comparator });
            }
            
            return Collection.prototype.clone
                .call( this );
        }

    }),
    
    myColl = new BaseCollection([
        { name: 'm1', enabled: true },
        { name: 'm2', enabled: false },
        { name: 'm3', enabled: true },
        { name: 'm4', enabled: false }
    ]);

console.log( myColl.clone( { enabled: true } )
    .toJSON() );

This works be overriding the clone() method, providing an optional where parameter. If it's there, we call the where() method using this argument, and passing the result to the new collection. Otherwise, we fall back to the default implementation. So instead of having to clone the new collection then perform filtering, we can just pass the filter criteria directly to clone(). Essentially, we're turning a two-step process into a one-step process.