pass "this" reference to function inside on() - javascript

I have following piece of code. What is does is when use hover's over a link then it consoles this.
var Mod = function () {
$('.link').hover(this.hover, this.out);
};
Mod.prototype = function () {
var hover = function () {
console.log(this);
},
out = function () {
console.log(this);
};
return {
hover: hover,
out: out
}
}();
In my above code this references to $('.link') element but I want to use this for current object. So to achieve this, I can modify constructor to following.
var Mod = function () {
var self = this;
$('.link').hover(function () {
self.hover();
}, function () {
self.out();
});
};
This works fine but the constructor looks messy now. Second way is to use jquery $.proxy() again this will make my constructor look messy.
My question is that how can I pass this which references to current object to rest of the functions inside object while using the jquery's hover function as i am using it now in first example above?

The code in your question looks perfect to me. You're calling hover and out in the correct context making this valid and pointing to the instance of Mod inside those functions.
this in a member function should always point to the instance of the object, so I would keep doing it that way even though you think it's a mess. A good IDE will be able to assist you or the team with both syntax and auto-completion, which I think is more important.
Don't do this
While you could assign this to a data-member of .link it will make the code less readable and prone to error:
var Mod = function () {
$('.link').data("mod", this);
$('.link').hover(this.hover, this.out);
};
Mod.prototype = function () {
var hover = function () {
console.log($(this).data("mod"));
},
out = function () {
console.log($(this).data("mod"));
};
return {
hover: hover,
out: out
}
}();
Side note: you could simplify prototype definition and just write it like this:
Mod.prototype.hover = function() {
}
Mod.prototype.out = function() {
}

You could make it like that:
var Mod = function () {
$('.link').hover(this.listener('hover'), this.listener('out'));
};
Mod.prototype = function () {
var hover = function () {
console.log(this);
this.otherMethod();
},
out = function () {
console.log(this);
this.otherMethod();
},
listener = function(func) {
var self = this;
return function() {
self[func]();
}
},
otherMethod = function() {
console.log("That's a method of Mod.");
};
return {
hover: hover,
out: out,
otherMethod: otherMethod,
listener: listener
}
}();
Just use a helper which returns a function. Your constructor is clean, but your prototype is not so :)

Related

Calling a JavaScript function from a nested objects function fails

On the simple example below and on JSFiddle here - https://jsfiddle.net/jasondavis/dnLzytju/ you can see the issue I have.
I can see why it could happen but I am not sure how to fix it while keeping the same JS structure.
The issue is when I define a JavaScript objects prototype functions and I have a 2nd level nested object which has a function and in that function I call a function on the parent/root level it fails.
This function from the code below this.nestedObject.nested_object_function() tries to call the function this.normal_function() however it fails and says:
Uncaught TypeError: this.normal_function is not a function
at Object.nested_object_function (VM2493:79)
I assume the reason is that this is referencing this.nestedObject instead of the parent object.
If that is the case, then how can I call that function like I am trying to do from the nested object function and call a parent function?
I have also tried calling JsLibTest.normal_function() as a test from the this.nestedObject.nested_object_function() function but I get the same error.
var JsLibTest = (function (document) {
"use strict";
var JsLibTest = function (){
// run init() function on initiation of a new JsLibTest object
this.init();
};
/**
* JsLibTest prototype functions
*/
JsLibTest.prototype = {
init: function() {
// as expected this function runs fine
this.normal_function();
// nested level objects functions run fune from parent level object function
this.nestedObject.nested_object_function();
},
normal_function: function() {
console.log('this.normal_function() ran');
},
nestedObject: {
// calling a function on the parent object fails here when called from this nested object function
nested_object_function: function() {
this.normal_function();
console.log('this.nestedObject.nested_object_function() ran');
},
}
};
return JsLibTest;
})(document);
// run it
$(document).ready(function(){
var Sidebar2 = new JsLibTest();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Your assessment is correct. this will be set to the nested object instead of the parent object and that's why it says the function is undefined.
What you need is a way of referencing the parent. Objects don't normally carry any information needed to reference an object which references them. This makes sense when you consider the fact that many objects can reference the same object internally.
You can either store a reference to the parent object and reference that in the nested function:
var nested = {
g() {
this.parent.f();
}
};
var parent = {
f() {
console.log('called');
}
};
nested.parent = parent;
nested.g();
or you can use Function.prototype.call (or something similar) to set the correct context.
var obj = {
f() {
console.log('called');
},
g() {
this.nested.nested_f.call(this);
},
nested: {
nested_f() {
this.f();
}
}
};
obj.g();
Putting the last solution in to the context of your problem:
var JsLibTest = (function(document) {
"use strict";
var JsLibTest = function() {
this.init();
};
JsLibTest.prototype = {
init: function() {
this.normal_function();
// NOTICE: Using .call here to set the context
this.nestedObject.nested_object_function.call(this);
},
normal_function: function() {
console.log('this.normal_function() ran');
},
nestedObject: {
nested_object_function: function() {
this.normal_function();
console.log('this.nestedObject.nested_object_function() ran');
}
}
};
return JsLibTest;
})(document);
// run it
$(document).ready(function() {
var Sidebar2 = new JsLibTest();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You are correct that scope doesn't have access to the parent. Easy solution would be that you pass parent to the nested object like:
this.nestedObject.nested_object_function(this);
then in your nested function call parent as:
nested_object_function: function(self) {
self.normal_function();
alert('this.nestedObject.nested_object_function() ran');
}
since you pass this (parent) as self you can then call it from nested one.
At first, the Object must be unique for Each, having a prototype:
this.nestedObject=Object.create(this.nestedObject);
var JsLibTest = function (){
// run init() function on initiation of a new JsLibTest object
this.init();
//bind to childs:
this.nestedObject.parent=this;
};
Now you can use this.parent inside of your inner function...
this.parent.normal_function();
If you want this to be the parent, bind:
var JsLibTest = function (){
// run init() function on initiation of a new JsLibTest object
this.init();
//bind to childs:
for(i in this.nestedObject){
var el=this.nestedObject[i];
if(typeof el==="function"){
this.nestedObject[i]=el.bind(this);
}
}
};
To make it easier, may use sth like that ( a helper function):
getfunc:function(...a){
a.reduce((obj,key)=>obj[key],this).bind(this);
}
Use like this:
JsLibTestInstance("nestedObject","nestedobject_function")();
Yea, you are right that the this value in your JSLibTest.prototype.nestedObject function is pointing to nestedObject and not JSLibTest.
If you want to maintain the same call signature, you can declare nestedObject as an IIFE:
nestedObject: (function() {
var that = this;
return {
nested_object_function: function() {
console.log(that);
// this.normal_function();
alert('this.nestedObject.nested_object_function() ran');
}
}
}())
https://jsfiddle.net/dnLzytju/1/
Note: You probably do not want to declare your prototype that way is it effectively deletes all the native prototype methods of the object.
To author your code in a similar way, consider using Object.assign to help you out.
var foo = Object.assign({}, Function.prototype, {
bar() {
console.log("Hello!")
}
});
foo.bar();

JavaScript: how to call a function on the prototype property, from inside another function on the prototype property, all inside a closure?

I am hoping that someone can help me figure out how to do this correctly, rather than just "make it work."
I am trying to use an object inside a closure, and having scope issues:
var Why = function() {
this.foo = 'bar';
}
Why.prototype.explain = function () {
alert(this.foo);
}
Why.prototype.doIt = function () {
this.explain();
}
(function() {
document.addEventListener("DOMContentLoaded", function(event) {
var why = new Why();
why.doIt();
});
})();
And I get in console:
Uncaught TypeError: this.explain is not a function
I could use
Why.prototype.explain.call();
but that just seems wrong, and when I actually do that... this.foo is undefined anyway, so it's obviously not the right approach.
If I remove the self calling function as follows...
var Why = function() {
this.foo = 'bar';
}
Why.prototype.explain = function () {
console.log(this.foo);
}
Why.prototype.doIt = function () {
// Why.prototype.explain.call();
this.explain();
}
// (function() {
document.addEventListener("DOMContentLoaded", function(event) {
var why = new Why();
why.doIt();
});
// })();
then it works of course, but:
what am I missing and where/how can I learn it?
Thanks in advance.
Your code is parsed as
Why.prototype.doIt = function () { ... }(function() { ... });
You're calling the function you want to assign to the prototype, then assigning its return value. Since it returns undefined, Why.prototype.doIt doesn't exist.
You need a semicolon.

Add function to object

I have the following code
var PROMO = PROMO || {};
PROMO.Base = (function () {
var _self = this;
var Init = function () {
WireEvents();
};
var WireEvents = function () {
//wire up events
};
} ());
In the same file I have the code to call the above function
I am trying to get to an end point where I can use the following code
$(document).ready(function () {
PROMO.Base.Init();
});
this gives the error
Cannot call method 'Init' of undefined
Now I know there are many ways to write javascript, but in this case I want to be able to call my functions, or least the Init method in the way shown above.
var PROMO = PROMO || {};
PROMO.Base = (function () {
var _self = this;
var Init = function () {
WireEvents();
};
var WireEvents = function () {
//wire up events
};
var reveal = {
Init: Init
};
return reveal;
} ());
You need to return the public facing functions. See updated code.
Working fiddle with both patterns, using IIFE and direct attribution.
Using var makes the definition private and your function is returning nothing. Use this:
PROMO.Base = {
Init: function() {
},
WireEvents: function() {
};
};
You are wrapping the definition with an IIFE(Immediately Executed Function Expression). So your PROMO.Base object will be assigned the value of that (function(){//blabla})(); returns. But your function doesn't have a return statement. By default it will return undefined.
Which is way your PROMO.Base will be undefined and you get this:
Cannot call method 'Init' of undefined
If you really want that IIFE:
var PROMO = PROMO || {};
// NEVER use _self = this inside static functions, it's very dangerous.
// Can also be very misleading, since the this object doesn't point to the same reference.
// It can be easily changed with Function.prototype.call and Function.prototype.apply
PROMO.Base = (function () {
_PROMO = {
Init : function () {
document.body.innerHTML += "itworks";
},
WireEvents : function () {
//wire up events
}
}
return _PROMO;
} ());
PROMO.Base.Init();
Update
The better and easier pattern is to simply assign the functions to PROMO.Base. Dully note you should not capitalize static functions, but only constructors. So if something is not meant to be instantiated, don't call it Init, it should be init. That is the convention.
var PROMO = {};
PROMO.Base = {};
PROMO.Base.init = function() {
console.log("this works");
};
PROMO.Base.wireEvents = function() {
console.log("this is a static function too");
};
You can attach it to the window object like ...
window.PROMO = (function($, _){
// this will access PROMO.Base
PROMO.Base = {
// inner functions here
Init:{}
};
})(jQuery, _);
Then load it as you do.
Or if you depend from jQuery
(function($){
var PROMO = {
// inner functions
Init: function(){},
WireEvents: function(){}
};
$.PROMO = PROMO;
})(jQuery);
On DOM ready
jQuery(function ($) {
var promo = $.PROMO || undefined;
promo.Base.Init();
});

Access dialog in callback method with proxy

I am currently creating a dialog within a user-define class:
$("<div>").dialog(buttons: {
'one': function () {
$(this).dialog('close').dialog('destroy');
}
});
The above works fine, however, this no longer refers to the class instance in the above function. I can get around this with $.proxy:
...buttons: {
'one': $.proxy(function () {
this.doWork();
}, this)
Then, I can call class methods when the dialog button is clicked.
However, I still need to call .dialog('close').dialog('destroy') on the dialog element itself. After redefining this with $.proxy, how can I access that element in the button callback? e.target refers to the button itself.
I also realize I can do something like this:
var obj = this;
...buttons: {
obj.doWork();
but I'm looking for a way around that.
I'm not sure why you want to avoid var obj = this; inside the class's scope, but the only other way would be with a self-invoking closure which does essentially the same thing. In order to have a reference to both contexts, you need to store the class reference in a different variable.
With closure:
function MyClass() {
this.createDialog = function () {
$("<div>").dialog({
buttons: {
"one": function (self) {
return function (e) {
self.doWork();
$(this).dialog("close").dialog("destroy");
};
}(this)
}
});
};
this.doWork = function () {
// do work
};
}
$(function () {
var obj = new MyClass();
$(".createDialog").click(function () {
obj.createDialog();
});
});
jsFiddle: http://jsfiddle.net/ar4ZL/

scopes in javascript

I have a simpe object in javascript which has few methods..Two of them I want to periodicaly call with window.setTimeout functions. My current code looks like this.
var shakeResolver = function () {
var resolveTimeout;
console.log(this);
var context = this;
this.startShakeResolve = function () {
this.resolveTimeout = window.setTimeout(this.call(context.stopShakeResolve, context), 2000);
$(window)
.on('devicemotion', this.onDeviceMotion);
};
this.onDeviceMotion = function (event) {};
this.stopShakeResolve = function (context) {
this.resolveTimeout = window.setTimeout(context.startShakeResolve, settings.interval);
};
}
The problem is apparently in my misunderstanding how scopes are working, it looks like when calling the function from timeout, it is called from another context where it actually doesn't exist?
Modified code: Scope of setTimeout is always window Object. you can Change context of function using call apply and bind(bind is not supported by older IE browsers IE <= 8 ).
var shakeResolver = function() {
this.resolveTimeout;
console.log(this);
var context = this;
this.startShakeResolve = function() {
this.resolveTimeout = window.setTimeout(function() {
context.stopShakeResolve.apply(context, arguments);
}, 2000);
$(window).on('devicemotion', function(){
context.onDeviceMotion.apply(context, arguments);
});
};
this.onDeviceMotion = function(event) {
};
this.stopShakeResolve = function(context) {
this.resolveTimeout = window.setTimeout(function() {
context.startShakeResolve.apply(context, arguments)
}, settings.interval);
};
}
call() takes as first parameter the context which the function is called from. This means that your this.call(context.stopShakeResolve, context) makes your context be context.stopShakeResolve which means that when the function is called this is equivalent to context.stopShakeResolve.
Just to make it clearer:
this.stopShakeResolve = function (context) {
this.resolveTimeout = window.setTimeout(context.startShakeResolve, settings.interval);
};
Does not have a function inside it called shakeResolver so it would throw an exception on you saying that it does not have property or method called this way. Change the call to the following:
this.stopShareResolve.call(this, context)

Categories