Backbone.js - Correct way of filtering and displaying collection data in a view

32,753

Solution 1

I think you need to use another collection. For example, in your inbox, do this:

inbox: function(){
    currentCollection = new TasksCollection(tasks.inbox());
}

I haven't tested this but when you do a .reset(); you are removing all your models and loading the ones passed in.

Solution 2

@sled there's typos in the code you posted, see comments inline. Did you post this as a project somewhere?

// add models
add: function(models, options) {
  // TYPO: next line was missing, so single models not handled.
  models = _.isArray(models) ? models.slice() : [models];

  var self = this;

  models = _.filter(models, this.filter);

  // return if no models exist
  // TYPO: returned undefined, so was not chainable
  if(models.length == 0) { return this; }

  // actually add the models to the superset
  this.superset.add(models, options);
  return this;
},

// remove models
remove: function(models, options) {
  // TYPO: next line was missing, so single models not handled.
  models = _.isArray(models) ? models.slice() : [models];

  // remove model from superset
  this.superset.remove(_.filter(_.filter(models, function(cm) {
    // TYPO: not 'm != null', causes error to be thrown
    return cm != null;
  }), this.filter), options);
  // TYPO: missing return so not chainable
  return this;
},

Solution 3

one quick amendment to you solution, you are using

$(this.el).html('');

My understanding is your the views and related event bindings will still exist in the browser memory, so you ideally need to use view.remove() on the TaskView to correctly clear the event bindings as well as the html.


This is a slightly different take on the answer as I have been looking for a solution to a similar problem, hope this may be of help to others.

My problem: - to filter a complete collection by attributes of the model. eg. a user clicks the models view, gets a list of (some of) the attributes, selecting an attribute filters the collection to only show ones with the same value.

The route I am taking is by calling a method on the collection from the view, in my case the view is specific to a model so:

this.model.collection.myFilter(attr,val);

where attr is an attribute of the model associated with the collection, then in the filter something like

myFilter: function(attr, val){
    var groupByAttr = this.groupBy(function(article){
        var res = (val === undefined)? true : (article.get(attr) == val);
        article.set({selected:res});
        return res;
    });
    return groupByAttr;
}

I have used ._groupBy as this returns 2 arrays (positive / negative) that may be of use. By setting the mode attribute "selected", and binding to this in the model view I can easily toggle a class which shows or hides the view.

if(val === undefined) is added as a simple way of clearing a filter by calling the same method without a value.

Share:
32,753
Juraj Ivan
Author by

Juraj Ivan

Updated on February 01, 2020

Comments

  • Juraj Ivan
    Juraj Ivan about 4 years

    I have got a huge list of tasks loaded on the start.
    I want to show them depending on selected list / inbox, so that there won't be additional loadings for each list.

    window.Task = Backbone.Model.extend({});
    
    window.TasksCollection = Backbone.Collection.extend({
        model: Task,
        url: '/api/tasks',
        inbox: function() {
            return this.filter(function(task) {
                return task.get('list') == null;
            });
        },
        list: function(id) {
            return this.filter(function(task) {
                return task.get('list') == id;
            });
        }
    });
    
    window.tasks = new TasksCollection;
    
    window.TaskView = Backbone.View.extend({
        tagName: 'li',
        template: _.template($('#item-template').html()),
        initialize: function() {
            _.bindAll(this, 'render', 'close');
            this.model.bind('change', this.render);
            this.model.view = this;
        },
        render: function() {
            $(this.el).html(this.template(this.model.toJSON()));
            this.setContent();
            return this;
        },
    });
    
    window.TasksView = Backbone.View.extend({
        el: '#todo-list',
        collection: tasks,
        initialize: function() {
            _.bindAll(this, 'render');
            this.collection.bind('reset', this.render);
            this.collection.fetch();
        },
        render: function() {
            var t = this;
            $(t.el).html('');
            this.collection.each(function(task) {
                var view = new TaskView({ model:task });
                $(t.el).append( view.render().el );
            });
            return this;
        },
    });
    
    window.Nicetask = Backbone.Router.extend({
        routes: {
            '':             'inbox',
            '/inbox':       'inbox',
            '/list/:id':    'list',
        },
        initialize: function() {
            _.bindAll(this, 'inbox', 'list');
            window.tasksView = new TasksView;
        },
        inbox: function() {
            tasks.reset( tasks.inbox() );
        },
        list: function(id) {
            tasks.reset( tasks.list(id) );
        }
    });
    

    This code works, but the reset() function removes other tasks in actual list from tasks collection. And on another route, tasks collection is empty.

    Is there any reasonable way to achieve this? thanks for any idea.

    ps: backbone novice


    UPDATE

    Thx to @sled and @ibjhb for comments, here is snippet of working solution.

    window.TasksView = Backbone.View.extend({
        el: '#todo-list',
        collection: Backbone.Collection.extend(),
        initialize: function() {
            _.bindAll(this, 'render', 'addOne', 'addAll');
            this.collection.bind('add', this.addOne);
            this.collection.bind('reset', this.render);
        },
        render: function(data) {
            $(this.el).html('');
            _.each(data, function(task) {
                this.addOne(task);
            }, this);
            return this;
        },
        addOne: function(task) {
            var view = new TaskView({ model:task });
            $(this.el).append( view.render().el );
        },
    });
    
    window.Nicetask = Backbone.Router.extend({
        routes: {
            '':             'inbox',
            '/inbox':       'inbox',
            '/today':       'today',
            '/list/:id':    'list',
        },
        initialize: function() {
            _.bindAll(this, 'inbox', 'today');
            window.tasksView = new TasksView;
            window.menuView = new MenuListView;
            tasks.fetch();
        },
        inbox: function() {
            tasksView.render( tasks.inbox() );
        },
        today: function() {
            tasksView.render( tasks.today() );
        },
        list: function(id) {
            tasksView.render( tasks.list(id) );
        }
    });