Filtering a collection in backbone.js

by admin on February 2, 2011

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.