Stephanie Gredell
Stephanie Gredell

Reputation: 168

How to use the original Backbone collection after filtering?

I'm relatively new to Backbone and though I know the general idea of how to use it, my learning has been rapid and I'm probably missing some key elements.

So I have a collection that contains an attribute called "type" which can be article, book, video, class. I have the view rendering and everything but I need to be able to filter the collection when links are clicked.

My question is - how can I get it to filter down the collection and still be able to refilter the original collection when I click on another type?

Here's the gist of my code, I simplified it for easy reading:

var TagsView = Backbone.View.extend({
    initialize: function(query) {
        this.collection = new TagsCollection([], {query: self.apiQuery} );
        this.collection.on('sync', function() {
            self.render();
        });
        this.collection.on('reset', this.render, this);
    },
    render: function() {
        //renders the template just fine
    },
    filter: function() { 
        //filtered does work correctly the first time I click on it but not the second.
        var filtered = this.collection.where({'type':filter});
        this.collection.reset(filtered);
    }
});

update: I managed to get this working. I ended up triggering a filter event.

var TagsCollection = Backbone.Collection.extend({
    initialize: function(model, options) {
        this.query = options.query;
        this.fetch();
    },
    url: function() {
        return '/api/assets?tag=' + this.query;
    },
    filterBy: function(filter) {
        filtered = this.filter(function(asset) {
            return asset.get('type') == filter;
        });
        this.trigger('filter');
        return new TagsCollection(filtered, {query: this.query});
    },
    model: AssetModel
});

And then in my view, I added some stuff to render my new collection.

var TagsView = Backbone.View.extend({
    initialize: function(query) {
        this.collection = new TagsCollection([], {query: self.apiQuery} );
        this.collection.on('sync', function() {
            self.render();
        });
        this.collection.on('filter sync', this.filterTemplate, this);
        this.collection.on('reset', this.render, this);
    },
    render: function() {
        //renders the template just fine
    },
    filterCollection: function(target) {
        var filter = $(target).text().toLowerCase().slice(0,-1);
        if (filter != 'al') {
            var filtered = this.collection.filterBy(filter);
        } else {
            this.render();
        }

    },
    filterTemplate: function() {
        filterResults = new TagsCollection(filtered, {query: self.apiQuery});
        console.log(filterResults);
        $('.asset').remove();
        filterResults.each(function(asset,index) {
            dust.render('dust/academy-card', asset.toJSON(),     function(error,output) {
                self.$el.append(output);
            });
        });
    },  
});

Upvotes: 1

Views: 2544

Answers (3)

Stephanie Gredell
Stephanie Gredell

Reputation: 168

Here's a better answer to this. Instead of making it so complicated, you can just use the where method. Here's my replacement solution for the question above.

filterby: function(type) {
      return type === 'all' ? this : new BaseCollection(this.where({type: type});
});

Upvotes: 1

Simon
Simon

Reputation: 1754

The reason it's not working a second time is because you're deleting the models that don't match your filter when you call reset. That's normal behaviour for the reset function.

Instead of rendering with the view's main collection, try using a second collection just for rendering which represents the filtered data of the original base collection. So your view MIGHT look something like:

var TagsView = Backbone.View.extend({

    filter: null,

    events: {
        'click .filter-button': 'filter'
    },

    initialize: function (query) {
        this.baseCollection = new TagsCollection([], {query: self.apiQuery} );
        this.baseCollection.on('reset sync', this.filterCollection, this);
        this.collection = new Backbone.Collection;
        this.collection.on('reset', this.render, this);
    },

    render: function () {
        var self = this,
            data = this.collection.toJSON();

        // This renders all models in the one template
        dust.render('some-template', data, function (error, output) {
            self.$el.append(output);
        }); 
    },

    filter: function (e) { 
        // Grab filter from data attribute or however else you prefer
        this.filter = $(e.currentTarget).attr('data-filter');
        this.filterCollection();
    },

    filterCollection: function () { 
        var filtered;

        if (this.filter) {
            filtered = this.baseCollection.where({'type': this.filter});
        } else {
            filtered = this.baseCollection.models;
        }

        this.collection.reset(filtered);
    }

}); 

To remove any filters, set a button with class filter-button to have an empty data-filter attribute. collection will then be reset with all of baseCollection's models

Upvotes: 2

n0minal
n0minal

Reputation: 3223

You can try using comparator function of your Collection.

http://backbonejs.org/#Collection-comparator

Basically its is like sorting your collection.

Upvotes: 0

Related Questions