Filtering a collection in backbone.js

Filtering a collection can seem a daunting task the first time, but this is in fact, really easy. There is more than one way to filter collections, here I wanted to present the solution that work best for me. Do not hesitate to comment on it, I’m always happy to improve my stuff.

Of course this post would not be complete without a little live demo

Setup the Collection

My favorite way of declaring a filter is doing it in the extend collection declaration like this:

myapp.collection.Tasks = Backbone.Collection.extend({
	currentStatus : function(status){
		return _(this.filter(function(data) {
		  	return data.get("completed") == status;
		}));
	},
	search : function(letters){
		if(letters == "") return this;
 
		var pattern = new RegExp(letters,"gi");
		return _(this.filter(function(data) {
		  	return pattern.test(data.get("name"));
		}));
	}
});
// We instantiate our collection
var myTasks = new myapp.collection.Tasks([task1,task2,task3]);

Here we got 2 filters, one that checks the completed attribute, and one that searches for a text pattern in the name attribute. I find the search functionality particularly useful. It’s a very easy and nice pattern, used with an input change event we can receive a list of models on the fly.

You might also wonder why I wrap my filters with _( ). Well it seems that without wrapping the filter with the underscore function, the filter does not return a collection, which was no use to me.

Of course we need to tie this to a view, we will need 2 views, one for the list container and our filters, and one that will create each item of our list.

myapp.view.TasksContainer = Backbone.View.extend({
	events: {
		"keyup #searchTask" : "search",
		"change #taskSorting":"sorts"
	},
	render: function(data) {
		$(this.el).html(this.template);
		return this;
	},
	renderList : function(tasks){
		$("#taskList").html("");
 
		tasks.each(function(task){
			var view = new myapp.view.TasksItem({
				model: task,
				collection: this.collection
			});
			$("#taskList").append(view.render().el);
		});
		return this;
	},
	initialize : function(){
		this.template = _.template($("#list_container_tpl").html());
		this.collection.bind("reset", this.render, this);
	},
	search: function(e){
		var letters = $("#searchTask").val();
		this.renderList(this.collection.search(letters));
	},	
	sorts: function(e){
		var status = $("#taskSorting").find("option:selected").val();
		if(status == "") status = 0;
		this.renderList(this.collection.currentStatus(status));
	}
});
 
// we would instantiate this view with our collection
this.listContainerView = new wedapp.view.TasksContainer({
	collection:myTasks
});
// print our template
$("#contentContainer").prepend(this.listContainerView.render().el);

Since we passed our collection at the view instantiation we can use it like this this.collection.etc.

Now we need our item view.

myapp.view.TasksItem = Backbone.View.extend({
	events: {},
	render: function(data) {
		$(this.el).html(this.template(this.model.toJSON()));
		return this;
	},
	initialize : function(){
		this.template = _.template($("#task_item_tpl").html());
	}
});

Nothing special here.

That’s it!

Of course there is a little bit more to it, I added my demo on github with the complete code, it include a more real life example using the backbone router and underscore templates. If you had problem filtering lists before you can use this as a base reference.

Cedric Dugas is a front-end veteran with more than 9 years under the belt, between his open source projects & entrepreneurial ambitions he is a product architect at CakeMail. Why don't you follow him on twitter.

16 Comments on "Filtering a collection in backbone.js"

  1. Good job! I didn't know about the _(). Thanks for that.

  2. Sebastian says:

    I'm working on an app using this filter approach and clearing the views with $("#taskList").html(""); doesn't seem to remove the view objects from memory. Have you tried adding, say, 10000 views and keept an eye on the memory usage? It doesn't go down when setting html("").

  3. bbuser says:

    ced, have you solved this memory leak? I'm doing a similar approach and also seeing memory leakage.

  4. Jens Alm says:

    I had a similar leak in a similarly constructed CollectionView with ItemViews. I solved it by overloading the remove() method to unload all events bound to models (if I bind to a model event in intialize in a View Class, I need to unbind in remove()). In my CollectionView.remove() I then cascade the remove() call down to the ListItems.

  5. This was super handy!

    I was doing it myself, going in circles, found this, now I’m good.

    Yay!

    Rich, Gun.io

  6. Samuel Goldenbaum says:

    Should we not rather publish an event in the collection so that the view could bind to that event in multiple views. Imagine several views needed to update when the collection was filtered. Could the reset() function rather be used?

  7. Perumal says:

    Hi,
    For me using undersocre caused problem in backbone v0.9.2 hence i removed the same and it started working. Thanks for the code snippet.
    return _(this.filter(function(data) {
    return pattern.test(data.get(“name”));

  8. You need to be aware that this returns an array of models, and not a backbone collection. This means the array will not have methods like ‘get’, so do something like this if your view expects a collection:

    search : function(letters){
    if(letters == “”) return this;

    var pattern = new RegExp(letters,”gi”);
    var tasks = _(this.filter(function(data) {
    return pattern.test(data.get(“name”));
    }));

    return new myapp.collection.Tasks(tasks);
    }

  9. Motin says:

    Thanks all! Ended up with a hybrid appraoch. Instead of:

    var view = new SomeView({
    collection: somecollection,
    });

    …we wanted to use:

    var view = new SomeView({
    collection: somecollection.whereType(‘active’),
    });

    Solution:

    define([
    'jquery',
    'underscore',
    'backbone',
    'models/some'
    ], function($, _, Backbone, SomeModel) {
    var c = Backbone.Collection.extend({
    model: SomeModel,
    url: endpoint(‘some’),
    whereType: function(type) {
    var items = this.filter(function(data) {
    return data.get(“type”) === type;
    });
    return new c(items);
    },
    });
    return c;
    });

  10. Andrew Veldran says:

    The above searches against a single model attribute and I needed to search against all the models attributes and came up with the below, hope this helps if anyone has the same requirement.

    return _(this.filter(function(data) {
    var str = _.flatten(data.attributes);

    str = String(str.join());
    return pattern.test(str);
    }));

  11. Sviatoslav says:

    Very useful, thank you! I’ve been looking for a way to search through collection of data, without extra complexity and underscore’s filter method seems to be the most optimal solution for that.

  12. Be careful! Wrapping with _() does *not* make the expression return a Backbone collection:

    c = new Backbone.Collection;
    c instanceof Backbone.Collection; // => true
    c2 = _(c.filter(function(){ return true; }))
    c2 instanceof Backbone.Collection; // => false

  13. Nick says:

    Hey,
    Thanks for the code. But have you noticed that it displays a maximum of 2 items. e.g. if you search for “the” in your demo, it should have displayed 3 items, i.e. “Get out the trash”, “Do the laundry”, “Vacuuming the carpet”.
    I am facing the same problem. Any thoughts on this?

    Thanks once again.

  14. eguneys says:

    Hey,
    Thanks for the code. But you should filter the elements on server side, writing database queries. That’s more efficient.
    Best regards.

Got something to say? Go for it!