Warning: I'm a backbone newbie.
I'm iterating over all the models in my collection and rendering them. Simple enough, however, I wanted to make sure I understand well how this works. Here's what I have -
Model:
File = Backbone.Model.extend({});
Collection
Folder = Backbone.Collection.extend({ model: File });
ModelView
FileView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'render');
this.render();
},
render: function() {
this.template = _.template(document.getElementById("fileTemplate").innerHTML);
this.$el.html(this.template({ fileName: this.model.get("FileName") }));
}
})
CollectionView
FolderView = Backbone.View.extend({
initialize: function () {
_.bindAll(this, 'render');
this.render();
},
render: function () {
_(this.collection.models).each(function(file) {
var fileView = new FileView({ model: file});
this.$el.append(fileView.el);
},this); <--???
}
});
This works perfectly fine. My question is about the _.each in my FolderView. Why do I have to pass the this back to my each loop?
If I don't pass this, it refers to my window object instead of my collection. I know it's necessary to pass it, i just don't know why.
_.each(list, iterator, [context]) Alias: forEach
Iterates over a list of elements, yielding each in turn to an iterator function. The iterator is bound to the context object, if one is passed. Each invocation of iterator is called with three arguments: (element, index, list). If list is a JavaScript object, iterator's arguments will be (value, key, list). Delegates to the native forEach function if it exists.
underscore docs#each
The default context is the window object. By setting this to be the context, you are making this in the function map to this where the function was called.
See the following for reference on this topic:
A bit about function contexts
underscore source for _.each
ForEach docs
Strict vs non-strict context
To explain you need to first realize that in JavaScript only a function creates a new scope (that's right a loop doesn't actual create a new scope) and that unlike some other languages the context of that scope is mutable, meaning depending on how it's called the this within that scope might refer to different things.
As a result of this a common problem that arises is that you might have a inner and outer function and within the inner function you want to refer to the scope of the outer function but the inner function has changed the scope so that this no longer refers to the outer function.
In order to handle this we need to make sure that we save the context of the outer function (have a look at the following for a more detailed explanation).
A common pattern you might see in JavaScript is assigning the context (this) to a variable and then using that within the function.
For example your render function could technically have been rewritten as the following
render: function () {
var self = this;
_(this.collection.models).each(function(file) {
var fileView = new FileView({ model: file});
self.$el.append(fileView.el);
});
}
Now that we provided a basic understanding of the this and context we can turn to the _.each function from Underscore.js, _.each like most of the functions in underscore.js take an optional third parameter which refers to the context which underscore.js then uses so that you can conveniently refer to that context.
Underscore.js also provides a utility function bind to bind a context to a function.
The original question was, "Why do I have to override "this" in the each(fn, this) call?"
You are creating an anonymous function for the first parameter. By default, "this" inside a function refers to the object holding the reference to the function through which the call was made. Your anonymous function is created against the root context object, which is window in a browser. That's what you are observing when you don't supply an object for the second parameter.
each() has the ability to determine if you passed a different object to serve as "this" within the function. each() can then use Object.bind() or Object.call(), both of which use the first parameter passed to them to override "this".
Passing "this" as the second parameter to each() causes the outer function's context object to be used as "this" in the inner function.
Related
I'm writing an directive which need to retrieve a scope of current DOM element. using the non public api angular.element().scope();
It works well until angular 1.3 introduces a new feature $compileProvider.debugInfoEnabled(false); which mainly aims to improve performance to avoid bind data in DOM element. But when debugInfoEnabled() is set to false, angular.element().scope() will return undefined. So I must find another way to get the scope of an DOM element or I have to redesign my code logic.
Is there a way to make this possible?
I just faced a similar problem in our application after compiling our app with $compileProvider.debugInfoEnabled(false);. I needed to later access some of our directive's isolate scope but couldn't use the isolateScope() method. To get around the problem, I created a helper function in a Utils service that looks like this:
this.setElementIsolateScope = function(element, scope) {
element[0].isolateScope = function() {
return scope;
};
};
Then inside any directive where I needed to be able to later access the isolate scope I called this function inside the link() function: Since element is a jqLite object, you need to set the isolateScope() function on element[0]. You should already have the jqLite wrapped element and scope already passed into your link function, which you then just pass to your service method.
Utils.setElementIsolateScope(element, scope);
To then access the isolate scope later, you would get a reference to your element and then do this (assuming child_element is the reference to your element/directive):
var child_iso_scope = _.isFunction(child_element.isolateScope) && child_element.isolateScope();
Depending on how you are getting the reference to your element, you may need to wrap it a jqLite wrapper like this:
child_element = angular.element(child_element);
And then just use the same way as above to get the isolate scope. Hope this helps!
I've written a pretty simple directive that adds/removes a css class on an element when the element is clicked.
app.directive('dropdown', function() {
var open = false, element,
callback = function(){
open = !open;
if (open) {
element.addClass('open');
} else {
element.removeClass('open');
}
};
return {
scope: {},
link: function(scope, elem){
element = elem;
elem.bind('click', callback);
scope.$on('$destroy', function(){
elem.unbind('click', callback);
elem.remove();
});
}
};
});
I think that the $destroy method is probably unnecessary. Since I've used the built in jqlite the listener will be destroyed along with the element right? Also is there any benefit to calling elem.remove(). I've seen it in some examples but not sure if I see the need.
Any thoughts appreciated
C
You don't have to remove the element manually for sure. You also don't need to unbind anything from scope because it will be handled by angularjs itsef.
For jquery dom listeners:
In case you are referencing JQuery then angular will use it instead of his internal jqLite implementation. It means that the native jquery remove method will be used for the element removal. And the jquery documentation for remove says:
Similar to .empty(), the .remove() method takes elements out of the
DOM. Use .remove() when you want to remove the element itself, as well
as everything inside it. In addition to the elements themselves, all
bound events and jQuery data associated with the elements are removed.
So i think that you don't need to unbind your listeners.
But I'm not 100% sure about this:)
In your case, you should be fine since the event is bound to the element that gets removed and thus the handler gets destroyed along with the element itself. Now, if your directive binds an event to a parent outside of its own DOM element, then that would need to be removed manually on the $destroy.
However, closure can cause any object to stay alive so that's something you do need to worry about. You could introduce a new function still referencing variable objects in the functions whose scope you are trying to destroy and that prevents GC from doing what you likely want it to. Again, that won't affect your current example, but it's something to always consider.
I have a function being used in my service that is defined as:
var getData = function() {
return anotherService.getData().$promise;
};
and a this property that I manipulate throughout the service.
this.someProperty = 'a string';
I call the above function inside the return section of my service:
return{
updateProperty: function(){
getData().then(function(data){
this.someProperty = data;
});
}
}
In the above, I get an this is undefined related error in my browser console. I assume this is because the resolved $promise is an AJAX call and this is used out of context. What's the best way to manipulate a this property using the returned data from an AJAX call in this instance?
if you're manipulating this throughout your service, assign it to a variable like var self = this. The problem is that this is the context of a function and can be changed by other code using fn.call(context) or fn.apply(context, args). So it's liable to be different inside of the scope of any given function.
So just assign it to some variable and use it:
var self = this;
return {
updateProperty: function(){
getData().then(function(data){
self.someProperty = data;
});
}
};
The simplest way would be to use the bind function. This function sets the 'this' context for the function call. So, in your case you'd have to use it twice so that the proper 'this' populates in.
return{
updateProperty: function(){
getData().then((function(data){
this.someProperty = data;
}).bind(this));
}
}
This comes to ensure that the handler you passed to the promise is executed with the original 'this' (passed to updateProperty). Now, to pass the correct 'this' value to the updateProperty function, you should, in your controller do:
(myService.updateProperty.bind(this))();
There are numerous versions of binding, including binding the entire service. Also, have a look at lodash for function extensions.
I prepared a small pen to demonstrate this. It covers what I listed above, plus another important thing to note. When you use setTimeout, the handler is invoked with in the global context (in this case, 'window'), this is why I added a third bind, to make sure 'this' is relevant inside the timeout handler. I also added various count increment calls to demonstrate that 'this' is the same value along the way.
If this is a repeating scenario, you might want to pass either the target object (and then use the handler just to know it was updated), or a handler (which also needs binding). I added examples for these scenarios as well.
One last word, call, apply and bind are key to javascript and worth learning. Put some time into it and work your way out of context hell.
I'm having a hard time understanding the what "this" is referring to in the Todo.js tutorial in Backbone.js. In specific, inside the AppView:
initialize: function() {
this.input = this.$("#new-todo");
this.allCheckbox = this.$("#toggle-all")[0];
Todos.bind('add', this.addOne, this);
Todos.bind('reset', this.addAll, this);
Todos.bind('all', this.render, this);
this.footer = this.$('footer');
this.main = $('#main');
},
So when Todos.bind('add', this.addOne, this) is called, it is binding the view (this.addOne) to the collection ('add'). If so, we assume that the third parameter ("this") is also referencing to the AppView object. Why do we need to have "this" as a third parameter?
Annotated source code: http://backbonejs.org/docs/todos.html
The this as the third argument is the this context to set when the function in the second argument is invoked.
That sounds very confusing, so let me try and break it down. Let's look at this line...
Todos.bind('add', this.addOne, this);
Had we used this instead...
Todos.bind('add', function() { this.$el.text("Hello, world!"); });
...we wouldn't need the third argument at all. In this case, the function is called immediately and its this is preserved.
However, because we don't want to inline every function, we pass a reference to the function like so...
Todos.bind('add', this.addOne, this);
This is the same as in your example. When you call a function like this.addOne(), its this is set to the this it was called on.
However, when you pass a reference (don't immediately invoke it), its this context is lost, and when invoked, it will be window, which isn't what you want.
To prevent this, the third argument is the this context to use when the function is invoked.
Internally, Backbone will use _.bind() to bind it. This is a polyfill for the native bind() method on functions.
I've been looking at some examples of backbone.js based application. I notice that in some (such as this example below) the underscore function _.bindAll() is used:
initialize: function (args) {
_.bindAll(this, 'changeTitle');
this.model.bind('change:title', this.changeTitle);
},
whereas in others (such as the todo app below) do not:
initialize: function() {
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.remove, this);
},
What is the purpose of _.bindAll() in this context, and is it necessary?
_.bindAll() changes this in the named functions to always point to that object, so that you can use this.model.bind(). Notice that in your second example, a third argument is passed to bind(); that's why using _.bindAll() isn't necessary in that case. In general, it's a good idea to use for any methods on the model that will be used as callbacks to events so that you can refer to this more easily.
In Detail: _.bind(ctx, 'method')
Takes your method, creates a copy with the context bound to 'ctx' and adds the copy as property.
This is a workaround for jQuery.bind() not allowing you to pass in a context.
JQ will always call the callbacks with a undefined context. Backbone is built on jQuery.
See here: http://backbonejs.org/#FAQ-this