Access dialog in callback method with proxy - javascript

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/

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();

pass "this" reference to function inside on()

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 :)

How to call current objects public method in JavaScript?

At the moment I came this far.
function Class() {
var privateMethod = function () {
return 'private'
}
this.publicMethod = function () {
return 'public'
}
var _constructor = function () {
$(document).on('click', _onClick)
}
var _onClick = function () {
// My error is `this`, focus now on the click event, but I need the object itself
console.log(privateMethod())
console.log(this.publicMethod())
}
_constructor()
}
$(document).ready(init)
function init() {
new Class()
}
The problem is that, in the click event, I am unable to call publicMethod.
I am able to call the private method.
How can I achieve this?
The problem is that, in your handler you've lost your context (this no longer means your instance of Class, it instead means the object that triggered your event. You need to create a closure scoped version of this to hold onto that context.
var self = this;
var _onClick = function () {
// My error is `this`, focus now on the click event, but I need the object itself
console.log(privateMethod())
console.log(self.publicMethod())
}
You have a scope issue, this in the onclick is pointing to a different object than what you expect. In your case it is the document
var that = this;
var _onClick = function () {
// My error is `this`, focus now on the click event, but I need the object itself
console.log(privateMethod())
console.log(that.publicMethod())
}
Running Example

Prototype Pattern and "this"

I'm trying to create a client-side api for a web control using the Prototype pattern. However I want to make life easier by not having to manage "this".
This is some sample code (i have commented the problematic line):
MyObject = function ()
{
MyObject.initializeBase(this);
this._someProperty = null;
};
MyObject.prototype = {
initialize: function()
{
// Init
},
get_someProperty: function()
{
return this._someProperty;
},
set_someProperty: function(value)
{
this._someProperty = value;
},
doSomething: function ()
{
$('.some-class').each(function ()
{
$(this).click(this.doClick); // this.doClick is wrong
});
},
doClick: function ()
{
alert('Hello World');
}
};
Normally, using the revealing module pattern I would declare a private variable:
var that = this;
Can I do something similar with the Prototype pattern?
You can do the exact same thing you are used to, just do it within the doSomething method:
doSomething: function ()
{
var instance = this;
$('.some-class').each(function ()
{
$(this).click(instance.doClick);
});
},
This approach has nothing to with prototype or not, it's just how to manage context with nested functions. So when a function on a prototype (method) has nested functions within in, you may have to preserve the context this at any of those level if you want to access it in a nested scope.
ES5's Function.prototype.bind() might be an option for you. You could go like
doSomething: function ()
{
$('.some-class').each(function(_, node)
{
$(node).click(this.doClick); // this.doClick is right
}.bind(this));
},
Now, we proxied each event handler by invoking .bind() and as a result, we call it in the context of the prototype object. The caveat here is, you no longer have this referencing the actuall DOM node, so we need to use the passed in arguments from jQuery instead.

How to use prototype method in class constructor

I habe read here about defining method for a Javascript class Advantages of using prototype, vs defining methods straight in the constructor? and I choose prototype way. But I get an issue, for example:
function MyClass() {};
MyClass.prototype.Hide = function() {};
function MyClass() {
this.layout = $("<div>", {id: "layout1"}).text("My content");
this.button = $("<input />", {id: "button1"});
this.layout.append(this.button);
$("#button1").click(function() {
//How can I call hide
this.Hide()//Error
});
}
MyClass.prototype.Hide = function() {
this.layout.hide("slow");
}
How can I call the prototype function in the contructor? I have try the forward declaration for the prototype method, but I think the issue is the way I call it, this.Hide() is no help!
Thanks for your time!
You're using the wrong this. The this you're using to call Hide() is actually the #button element. Assign the this that is the MyClass object to a local variable, and then use that in the click delegate:
...
this.layout.append(this.button);
var $this = this;
$("#button1").click(function() {
$this.Hide();
});
...
$("#button1").click(function() {
//How can I call hide
this.Hide()//Error
});
In this line of code, this refers to the button (it's inside a function).
Before ths binding, you can define var that = this; and use thatin the callback:
function MyClass() {};
MyClass.prototype.Hide = function() {};
function MyClass() {
var that = this;
this.layout = $("<div>", {id: "layout1"}).text("My content");
this.button = $("<input />", {id: "button1"});
this.layout.append(this.button);
$("#button1").click(function() {
//How can I call hide
that.Hide();
});
}
MyClass.prototype.Hide = function() {
this.layout.hide("slow");
}
You're not calling Hide in the constructor. You're calling it in the click callback, which has a different context (this is different).
Use a temp variable to store a reference to the current object:
var t;
t = this;
...click(function () {
t.hide();
});
Also, JavaScript convention is that PascalCase is used for constructors, and camelCase is used for functions/methods.
You can call prototype methods from constructor. You problem is that you are loosing context inside anonymous click function. So you have two options:
// 1. link to original object
var self = this;
$("#button1").click(function() {
self.Hide();
});
// 2. use proxy (bind) to invoke method in correct context
// there is built in function available in jQuery
$("#button1").click($.proxy(function() {
this.Hide();
}, this));

Categories