I've got a JS object, and one of it's prototype functions is an event handler for a click. When that function gets called, the this object is set to the element that the click is bound to. I'd like this to be the instance of the object that the function belongs to. Is this possible, and if so, how do I do it? Solutions with or without jQuery are acceptable to me, although I'm sure that the rest of SO would appreciate a pure JS solution.
I've tried binding the function to this, and it gets bound to the window rather than the instance of the object.
Example of what I want: In this demo (code duplicated below), I want an alert that says "Bark" when the button is clicked.
var Dog = function () {
this.sound = "Bark";
}
Dog.prototype = {
sayHello: function (e) {
if (typeof this.sound == "undefined") {
alert("I don't know what sound I should make!\n" + this);
} else {
alert(this.sound);
}
}
}
var d = new Dog();
var elem = document.getElementById("click");
elem.addEventListener("click", d.sayHello);
You can use .bind() like this:
elem.addEventListener("click", d.sayHello.bind(d));
The manual way of doing it is with your own function:
elem.addEventListener("click", function(e) {
return d.sayHello();
});
If you always intend for a function to be invoked with its own context, do the binding when the constructor runs instead:
var Dog = function () {
this.sound = "Bark";
this.sayHello = this.sayHello.bind(this);
}
http://jsfiddle.net/04ykpsx1/1/
Something like _.bindAll can reduce the boilerplate for you.
This is a better approach than forcing your callers to always call a function with .bind, because they shouldn't need to understand your class so deeply.
Related
When creating modular JavaScript code for web pages, I often need to attach events to e.g. buttons.
Take the following example code, typically found in an AMD module:
define(function(require) {
var MyObject = function(ele) {
this.ele = ele;
}
MyObject.prototype = {
myfunct: function() {
console.log('myfunct called');
}
}
return MyObject;
});
Later on the page I would do:
$(document).ready(function() {
var button = $('#button'),
myobj = new MyObject(button);
button.click(function() {
myobj.myfunct();
});
});
This works, but still seems to be a bit unclean I think.
For example, I need to create at least one variable in the global namespace to bind a function to a button. Also, when there are many JavaScript powered interactions on a page, the code gets messy – which is something I initially wanted to tackle by using modular JavaScript.
Thats why my idea was to to the event binding inside the prototype:
var MyObject = function(ele) {
self = this;
this.element = ele;
this.init();
}
MyObject.prototype = {
init: function() {
$(this.element).click(function() {
self.myfunct();
});
},
myfunct: function() {
console.log('myfunct called');
}
}
That way, the code outside of the module would look like this:
$(document).ready(function() {
var button = $('#button'),
myobj = new MyObject(button);
});
Is it wise to move the event binding into the prototype? If so, is it okay the way I have done it, or is there a way using init()?
In addition, I've noticed that when there are two buttons on a page, some context is lost – self always refer to the last instance of MyObj.
Why is this happening – I thought with using self = this; I could prevent the context?
Fiddle
Ok, first what's happening with self.
With this line:
self = this;
you are creating a global variable called self that gets overwritten every time your constructor is called. This could have been easily detected if you were using strict mode. Also, if you were using a local variable correctly, your prototype would have no idea what self, so you attempt to use self in the prototype is broken.
I think there are problems with both of your approaches:
The first approach requires too much manual work outside of your MyObject type.
The second approach (if it worked correctly) attaches events to the button as a side effect of calling the constructor. This is confusing to someone using your API because one expects a constructor to create an object, not to modify other existing objects.
As a remedy, I would propose the following approach:
var MyObject = function(ele) {
this.element = ele;
}
MyObject.prototype = {
attachEvents: function() {
var self = this;
$(this.element).click(function() {
self.myfunct();
});
},
myfunct: function() {
console.log('myfunct called');
}
};
$(document).ready(function() {
var button = $('#button'),
myobj = new MyObject(button);
myobj.attachEvents();
});
This requires one extra step on the part of the person instantiating the MyObject, but it clearly conveys the intent of attaching events to myobj's encapsulated elements. It also doesn't require someone using a MyObject to do the intricate maneuvering of your first approach.
Let's start from the second question. The problem with your code is that you declare self as global variable because you forgot/omitted var keyword. As the result when you create two or more instances the last one overwrites previous and self inside all click events points to the last instance.
Check the fixed code. Note that you have to move var self = this to init method, because now it's local variable:
var MyObject = function(ele) {
this.element = ele;
this.init();
}
MyObject.prototype = {
init: function() {
var self = this;
$(this.element).click(function() {
self.myfunct();
});
},
myfunct: function() {
console.log('myfunct called');
}
}
As for the first question, it's alright it's your design and there is nothing wrong with it. Binding events in init method is indeed cleaner.
There's nothing wrong with doing the event binding the way you are. The reason you're losing the scope is because you did
self = this;
which created self as a global variable, in the constructor function. So every time you call the constructor it sets self to that instance.
To fix it, set self as a local variable in your init function:
MyObject.prototype = {
init: function() {
var self = this; // <-- this is the fix
$(this.element).click(function() {
self.myfunct();
});
}
I've written a class and this is a snippet taken from it to demonstrate my problem.
<!--language:lang-js-->
var myclass = function () {
this.addItem = function () {
//generateDiv and append..
//generatedeletebtn and append
btn.onclick = this.deleteItem;
}
this.deleteItem = function () {
console.log(this);
}
}
Inside the deleteItem function, this represents the HTML element clicked. Is there a way I can access the myclass object directly?
I've tried changing
div.onclick = this.deleteItem
to
div.onclick = this.deleteItem(this) which works as far as accessing the object but the parenthesis invoke the function and remove the items as soon as they're added so that's no good.
This looks like a job for bind
btn.onclick = this.deleteItem.bind(this);
You can think of bind as wrapping function A referenced by this.deleteItem in another function B, which always invokes A with the context given as the first parameter of bind.
There is a polyfill available on MDN if you can't assume bind is available in your environment.
You can use the closure to store a reference to the myclass object itself in a variable called that. Then, when deleteItem is eventually called, it should use the myclass object as that.
var myclass = function () {
var that = this;
this.deleteItem = function () {
console.log(that);
}
}
You shouldn't have to worry about polyfills with this one.
My code is very simple. Ans to me it should work.
var preview = WinJS.Class.define(
function (el, options) {
el.winControl = this;
this.el = el;
this.textarea = d.getElementById('preview-input');
this.preview = d.getElementById('preview-text');
this.form = d.getElementById('perview-form');
this.preview.addEventListener('click', this.click, false);
//WinJS.Utilities.query("button", this.form)
//this.preview.addEventListener('', this.save, false);
},
{
click: function (e) {
this.form.style('display', 'block');
}
}
);
WinJS.Namespace.define('RegCtrl', { preview: preview });
But when click occurs this.form seems to be undefined of null. Why? I do not want to initialize objects in every method of the class.
New tests
I made additional test very small
var preview = WinJS.Class.define(
function (el, options) {
var test = 1;
this.test = 1;
this.test1();
},
{
test1: function () {
console.log(this.form, test);
}
}
);
WinJS.Namespace.define('RegCtrl', { preview: preview });
This test fails on line this.test1();. What I think now that this class is called RegCtrl.preview() rather than new RegCtrl.preview().
How do I shek inside the function that this called as new but not a simple function?
The other answers aren't explaining what's going on, and as such are giving incorrect advice.
JavaScript has first-class function objects - you can pass them around as values. That's exactly what you're doing when you set up this callback:
this.preview.addEventListener('click', this.click, false);
You're taking the contents of the this.click property, which happens to be a function, and handing it to the addEventListener function to do whatever it wants with it.
I was very specific about terminology there - note I specifically said function, not method. JavaScript doesn't really have a method construct, it just has methods as properties on an object.
So where does the "this" member come from? It's determined at the caller - the object you use on the left side of the '.' is the one that becomes the value of this. For example,
function exampleFunc() { console.log("this.myName = " + this.myName); }
var a = { myName: "Chris", doSomething: exampleFunc };
var b = { myName: "Bob", doSomething: exampleFunc };
Note I've assigned the exact same function to the doSomething properties. What what happens:
a.doSomething(); // Outputs "this.myName = Chris"
b.doSomething(); // Outputs "this.myName = Bob"
The exact same function object, called through two different objects, has a different this pointer.
exampleFunc is a global function, let's call it:
exampleFunc() // Outputs "this.myName = undefined"
So where'd the undefined come from? In a global function, "this" is set to window (the global scope), which didn't have the myName property defined. Which also means that you could do this instead:
myName = "Global Name"; // note, no var - we want this global
exampleFunc(); // Outputs "this.myName = Global Name"
Ok, so what's going on with the original question? Basically, you've passed the function this.click to be the callback, but you haven't passed the "this" pointer that you want it called through. Actually, addEventListener doesn't have a way to pass the this pointer. As a result, when the function is invoked this is not pointing at your object. I don't remember off the top of my head what it's pointing at - it's either window or the element that was clicked on, check the DOM documentation to verify.
To get it to call the right function with the right context (context = the correct "this"), the traditional approach is to use a closure. Capture "this" in a variable, then pass in an anonymous function that calls your actual callback with the right this pointer. The code looks like this:
var preview = WinJS.Class.define(
function (el, options) {
// Capture your current this pointer in a global variable
// Using "that" as the name comes from JavaScript: The Good Parts book
var that = this;
el.winControl = this;
this.el = el;
this.textarea = d.getElementById('preview-input');
this.preview = d.getElementById('preview-text');
this.form = d.getElementById('perview-form');
// Note what gets passed instead of this.click:
this.preview.addEventListener('click',
function (e) {
// NOTE: Calling through "that": "this" isn't pointing to the right object anymore
// Calling through "that" resets "this" inside the call to click
that.click(e);
}, false);
},
{
click: function (e) {
this.form.style('display', 'block');
}
}
);
This is a common enough pattern that ECMAScript 5 has a utility function to build these wrappers for you - function.bind. Do this:
this.preview.addEventListener('click',
this.click.bind(this),
false);
The construct this.click.bind(this) will construct a new function that, when called, will set the "this" reference to whatever you passed (in this case "this"), and then invoke the function you called it on.
Yes, there are a lot of different values for "this" floating around. Keeping track of what "this" is pointing at is an important part of mastering JavaScript programming.
I think you may want to define a global JavaScript variable as :
var myForm = document.getElementById('perview-form');
or jest define var myForm; and initialize inside function (el, options) as:
myForm = d.getElementById('perview-form');
Now you can use this variable in your function as :
myForm.style('display', 'block');
EDIT: I believe you may define this variable as first line in your WinJS.Class.define to make it instance level variable as below:
var preview = WinJS.Class.define(
var myForm;
function (el, options) {
....
....
myForm = d.getElementById('perview-form');
...
},
{
click: function (e) {
myForm.style('display', 'block');
}
});
This is a really hard thing to research if you don't know what to look for. I added one line and changed another line. That should fix your issue.
In short, the keyword this gets reset every time you enter a new function, this the value of this inside your click function is not the same this of the outer scope. Preserve this this you want. The name of that seems fairly common.
Edited based on the link provided by the OP.
This code is UNTESTED. If using this doesn't work now, then I'd try this2
Sorry I can't test this, but I don't have the framework anywhere so I'm doing
educated guesswork.
var preview = WinJS.Class.define(
function (el, options) {
that = this; // No var should be needed since it is declared already
el.winControl = this;
this.el = el;
this.textarea = d.getElementById('preview-input');
this.preview = d.getElementById('preview-text');
this.form = d.getElementById('perview-form');
this.preview.addEventListener('click', this.click, false);
//WinJS.Utilities.query("button", this.form)
//this.preview.addEventListener('', this.save, false);
},
// This is the section for instance vars
{
click: function (e) {
that.form.style('display', 'block'); // AND THIS ONE
},
that: null // Added instance variable
},
// And these are static variables
{
that2: null
}
);
I need to edit the function which locates inside of the constructor.
Example:
some.thing = function() {
this.somefn = function() { // this is the function that I need to fix
...
}
}
But function should be edited not just only for a single object (new obj = some.thing();) but also for any created objects by this constructor.
So is there any way to edit such inner-functions?
Here is a solution based on prototype:
var Something = function () {
this.f = function () {
console.log("Something");
};
};
var Old = Something;
var Something = function () {
Old.apply(this);
this.f = function () {
console.log("New");
};
};
Something.prototype = new Old();
var s = new Something();
s.f(); // prints "New"
The solutions seem just a little too obvious, so I'm wondering if the trouble is that you don't have access to the original code, and you need a more dynamic solution.
If so, one option may be to override the constructor with your own constructor, and have it call the original, and then update the object.
Original code:
some.thing = function() {
this.somefn = function() { // this is the function that I need to fix
...
}
}
Your code:
// cache a reference to the original constructor
var _thing = some.thing;
// your constructor
some.thing = function() {
// invoke the original constructor on the new object.
_thing.apply(this, arguments);
this.somefn = function() { /*your updated function*/ };
};
// maintain inheritance
some.thing.prototype = Object.create(some.thing.prototype);
// make an instance
var theThing = new some.thing();
Now you're getting the benefit of the original constructor and prototype chain, but you're injecting your own function on to the objects being created.
Only trouble may be that the original function you replaced could make special use of the original constructor's variable scope. If that's the case, there would be an issue to resolve.
It would be possible to retain and invoke the original method that you overwrote before invoking yours. Not sure if this situation calls for that or not.
I exactly know your need cause last week I passed through it. I just implemented a complete inheritance model in javascript and as far as I remember, I had a problem with overriding constructors and calling the parent class's ctor when child class is initializing.
So I just solved the problem with modifing some points in my design and it's now working like a charm! (something like C# but in Javascript)
By the way, I don't suggest you to change a method contents this way, but here is a way to do that (I myself did not do that this way and AGIAIN I DO NOT RECOMMEND IT. THERE ARE MANY OTHER WAYS, BUT THIS IS THE EASIEST):
var test = function() { /*InjectionPlace*/ };
eval("var newTest = " + test.toString().replace(
"/*InjectionPlace*/",
"var i = 10; alert(i);"
));
test();
newTest();
Cheers
In the following code:
function xyz(x) {
//something
this.x = x;
}
xyz.prototype = {
a: function () {
//do something
},
b: function () {
//pre
this.a();
//post
}
}
the call of this.a() gives the warning of method not supported. So I tried using xyz.prototype.a.call(this) instead. But it does not maintain the value of x. What do I do to call one method of a class from other?
Given your code, if you write:
var myXyz = new xyz("hello");
then calling
myXyz.b();
should correctly get to the "a()" function on the prototype. However, if you do something like this:
var otherB = myXyz.b;
otherB();
then it will not work, because there's no context object (that is, the this value inside "b()" won't be set correctly to an instance of "xyz"). That often happens when a function is being used as an event handler:
something.onclick = myXyz.b;
The event handler, when called, won't have an "xyz" instance to work with. Instead of that, therefore, you could write:
something.onclick = function() { myXyz.b(); };
which clearly ensures that there's an "xyz" object.