Question

I'm trying to render a filtered collection from a single view using a select box using the reset property. First issue is that the select box and the rendered content must all appear in the same div in order to work. Is this normal? Second issue, is that instead showing the filtered results only, it appends the filtered collection to the end of the rendered content instead of replacing it.I know append in my ._each function won't work because it will only append the filtered items to the end of the entire collection. Here's my code sample below:

(function($) {

  var images = [
       { tags: "Fun", date : "April 3, 2012", location : 'Home', caption : 'Having fun with my lady'},
       { tags: "Chillin", date : "April 4, 2012", location : 'Home', caption : 'At the park with my lady'},
       { tags: "Professional", date : "April 5, 2012", location : 'Home', caption : 'At the crib with my lady'},
       { tags: "Education", date : "April 6, 2012", location : 'Home', caption : 'Having fun with my baby'},
       { tags: "Home", date : "April 3, 2012", location : 'Home', caption : 'Having fun with my lady'},
       { tags: "Professional", date : "April 4, 2012", location : 'Home', caption : 'At the park with my lady'},
       { tags: "Fun", date : "April 5, 2012", location : 'Home', caption : 'At the crib with my lady'},
       { tags: "Chillin", date : "April 6, 2012", location : 'Home', caption : 'Having fun with my baby'},
       { tags: "Fun", date : "April 3, 2012", location : 'Home', caption : 'Having fun with my lady'},
       { tags: "Education", date : "April 4, 2012", location : 'Home', caption : 'At the park with my lady'},
       { tags: "Personal", date : "April 5, 2012", location : 'Home', caption : 'At the crib with my lady'},
       { tags: "Personal", date : "April 6, 2012", location : 'Home', caption : 'Having a play date'}
    ];


var Item = Backbone.Model.extend({
    defaults : {
        photo : 'http://placehold.it/200x250'
    }
});

var Album = Backbone.Collection.extend({
    model : Item
});

var ItemView = Backbone.View.extend({
el : $('.content'),

   initialize : function() {
        this.collection = new Album(images);
        this.render();
        $('#filter').append(this.createSelect());
        this.on("change:filterType", this.filterByType);
        this.collection.on('reset', this.render, this);
    },
    template : $('#img_container').text(),
    render : function() {
        var tmpl = _.template(this.template);
        _.each(this.collection.models, function (item) {
           this.$el.append(tmpl(item.toJSON()));                   
        },this);
},

events: {
    "change #filter select" : "setFilter"
},

 getTypes: function () {
    return _.uniq(this.collection.pluck("tags"), false, function (tags) {
        return tags.toLowerCase();
        });
},

createSelect: function () {
    var filter = this.$el.find("#filter"),
        select = $("<select/>", {
            html: "<option>All</option>"
        });

    _.each(this.getTypes(), function (item) {
        var option = $("<option/>", {
            value: item.toLowerCase(),
            text: item.toLowerCase()
        }).appendTo(select);
    });
    return select;
},

setFilter: function (e) {
   this.filterType = e.currentTarget.value;
   this.trigger("change:filterType");
},

filterByType: function () {
    if (this.filterType === "all") {
        this.collection.reset(images);
    } else {
        this.collection.reset(images, { silent: true });
        var filterType = this.filterType,
            filtered = _.filter(this.collection.models, function (item) {
            return item.get("tags").toLowerCase() === filterType;   
        });   
       this.collection.reset(filtered);
    }
}

}); 

var i = new ItemView();


})(jQuery);
Was it helpful?

Solution

Without seeing your markup it's a little hard to say for certain, but I suspect that the reason your select needs to be in the same div is because when you bind events in backbone using the event hash it uses it's el as the root element for the event delegation.

As for why your collection is only getting appended instead of replacing it, I don't see you emptying the current contents anywhere, you should have in the beginning of your render method something along the lines of this.$el('.itemCnt').empty().

A separate point, you might consider having view for each of your items and an object to cache them then every time the filter changes instead of resetting your collection you can detach all of the items and reattach the filtered elements without regenerating their markup.

For example, something along the lines of

var ItemView = Backbone.View.extend({
   el : $('.content'),
   _views: {},
   //...

   add: function(item) {
     var view = new ItemView({model: item});
     this._views[item.cid] = view;
   }

   render: function (filteredItems) {
      this.$el.find('.ItemCnt .item').detach();

    var frag = document.createDocumentFragment();

      _.each(filteredItems, function(item) {
        frag.appendChild(this._views[item.cid].el);
     }
       this.$el.find('.ItemCnt').append(frag);
   }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top