2

I have a Backbone model (let's call it Foo) that includes a collection of n sub-models (let's call them Bar), and in one particular view, I only want to display m of those sub-models, along with a message along the lines of "(n-m) remaining".

Right now, what I've got is something like this:

var FooView = Backbone.View.extend({
    ...
    render: function() {
        this._barViews = [];
        var bars = this.model.get("bars");

        var that = this;
        _.each(bars.first(maxToShow), function(bar) {
            that._barViews.push(new BarView({model:bar}));
        }

        var remaining = bars.length - maxToShow;
        this.model.set("remaining", remaining > 0 ? remaining : undefined;

        var json = this.model.toJSON();
        $(this.el).html(this.template(json));

        _(this._holdViews).each(function(hv) {
            holdList.append($(hv.render().el));
        });
    }
});

This works, but it feels hacky, because I'm injecting "remainingMessage" into the model even though that's specific to this particular view. (Another view might show all the bars, or none of them, and might or might not have a remaining message.) I'm also not totally excited about the nested views, since they mean creating an extra template file and having to remember to include it (FWIW, I'm using Handlebars.js for the templates, with server-side compilation).

Is there a better way to (1) filter the bars collection down to maxShown items, and (2) generate / include the number remaining in the view?

David Moles
  • 48,006
  • 27
  • 136
  • 235

1 Answers1

4

You want a "view model" - a model that is there specifically to handle the concerns about the specific view that will use it. And fortunately, this is dirt simple in JavaScript.

Using Object.create you can get a new object instance that inherits from the original object you pass in as a parameter. This gives us the ability to "decorate" the original model with new code, without actually changing the original model.

In your case, we want to decorate the "foo" model with the remaining info. We only need that info in the toJSON results, though, so we'll only add it to that method.



function buildFooViewModel(model){
  var foovm = Object.create(model);

  foovm.toJSON = function(){
    // call the original model's toJSON
    var args = Array.prototype.slice.apply(arguments);
    var json = model.toJSON.apply(this, args);

    // add the needed "remaining" data using your calculations, here
    json.remaining = bars.length - maxToShow;

    // send the json data back
    return json;
  }

}

var FooView = Backbone.View.extend({

  initialize: function(){
    // use the view model instead of the original
    this.model = buildFooViewModel(this.model);
  },

  render: function(){
    // your normal render stuff here... calling this.model.toJSON
    // will return your JSON data with the `remaining` field in it already
  }

});

I do this quite often with my views that need calculations like this. You can see it happening all over http://ravenhq.com for example, in the database management screen, for % used / remaining, and other values like that.

Derick Bailey
  • 72,004
  • 22
  • 206
  • 219