Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mix in each Underscore method as a proxy to Collection#models

I am using the backbone library to perform the following:

var Persons = Backbone.Collection.extend({
    defaults: {
        name: 'unknown',
        age: 18
    },

    over_18: function () {
        return this.filter(function (model) {
            return model.get('age') > 18
        });
    },

    under_18: function () {

        var persons_over_18 = this.over_18;

        return this.without(this, persons_over_18); // it does not work!! why?
    }
});

persons = new Persons([{age: 17}, {age: 27}, {age:31} ]);

persons.under_18().length; // 3 instead of 1

As you can see the method under_18 is not working properly because it returns all the models instead of giving me just the models which age attribute is under 18.

So in order to debug my code, I decided to see the the Backbone.js Annotated Source,in particular the following code:

var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl', ... ]; // and more

_.each(methods, function(method) {
    Collection.prototype[method] = function() {
      var args = slice.call(arguments);
      args.unshift(this.models);
      return _[method].apply(_, args);
    };
});

But the above piece of code is not clear to me and I still cannot make the first one work as I wish.

So my question is how can I fix the first code in relation to the second one?

Here is my code into jsfiddle.net http://jsfiddle.net/tVmTM/176/

like image 851
Maria Avatar asked Oct 20 '22 16:10

Maria


1 Answers

Lenghty answer to better understand the Backbone code:

In javascript a "member" for an object can be referenced in two ways:

  1. the usual . notation: foo.say('hello');
  2. the [...] notation, with the string as an argument... a bit like an associative array: foo["say"]('hello')

What happens in backbone is that each string in the methods array, defined just above this method, is iterated and added to the Collection prototype, so added to all classes that inherit (or extend) Collection.

In the function, the arguments keyword simply references all the arguments passed into the function, even if the signature is empty:

Collection.prototype[method] = function() { // <- empty signature here!

The slice with no arguments will transform the passed arguments into an array. Notice the use of slice.call(...) here (and refer to this SO question).

var args = slice.call(arguments);

unshift then adds the Collection models array to the beginning of the array.

args.unshift(this.models);

Then we are actually calling the Underscore method (using the [...] notation) on the new array of arguments. Using apply, we pass the _ as the first argument which will become the this scope (more info here)

return _[method].apply(_, args);

This allows you to do stuff like:

MyCollection.each(function (model) { ... });

instead of

_.each(MyCollection.models, function (model) { ... });

The resulting effect is identical! The former will just call the latter. :)

To answer your question, the problem in your case is that the _.without method does not accept two arrays but an array followed by a list of arguments; the method you are looking for is called difference (look at this SO question), but it is not mapped into the Collection, so you either map it yourself (replicating the code found in the Backbone source) or just use it directly:

return _.difference(this.models, this.over_18());

Working fiddle: http://jsfiddle.net/tVmTM/177/

In my opinion, better just keep using filter as you did for the over_18 method... Even better, put the over_18 and under_18 as methods in the Model (where they belong) and from the collection just use those.

like image 154
Tallmaris Avatar answered Oct 23 '22 08:10

Tallmaris