There are often times when I want to filter Backbone collections and prevent some of the collection items from being displayed in my view. For instance, say I’m looking at a page of houses for sale. When I set a limit on the maximum price of a house either by entering a number or adjusting some type of slider, all the expensive houses should disappear from the page. Implementing this can be tricky, and I’d like to show you an especially nice solution using the decorator pattern.
What’s the Decorator Pattern, again?
The decorator pattern offers a way to “decorate” individual objects with new behaviors. (There, now do you understand?) It was difficult idea for me to get. I think the most helpful place to look first is the interface this pattern provides for creating objects.
Let’s say your program needs to process orders for tacos, and let’s say customers can order beef or chicken tacos. The usual way to solve a problem like this in Java is through inheritance:
public class BeefTaco extends Taco {
// process and apply beef
}
Taco taco = new BeefTaco();
Now, let’s say customers can also add sour cream, guacamole, or salsa to their tacos. That would mean we’d have to be able to do this:
Taco johnsTaco = new GuacamoleBeefTaco();
Taco billsTaco = new SourCreamSalsaChickenTaco();
But this would require a complicated, nonsensical class hierarchy. We’d have to have a GuacamoleBeefTaco
class extending BeefTaco
, a SourCreamSalsaChickenTaco
class extending SalsaChickenTaco
, and many others.
This is the problem that the decorator pattern solves. Instead of the above, where we try to create a class for every single combination of taco toppings, we design each class to be created from another (partially assembled) taco. The above example would become this:
Taco johnsTaco = new GuacamoleTaco(new BeefTaco(new PlainTaco()));
Taco billsTaco = new SourCreamTaco(new SalsaTaco(new ChickenTaco(new PlainTaco())));
See Decorator Patterns in Wikipedia for an example implementation in Java.
Back to Backbone
If we go back to our example of wanting to filter our list of houses by price, we can create a filteredCollection
decorator function that takes a regular Backbone collection and returns a new collection based on the filter function you pass in:
var cheapHouseFn, houses, matchingHouses;
houses = new HouseCollection;
houses.fetch();
cheapHouseFn = function(house) {
return house.get('price') < 100000;
};
matchingHouses = filteredCollection(houses, cheapHouseFn);
And that’s it. Just pass the new collection into your view without having to worry about hiding elements that shouldn’t be there. Then when the user changes the filters, just create another filtered collection from the original, and re-render.
Filtering Backbone Collections
How can this be done? How can we create a Javascript function that takes a Backbone collection and returns a new, filtered collection? Thanks to Derick Bailey for this solution. Here's the code:
filteredCollection = function(original, filterFn) {
var filtered;
// Instantiate new collection
filtered = new original.constructor();
// Remove events associated with original
filtered._callbacks = {};
filtered.filterItems = function(filter) {
var items;
items = original.filter(filter);
filtered._currentFilter = filterFn;
return filtered.reset(items);
};
// Refilter when original collection is modified
original.on('reset change destroy', function() {
return filtered.filterItems(filtered._currentFilter);
});
return filtered.filterItems(filterFn);
};
It might look complicated at first. Here's how it works:
- A new instance of the original collection type is created on line 6.
- Any events that the collection type usually listens to are discarded on line 9. Instead, on lines 20-22, the new filtered collection listens for changes in the original collection and re-filters itself when those changes happen.
- The rest of the code is the
filterItems
method that does the actual filtering using Underscore'sfilter
and Backbone'sreset
. - The filter function itself is also saved in
_currentFilter
so the collection can be re-filtered later when a modification event has occurred. - At the very end (line 23), the new collection is filtered by calling
filterItems
and then returned.
The great part is this can be run on any Backbone collection. You can even create filtered collections from other filtered collections. The decorator pattern can also used to create paginated collections, making client-side pagination simple.