I have a problem with variable scope. I am setting event listeners (onclick), but the handler is method of an object and I need to refer to the object within the handler method.
Example:
var FOO = function () {
this.clicked = false
};
FOO.prototype.handler = function(e)
{
this.clicked = true;
}
FOO.prototype.setListeners = function()
{
$("#but").click(this.handler);
}
var oop = new FOO();
oop.setListeners();
Example works to the point this.clicked = true; where because this doesn't refer to the oop.
How do I pass a reference of the object to the handler function?
FOO.prototype.setListeners = function()
{
var that = this;
$("#but").click(function(){that.handler();});
}
Related
Let's assume we have the following code:
var MyClass = (function(){
var _this;
function MyClass(inputVal){
_this = this;
this.value = inputVal;
}
MyClass.prototype.getValue = function(){
return this.value;
}
MyClass.prototype.getValue2 = function(){
return _this.value;
}
return MyClass;
})();
Let's make two instances of the class:
var instance1 = new MyClass(10);
var instance2 = new MyClass(20);
Now if we console.log() the values we see that:
instance1.getValue(); // 10
instance1.getValue2(); // 20
var MyClass = (function(){
var _this;
function MyClass(inputVal){
_this = this;
this.value = inputVal;
}
MyClass.prototype.getValue = function(){
return this.value;
}
MyClass.prototype.getValue2 = function(){
return _this.value;
}
return MyClass;
})();
var instance1 = new MyClass(10);
var instance2 = new MyClass(20);
console.log(instance1.getValue());
console.log(instance1.getValue2());
Why is that happening? It looks obviously that the _this variable gets the latest created instance properties. How to fix that? I need to keep a copy of this. Thanks!
Edit:
Here's the real situation
var HoverEffects = (function(){
var _this;
function HoverEffects($nav){
_this = this;
this._$activeNav = $nav.siblings('.active_nav');
this._$hoverableLis = $nav.find('>li');
this._$activeLi = $nav.find('>li.active');
if(!$nav.length || !this._$hoverableLis.length || !this._$activeNav.length || !this._$activeLi.length) return;
if(this._$activeNav.hasClass('bottom')){
this._$activeNav.align = 'bottom';
this._$activeLi.cssDefault = {
left: this._$activeLi.position().left,
width: this._$activeLi.width()
};
}
else if(this._$activeNav.hasClass('left')){
this._$activeNav.align = 'left';
this._$activeLi.cssDefault = {
top: this._$activeLi.position().top,
height: this._$activeLi.height()
};
}
else{
return;
}
this._$hoverableLis.hover(
function(){
// How to set the correct this inside this function?
if(this._$activeNav.align === 'bottom'){
this._$activeNav.css({
left: $(this).position().left,
width: $(this).width()
});
}
else if(this._$activeNav.align === 'left'){
this._$activeNav.css({
top: $(this).position().top,
height: $(this).height()
});
}
},
function(){
// Same here, wrong this
this._$activeNav.css(this._$activeLi.cssDefault);
}
);
}
return HoverEffects;
})();
var sideNavHoverMagic = new HoverEffects($('#side-navigation'));
var primaryNavHoverMagic = new HoverEffects($('#primary-navigation'));
Why is that happening?
Every time you call new MyClass, _this = this gets run. The second time overrides the first time.
So _this refers to new MyClass(20), which means that when you call getValue2 from any MyClass instance, 20 will be returned because all MyClass instances are referring to the same _this value.
Based on commentary on the Question:
If you're attempting to pass a function bound to the appropriate context there are a variety of ways to make sure that this refers to the right object. Before continuing, please read "How does the 'this' keyword work?", because there's no reason for me to repeat all of it here.
If you're binding event callbacks such as in a constructor:
function Example(something) {
something.addEventListener(..event.., this.callback, false);
}
Example.prototype.callback = function () {
this.doStuff();
this.doMoreStuff();
};
The callback will have the wrong this value because it's not being called as this.callback, it's just being called as:
fn = this.callback;
fn(); //no reference to this
You can get around this in a number of ways.
Function.prototype.bind
You can bind the callback for every instance on their respective instance. This is very concise:
function Example(something) {
//generate a new callback function for each instance that will
//always use its respective instance
this.callback = this.callback.bind(this);
something.addEventListener(..event.., this.callback, false);
}
Example.prototype.callback = function () {
this.doStuff();
this.doMoreStuff();
};
that = this
You can create the callback (closure) within the constructor and reference a variable inside the constructor.
function Example(something) {
//every Example object has its own internal "that" object
var that = this;
this.callback = function () {
//this function closes over "that"
//every instance will have its own function rather than
//a shared prototype function.
that.doStuff();
that.doMoreStuff();
}
something.addEventListener(..event.., this.callback, false);
}
() => {} (Fat Arrow Syntax)
If you're using ES2015 you can use "fat arrow" syntax for creating lambdas that don't create a new context:
function Example(something) {
this.callback = () => {
//the callback function doesn't create a new "this" context
//so it referes to the "this" value from "Example"
//every instance will have its own function rather than
//a shared prototype function.
that.doStuff();
that.doMoreStuff();
}
something.addEventListener(..event.., this.callback, false);
}
So i need to get the inner text of a given element through a Jquery event, and then set this text into a member of my class e.g.
myClass = function ()
{
this.index = 0;
this.onNavElementClick = function ()
{
this.index = parseInt(this.text());
}
this.myMain = function ()
{
$("nav#wow-so-much-inspiration").on("click", "a", this.onNavElementClick);
}
}
myObject = new myClass();
myObject.myMain();
HTML:
<nav id="wow-so-much-inspiration">
1
2
3
</nav>
But this won't work because of the two different scopes inside the onNavElementClick() function... And i don't like the idea of doing _this = this, i'm pretty sure there is a right way to do that without doing MacGyver coding.
jQuery event handlers also take the event object (including the target on which the event was triggered) as a first argument. Then you can use $.proxy to have your event handler bound to the outer this.
Something like this:
this.onNavElementClick = $.proxy(function (e)
{
this.index = parseInt($(e.target).text());
}, this);
You could make use of the bind method, but I believe this has been proven to have minor performance implications.
Example - which is essentially what the $.proxy answer does.
var myClass = function ()
{
this.index = 0;
this.onNavElementClick = (function (event) {
this.index = parseInt( $(event.target).text() );
}).bind(this);
this.myMain = function ()
{
$("nav#wow-so-much-inspiration").on("click", "a", this.onNavElementClick);
};
}
var myObject = new myClass();
myObject.myMain();
Another option is to use call or apply with a simple wrapper function.
var myClass = function ()
{
this.index = 0;
this.onNavElementClick = function (event)
{
this.index = parseInt( $(event.target).text() );
};
this.myMain = function ()
{
var self = this;
$("nav#wow-so-much-inspiration").on("click", "a", function (event) {
self.onNavElementClick.call(self, event);
});
};
}
var myObject = new myClass();
myObject.myMain();
Define onNavElementClick as a var within the my class constructor and reuse the function as needed.
var onNavElementClick = function(){...};
I am trying to call instance method inside event handler of another instance method but I am getting function undefined, I assume this is because in the event handler "this" refers to DOM element rather then the instance:
function MyObject(something) {
this.something = something;
this.value = 'abc';
}
MyObject.prototype.Init = function() {
$(this.something).click(function() {
this.DoSomething();
});
};
MyObject.prototype.DoSomething = function() {
//do something
};
Is there way to get "this" to point to instance?
Define another variable whose name is something other than this and assign it the context you wish to refer to in your inner function:
MyObject.prototype.Init = function() {
var scope = this;
$(this.something).click(function() {
scope.DoSomething();
});
};
I have the following class in javascript:
function User(aJid){
this.jid = aJid;
this.name = '';
this.uni = '';
this.edad = '';
this.foto = '';
this.avatar = '';
this.initialize2 = function(){
$('#edit_vcards').on('click', '#enviar_vcard', function(){
//alert("enviando...");
console.log(this);
});
};
As you can see I have a method "initialize2" that binds a function to some elements in the DOM. In there I do a console.log(this) which prints the DOM element we binded the method to and not the object that is executing the method initialize2. How can I have access to that object from that function?
Its like if the scope of the function binded is the whole DOM and not the object. Anyway to do what Im trying to do ?
function User(aJid){
this.jid = aJid;
this.name = '';
this.uni = '';
this.edad = '';
this.foto = '';
this.avatar = '';
this.initialize2 = function(){
var that = this; //store a reference to maintain scope
$('#edit_vcards').on('click', '#enviar_vcard', function(){
//alert("enviando...");
console.log(that); //use that variable here
});
};
Try passing the obj this to .on and the inside the handler you can use event.data to access the obj this. See below,
this.initialize2 = function(){
$('#edit_vcards').on('click', '#enviar_vcard', {obj_this: this }, function(){
//alert("enviando...");
console.log(event.data.obj_this); //should be the obj this
});
};
Pass the outer this through event.data:
$('#edit_vcards').on('click', { outerThis: this }, function (event) {
console.log(event.data.outerThis);
});
Nowadays with ES6 it can be even more elegant
$('#edit_vcards').click( () => {
//alert("enviando...");
console.log(this); // all your variables are availabled here
});
or even like that (if you need only one line):
$('#edit_vcards').click( () => console.log(this) );
NOTE: This code cannot be used directly and should be additionally compiled with balel, for example.
In the SomeObj object, the onkeydown event handler this.doSomething is called in the wrong context (that of the textbox element) but it needs to be called in the context of this. How can this be done?
function SomeObj(elem1, elem2) {
this.textboxElem = elem1;
this.someElem = elem2;
this.registerEvent();
}
SomeObj.prototype = {
registerEvent: function() {
this.textboxElem.onkeydown = this.doSomething;
},
doSomething: function() {
// this must not be textboxElem
alert(this);
this.someElem.innerHTML = "123";
}
};
Copy the reference to a local variable, so that you can use it in a closure:
registerEvent: function() {
var t = this;
this.textboxElem.onkeydown = function() {
t.doSomething();
};
},