Backbone.js – Todo Collection – What exactly is happening in this return statement?

I’m looking back at the Backbone todo list and have a question about the collection.

Here is the code:

window.TodoList = Bacbone.Collection.extend({

        model: Todo,

        localStorage: new Store("todos"),

        done: function() {
            return this.filter(function(todo){return todo.get("done")})
        },

        remaining: function() {
            return this.without.apply(this, this.done());
        }



    })

I understand everything that is going on here, except for the ‘remaining’ function.

The return statement: return this.without.apply(this, this.done()); is using a proxy to an underscore method – _.without

According to Underscore docs, here is what that is for:

without_.without(array, [*values]) Returns a copy of the array with
all instances of the values removed. === is used for the equality
test.

_.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
=> [2, 3, 4]

So, I get that it is saying to return everything in the collection without a ‘done’ attribute with the value of ‘true’.

What I don’t understand is the ‘apply’ function that is being chained to it. That doesn’t appear in the Backbone docs or the Underscore docs. At least not anywhere I can find it.

Can anyone explain in detail what is going on with those elements in the Return statement?

this is referring to the collection.

apply is a method of javascript functions that allows you to set context of a method and send an array of values to the caller.

apply expects context as the first parameter then an array or array-like (such as arguments) which will be passed in as parameters the function.

You can do the same thing with .call except the 2nd+ params are comma separated.

Read More:   Angular2: Show placeholder image if img src is not valid

apply and call are native to javascript.

So…

return this.without.apply(this, this.done());

the method this.done() returns an array, but uses the context of the collection and passes in a series of values to be ignored via the without method. Which in turn returns all todos that aren’t done within the collection.

Example:

_.without([1,2,3,4],1,2); === _.without.call([], [1,2,3,4], 1, 2);

My understanding is that, in this case, the use of apply is redundant, remaining could be shortened as follows:

remaining: function() {
  return this.without(this.done());
}

As I understand it, the only reason to use apply is if you want (or need) to change the contextual item that without will operate on. In this case, we have no need to do that.

If I’m wrong, I’d really (really!) like to have an explanation of why apply is necessary here.

apply invokes a function and binds this in the context of that function to the first argument passed (in this case, the Collection instance TodoList). The second argument is an array of arguments to be passed to without.

By the way, apply isn’t a Backbone thing — it’s native to JavaScript.

The reason for this

this.without.apply(this, this.done())

is that “_.without” does not accept as argument the array of items to be excluded as a single array ([]) argument

See here _.without to accept array as second argument

apply, which is part of the JS Function.prototype, here is a workaround to inject the excluding items in a single array argument

Read More:   Best way to implement logout in Firebase v3.0.1+? Firebase.unauth is removed after update

the use of apply in this case is redundant because backbone collections is doing the job correctly cf. http://backbonejs.org/docs/backbone.html#section-122

we can use underscore like this: _.without.apply(this, this.done()) or backbone binding like this: this.without(this.done) by using backbone bind.

Please take a look at underscore doc :
like this :

without_.without(array, [*values])
Returns a copy of the array with all instances of the values removed.

_.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
=> [2, 3, 4]

I changed the function to this and got the exact same result on the TODO list application:

remaining: function () {
        return this.filter(function (todo) {
            return !todo.get('done');
        });
}

I still didn’t understand how apply became a method of without, I knew apply was a Javascript function, but then I read the documentation for apply and understood that in this case without was being used in an object-oriented way (see http://underscorejs.org/#chain).

In fact, apply could be passed null instead of this as the context and it wouldn’t change the result because it’s not being used at all:

return this.without.apply(null, this.done());

Notice the first this is the actual collection of models and without, via the second argument in apply, which is the array of done (completed) tasks, is producing the array of pending todo tasks.

By the way, the latest version of the TODO application renames the function done to completed.

Here this.without() delegates to _.without() function. _.without() needs an array and elements as parameters not as an array. By using apply() , apply calls _.without() in the correct manner.

var obj = {
    f1: function(a,b,c){}
};

Now obj.f1(1,2,3) == obj.f1.apply(obj, [1,2,3]).

With this infomation, this.without(this.complete()) passes an array to the without method. But without method needs individual elements to be passed as arguments. That can be done using Function.apply().

Read More:   How do you detect when HTML5 audio has finished playing (more than once)? [duplicate]

The without function needs a list of elements to remove from this.
this.completed() returns an array, therefore it is not what without function is expecting.

apply is a native JavaScript function, which calls a function setting the this context as the first argument and an array as the second argument. The argument is passed to the original function as its list arguments.

In this case apply passes the arguments to without in this.completed(), meeting the expectation of without.

In conclusion, in this case, apply is necessary.

Sorry, I’m a total newb @ this stuff, but couldn’t fn.remaining (also) be declared as:

return this.filter(function(todo){return !todo.get(“done”)})

Stating this as a request for clarification, rather than an alternative declaration 🙂

(edit: couldn’t bold the ‘!’ before ‘todo.get…’)


The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 .

Similar Posts