Underscore bindAll: preserving the 'this' context - javascript

I am having issues attempting to preserve uploader as the this context when onSubmit is called. Can any JS gurus help out?
uploader: {
init: function(){
var that = this;
var fileUploader = new Uploader.FileUploaderBasic({
button : $("#upload-btn")[0],
action : "/filesCollection",
onSubmit : that.onSubmit
});
_.bindAll(this, this.onSubmit); // attempting to bind 'this'
},
onSubmit: function(id, fileName){
console.log(this); // still refers to 'fileUploader' object :(
}
}
Results in the following error:
Uncaught TypeError: Cannot read property 'bind' of undefined
Example: http://jsfiddle.net/WilsonPage/BE3Lp/5/

Problem Solved: http://jsfiddle.net/WilsonPage/BE3Lp/41/
Several Important Things I Discovered:
The _.bindAll() must be called before the function is assigned.
The first argument must be the object or this you wish to bind.
The arguments that follow must be the names of the functions present within the object (this) and they must be in string form!
If you want to bind all functions in the object (this) then omit any function names and have the object (this) as the only argument eg. _.bindAll(this)
Hope this helps some confused peeps like me!

By using call or apply you can specify the context for this for any function. This should do the trick (in the fileUploader declaration):
onSubmit: function() {
that.onSubmit.apply(that, arguments);
}
Edit: Updated jsfiddle: http://jsfiddle.net/WDTBV/1/

Related

Javascript object function calling another function within object, Rails

Original Question
Can't figure out why I can't call the second function from within that first function. I am using jQuery-turbolinks. (Also, if you happen to know of a better way to only run page-specific javascript in rails, let me know. Currently this is my best implementation where I check if the body has a certain class, and if it does then I run the init function within this javascript object).
app/assets/javascripts/blogs.js
$(document).ready(function(){
var blogsjs = {
myBlog: this,
init: function(){
alert("hello from blogs");
$("input").on('click', function(){
$(this).hide('slow', function(){
myBlog.another();
});
});
},
another: function(){
alert("I was called!")
}
};
if($('body').hasClass("blogs") == true){
blogsjs.init();
}
});
Solution After Feedback
Simply Just used object.method() syntax from within a method to call another method within that same object:
$(document).ready(function(){
var blogsjs = {
init: function(){
alert("hello from blogs");
$("input").on('click', function(){
$(this).hide('slow', function(){
blogsjs.another();
});
});
},
another: function(){
alert("I was called!");
blogsjs.yetanother();
},
yetanother: function(){
alert("yet another called");
}
};
blogsjs.init();
});
I don't like how messy this code looks, but the encapsulation benefits from an Object-oriented design, I think, is solid: Each resource's javascript only has access to the methods inside its javascript object.
I don't know what you're trying to accomplish with this part of your declaration:
var blogsjs = {
myBlog: this
}
but, this will NOT be set to blogsjs. It will be whatever it was in the above function. In Javascript, this is only set on a function call. It is NOT set in a Javascript literal declaration so you cannot statically declare a property that refers to the object itself. Javascript just does not support that.
You can add properties after the object is constructed that contain references to the object if desired.
If you want myBlog to be initialized to point to blogsjs, then you will have to do that after the object is defined:
var blogsjs = {
init: function() {...},
another: function() {...}
};
blogsjs.myBlog = blogsjs;
In addition, this line of code won't work:
myBlog.another();
because myBlog is a property of an object, not a variable. It must be referenced with its parent object.
So you're probably getting an Cannot read property 'another' of undefined exception because you're specifying myBlog on the blogsjs object but do not reference it. Also myBlog will not be a reference to blogsjs but the scope jquery calls the document.ready function with.
You need to either create the reference inside your init method:
init: function(){
var myBlog = this;
alert("hello from blogs");
$("input").on('click', function(){
$(this).hide('slow', function(){
myBlog.another();
});
});
}
or simply use blogsjs from one scope above your init method.
Have a look at this question to learn about scoping.

How can you pass anonymous functions as parameters to existing functions to use later in javascript?

I am trying to create a basic javascript framework that you can pass different things into, including functions for it to execute later. Right now, I'm in a more simple testing phase, but I can't quite get the function calling to work. A piece of my code is here:
[My JS Fiddle][1]http://jsfiddle.net/mp243wm6/
My code has an object that holds different data, and I want to call the method later, but with data that is available at the time of creation. Here is a code snippet of the function that uses the function that is passed to the object:
clickMe : function() {
this.obj.click(function() {
this.func();
});
}
Any suggestions or things I should read are welcome.
The problem is that there're two different contexts:
clickMe : function() {
// here is one
this.obj.click(function() {
// here is another
this.func();
});
}
You can simple pass the function as parameter, like the following:
clickMe : function() {
this.obj.click($.proxy(this.func, this));
}
http://jsfiddle.net/mp243wm6/2/
The problem:
Considering your code in the JSFiddle, you have:
onClick : function() {
this.obj.click(function() {
this.func();
});
},
As noted, you have different contexts going on here.
Consider the snippet this.obj.click(function() { this.func(); }). The first this here is a reference to the framework.events object. The second this here is a reference to whatever will be this when this function get called. In the case of your JSFiddle, when this.func gets called, this is actually the DOM object that represents the <div id="test">TEST</div> node. Since it doesn't have a func function, calling func() on it causes:
Uncaught TypeError: undefined is not a function
You have to understand the following: you have to pass the correct this in which you want the function func to be called.
The solution:
A couple of ways to make it work as you would like:
1. with bind
this.obj.click(this.func.bind(this));
This way, you are telling: "call my this.func function, but make sure that it will be called using the this that I am passing as a parameter". Vanilla JS, no $.proxy stuff.
JSFiddle
2. with a copy of the reference to the actual function
onClick : function() {
var theFunctionReference = this.func;
this.obj.click(function() {
theFunctionReference();
});
},
This way, you will not rely on the value of this outside of the context of the framework.events object.
JSFiddle
The issue is that this is not bound to the correct object. I would suggest you look into Function.bind() because that creates a function with this pointing to the right thing.

How to pass arguments in a function reference

I'm looking for a way to save reference to two this objects in one function called after an event triggers in jQuery - this reference to the object the method is defined in (so I can use this.anotherObjectFunction()) and this reference to the object that triggered the event - so that I can use $(this).someJQueryFunction later on. The way I'd like to do it is by passing a this (function object) reference as an argument to the function. Unfortunately, the function is to be called by jQuery, not me, so it's passed as a reference, i.e.
someFunction: function()
{
...
cell.$el.on('click', 'li.multiselect-option', this.myClickFunction);
...
},
myClickFunction: function(objectReference)
{
//It should be able to call methods of that object.
objectReference.anotherFunction();
//And reference to the clicked item.
$(this).html("Don't click me anymore!!!");
}
I'm aware of the fact that I can do something like
cell.$el.on('click', 'li.multiselect-option', myFunction.bind(this));
...
myClickFunction: function(event)
{
this.anotherFunction();
$(event.currentTarget).html("Don't click me anymore!!!");
}
But this workaround doesn't really answer the question as it doesn't show how to pass additional arguments and in the future there may be a necessity to pass another (no, I don't want to register them as fields in the object).
No anonymous functions are allowed unless they can be easily removed with cell.$el.off() function that will remove them and only them (there are some other function associated with the same objects and events at the same time and they should remain intact).
UPDATE:
By no anonymous functions I mean solutions like:
var self = this;
cell.$el.on('click', 'li.multiselect-option', function() {
self.MyClickFunction(self, this);
});
They will not work because I'll have to use cell.$el.off() with the function reference (3-argument prototype) to remove this single function and only it, leaving other functions bound to both the same element and event.
Jquery .on event has option to pass the argument as parameter in event handler like this
cell.$el.on('click', 'li.multiselect-option', {arg1:'arg1' , arg2:'arg2'} , myFunction);
...
myClickFunction: function(event)
{
alert(event.data.arg1);
alert(event.data.arg2);
this.anotherFunction();
$(event.currentTarget).html("Don't click me anymore!!!");
}
Passing data to the handler
If a data argument is provided to .on() and is not null or undefined,
it is passed to the handler in the event.data property each time an
event is triggered. The data argument can be any type, but if a string
is used the selector must either be provided or explicitly passed as
null so that the data is not mistaken for a selector. Best practice is
to use a plain object so that multiple values can be passed as
properties.
or another way
cell.$el.on('click', 'li.multiselect-option', function() {
myClickFunction("hai" , "bye");
});
myClickFunction: function(arg1, arg2)
{
alert(arg1);
alert(arg2);
}
And I would also suggest a plugin-free solution to the question "how to supply a parameter to function reference"
Example
function x(a){
console.log(a);
}
setInterval('x(1)',1000);

Is it possible to use object as invokable?

I found in requirejs-text plugin weird code. So load method accepts onLoad callable which is invoked for a few times as onLoad(), but later there is error handler which checks for an error method.
if (onLoad.error) {
onLoad.error(err);
}
Am i missing something or it's obvious code issue?
You cannot use a normal object as a callable entity, but a function is a type of object, so you can add properties to a function.
var onLoad = function(){
};
onLoad.error = function(){
};

Firefox add-on - `this` works in one method but fails in another method of the same object

I am developing an add-on for Firefox (3.6.*). in the following code notify called from inside init works fine, but I get an error saying this.notify is not a function when it is called from within onPageLoad. Why is that?
Also when I change the call to myextobj.notify('title', 'msg'), it works. The same is true for accessing variables. So, what is the difference between this and the object name as a prefix?
var myextobj = {
init: function() {
this.notify('init', 'We are inside init');
...
var appcontent = document.getElementById("appcontent"); // browser
if(appcontent)
appcontent.addEventListener("DOMContentLoaded", this.onPageLoad, true);
},
onPageLoad: function(aEvent) {
this.notify('onPageLoad', 'We are inside onPageLoad');
...
},
notify: function (title, text) {
Components.classes['#mozilla.org/alerts-service;1'].
getService(Components.interfaces.nsIAlertsService).
showAlertNotification(null, title, text, false, '', null);
}
};
window.addEventListener("load", function() { myextobj.init(); }, false);
When you do this:
appcontent.addEventListener("DOMContentLoaded", this.onPageLoad, true);
you just add the function that is hold in onPageLoad as event handler. The connection to the object is lost and this will refer to the global object when executed.
Just create an anonymous function as you do for the load event:
var that = this; // capture reference to object
appcontent.addEventListener("DOMContentLoaded", function(event) {
that.onPageLoad(event);
// myextobj.onPageLoad(event); should also work in this case
}, true);
Remember that functions are first class objects in JavaScript, they can be passed around like any other value. Functions have no reference to an object they are defined on, because they don't belong to that object. They are just another kind of data.
To which object this refers to in a function is decided upon execution and depends on the context the function is executed in. If you call obj.func() then the context is obj, but if you assign the function to another variable before like var a = obj.func (that is wat you do with adding the event handler (in a way)) and then call a(), this will refer to the global object (which is window most of the time).
When onPageLoad is called for the event, 'this' would not be referring to your myextobj. Because it wasn't called in the context of your object myextobj.
The way I deal with this is, by having all member functions of an object using the following convention.
var myObj = {
.....
counter: 0,
.....
myFunction: function () {
var t = myObj;
t.myOtherFunc();
},
....
myOtherFunc: function() {
var t = myObj;
t.counter++;
}
};
See how, I'm aliasing myObj as t, to save on typing and making my intent of using this clear.
Now you can call your methods safely from any context without worrying about what this would be referring to. Unless you really want the standard behavior; in that case, you may like to look at the call and apply methods. This link might help: Function.apply and Function.call in JavaScript
You may also want to look at a recent addition to JavaScript (would be available in FireFox 4): the bind method: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
Another link, which directly addresses your problem: https://developer.mozilla.org/en/DOM/element.addEventListener#The_value_of_this_within_the_handler
The other way to add an event listener without losing track of this is to pass this itself as the event listener. However you are limited in that the function is always called handleEvent, so it's less useful if you have many listeners (unless they are all for different events, in which case you can switch on the event's type).

Categories

Resources