Monday, March 31, 2014

Backbone: Combining The where() and toJSON() Collection Methods

The two Backbone collection methods I use most often are probably where(), and toJSON(). However, they're difficult to use together sometimes. For example, I can filter my collection before I actually do anything with it using where(), but this gives me back an array of models. I can't do further Lodash processing on this array, because it has full-fledged models, not objects in it. I can't pass this array to my template context because, again, it's looking for plain objects, and not Backbone model instances.

The other side of the coin is to always use toJSON() before performing any Lodash data transformations and before passing the data to my template. Of course, this is possibly the most inefficient route since I'm serializing a lot of data that I'm not even using. For example, imaging a collection with a thousand models in it. Calling toJSON() has to iterate over the entire collection. That's not all that bad, but, what if the next step is to filter down the toJSON() array? Let's say you filtered the resulting array down to 50 elements — you wasted 950 iterations on serializing unneeded data.

With Backbone collections, it's always the safer bet to perform any filter operations before performing serialization operations. Easy enough, aside from the fact that the where() method returns an array of models. One solution is to combine the two methods into one. Here's an example.

var MyCollection = Backbone.Collection.extend({

    whereJSON: function( args ) {
        return this.toJSON.call(
            _( this.where( args ) ) ).value();
    }

});

var MyView = Backbone.View.extend({
    render: function() {
        this.$el.html( JSON.stringify(
            this.collection.whereJSON({
                active: true
            }), undefined, 2 ) );    
        return this;
    }
});
        
new MyView({
    tagName: 'pre',
    collection: new MyCollection([
        { name: 'Name1', active: true },
        { name: 'Name2', active: false },
        { name: 'Name3', active: true },
        { name: 'Name4', active: false }
    ])
}).render().$el.appendTo( 'body' );

The whereJSON() method in MyCollection is an attempt to bridge the gap between filtering collections and serializing them. You invoke it as you would the where() method — passing in a filtering object. The difference is in the result. It's as though you called toJSON(). So in one step, you have your filtered data that you can further manipulate with Lodash, or pass straight to your template.

It works by calling the where() method as usual, and wrapping that response in a Lodash object. This, in turn, gets passed to the toJSON() method as the context. The reason we wrap the resulting array in a Lodash object is because toJSON() calls map() on this. So, we give it a this value that will work as expected. You can see in the view that as expected, the result is real JSON data, filtered to include only active items.