How do I find the instance to which a method belongs? - javascript

// minified base class code (if the uncompressed code is needed, I'll post it)
function Class(){}Class.prototype.construct=function(){};Class.extend=function(c){var a=function(){arguments[0]!==Class&&this.construct.apply(this,arguments)},d=new this(Class),f=this.prototype;for(var e in c){var b=c[e];if(b instanceof Function)b.$=f;d[e]=b}a.prototype=d;a.extend=this.extend;return a};
// custom event class
var Event = Class.extend({
handlers: [],
// stores the event handler
subscribe: function(handler, thisObj) {
this.handlers.push([handler, thisObj]);
},
// calls the event handlers
fire: function() {
for(i in this.handlers) {
var handler = this.handlers[i];
handler[0].apply(handler[1]);
}
}
});
var Class2 = Class.extend({
myEvent: new Event(), // the event
test: function() { // fires the event
this.myEvent.fire(this);
}
});
var Class3 = Class.extend({
construct: function(model) {
this.name = "abc";
model.myEvent.subscribe(this.handler, this); // subscribe to the event
},
handler: function() {
alert(this.name); // alerts 'abc'
}
});
var instance1 = new Class2();
var instance2 = new Class3(instance1);
instance1.test();
The only way to make the event handler code to work with the good 'this' is by adding a new argument ('thisObj') to the 'subscribe' method? Is there a better way to do this?

The behaviour you're getting is due to the fact that when you pass a "method" to a function, the receiving function has no idea it's a method. It's just a block of javascript that needs to get executed.
Prototype gets around this issue with the bind method
http://api.prototypejs.org/language/function/prototype/bind/
You can get similar behaviour (I didn't look at the implementation details of bind) by using a closure.
var Class3 = Class.extend({
construct: function(model) {
this.name = "abc";
//model.myEvent.subscribe(this.handler, this); // subscribe to the event
var self = this;
model.myEvent.subscribe(function() {self.handler()});
},
handler: function() {
alert(this.name); // alerts 'abc'
}
});
Or apply some similar functionality to the subscribe method of your custom event class.
EDITED To reflect CMS's observations. Thanks!

Related

Cannot removeEventListener HTML5 Canvas [duplicate]

In JavaScript, what is the best way to remove a function added as an event listener using bind()?
Example
(function(){
// constructor
MyClass = function() {
this.myButton = document.getElementById("myButtonID");
this.myButton.addEventListener("click", this.clickListener.bind(this));
};
MyClass.prototype.clickListener = function(event) {
console.log(this); // must be MyClass
};
// public method
MyClass.prototype.disableButton = function() {
this.myButton.removeEventListener("click", ___________);
};
})();
The only way I can think of is to keep track of every listener added with bind.
Above example with this method:
(function(){
// constructor
MyClass = function() {
this.myButton = document.getElementById("myButtonID");
this.clickListenerBind = this.clickListener.bind(this);
this.myButton.addEventListener("click", this.clickListenerBind);
};
MyClass.prototype.clickListener = function(event) {
console.log(this); // must be MyClass
};
// public method
MyClass.prototype.disableButton = function() {
this.myButton.removeEventListener("click", this.clickListenerBind);
};
})();
Are there any better ways to do this?
Although what #machineghost said was true, that events are added and removed the same way, the missing part of the equation was this:
A new function reference is created after .bind() is called.
See Does bind() change the function reference? | How to set permanently?
So, to add or remove it, assign the reference to a variable:
var x = this.myListener.bind(this);
Toolbox.addListener(window, 'scroll', x);
Toolbox.removeListener(window, 'scroll', x);
This works as expected for me.
For those who have this problem while registering/removing listener of React component to/from Flux store, add the lines below to the constructor of your component:
class App extends React.Component {
constructor(props){
super(props);
// it's a trick! needed in order to overcome the remove event listener
this.onChange = this.onChange.bind(this);
}
// then as regular...
componentDidMount (){
AppStore.addChangeListener(this.onChange);
}
componentWillUnmount (){
AppStore.removeChangeListener(this.onChange);
}
onChange () {
let state = AppStore.getState();
this.setState(state);
}
render() {
// ...
}
}
It doesn't matter whether you use a bound function or not; you remove it the same way as any other event handler. If your issue is that the bound version is its own unique function, you can either keep track of the bound versions, or use the removeEventListener signature that doesn't take a specific handler (although of course that will remove other event handlers of the same type).
(As a side note, addEventListener doesn't work in all browsers; you really should use a library like jQuery to do your event hook-ups in a cross-browser way for you. Also, jQuery has the concept of namespaced events, which allow you to bind to "click.foo"; when you want to remove the event you can tell jQuery "remove all foo events" without having to know the specific handler or removing other handlers.)
jQuery solution:
let object = new ClassName();
let $elem = $('selector');
$elem.on('click', $.proxy(object.method, object));
$elem.off('click', $.proxy(object.method, object));
We had this problem with a library we could not change. Office Fabric UI, which meant we could not change the way event handlers were added. The way we solved it was to overwrite the addEventListener on the EventTarget prototype.
This will add a new function on objects element.removeAllEventListers("click")
(original post: Remove Click handler from fabric dialog overlay)
<script>
(function () {
"use strict";
var f = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, fn, capture) {
this.f = f;
this._eventHandlers = this._eventHandlers || {};
this._eventHandlers[type] = this._eventHandlers[type] || [];
this._eventHandlers[type].push([fn, capture]);
this.f(type, fn, capture);
}
EventTarget.prototype.removeAllEventListeners = function (type) {
this._eventHandlers = this._eventHandlers || {};
if (type in this._eventHandlers) {
var eventHandlers = this._eventHandlers[type];
for (var i = eventHandlers.length; i--;) {
var handler = eventHandlers[i];
this.removeEventListener(type, handler[0], handler[1]);
}
}
}
EventTarget.prototype.getAllEventListeners = function (type) {
this._eventHandlers = this._eventHandlers || {};
this._eventHandlers[type] = this._eventHandlers[type] || [];
return this._eventHandlers[type];
}
})();
</script>
Here is the solution:
var o = {
list: [1, 2, 3, 4],
add: function () {
var b = document.getElementsByTagName('body')[0];
b.addEventListener('click', this._onClick());
},
remove: function () {
var b = document.getElementsByTagName('body')[0];
b.removeEventListener('click', this._onClick());
},
_onClick: function () {
this.clickFn = this.clickFn || this._showLog.bind(this);
return this.clickFn;
},
_showLog: function (e) {
console.log('click', this.list, e);
}
};
// Example to test the solution
o.add();
setTimeout(function () {
console.log('setTimeout');
o.remove();
}, 5000);
As others have said, bind creates a new function instance and thus the event listener cannot be removed unless it is recorded in some way.
For a more beautiful code style, you can make the method function a lazy getter so that it's automatically replaced with the bound version when accessed for the first time:
class MyClass {
activate() {
window.addEventListener('click', this.onClick);
}
deactivate() {
window.removeEventListener('click', this.onClick);
}
get onClick() {
const func = (event) => {
console.log('click', event, this);
};
Object.defineProperty(this, 'onClick', {value: func});
return func;
}
}
If ES6 arrow function is not supported, use const func = (function(event){...}).bind(this) instead of const func = (event) => {...}.
Raichman Sergey's approach is also good, especially for classes. The advantage of this approach is that it's more self-complete and has no separated code other where. It also works for an object which doesn't have a constructor or initiator.
If you want to use 'onclick', as suggested above, you could try this:
(function(){
var singleton = {};
singleton = new function() {
this.myButton = document.getElementById("myButtonID");
this.myButton.onclick = function() {
singleton.clickListener();
};
}
singleton.clickListener = function() {
console.log(this); // I also know who I am
};
// public function
singleton.disableButton = function() {
this.myButton.onclick = "";
};
})();
I hope it helps.
can use about ES7:
class App extends React.Component {
constructor(props){
super(props);
}
componentDidMount (){
AppStore.addChangeListener(this.onChange);
}
componentWillUnmount (){
AppStore.removeChangeListener(this.onChange);
}
onChange = () => {
let state = AppStore.getState();
this.setState(state);
}
render() {
// ...
}
}
It's been awhile but MDN has a super explanation on this. That helped me more than the stuff here.
MDN :: EventTarget.addEventListener - The value of "this" within the handler
It gives a great alternative to the handleEvent function.
This is an example with and without bind:
var Something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // undefined, as this is the element
};
this.onclick2 = function(event) {
console.log(this.name); // 'Something Good', as this is the binded Something object
};
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2.bind(this), false); // Trick
}
A problem in the example above is that you cannot remove the listener with bind. Another solution is using a special function called handleEvent to catch any events:

object oriented javascript - this [function] is not a function

I am moving some jquery functions into a javascript object to clean up some code. My problem is, when I put methods on my object's constructor, calling this.functionName() returns the error this.functionName is not a function but if my functions are helper methods and are outside of the object's constructor, they work just fine.
Here is my code that does not work
function MyConstructor() {
this.init();
this.selectAllHandler();
}
MyConstructor.prototype = {
init: function() {
var self = this;
$(document).on('click', '#my_element', function() {
self.selectAllHandler.call(this);
});
},
selectAllHandler: function() {
// handler works fine
var ids_array = this.idsArray(checkboxes); // error happening here
},
// helpers
idsArray: function(checkboxes) {
// trying to call
}
}
But, having my object w/ a constructor and then calling the "helper" outside of the object works fine. For example, this works fine.
function MyConstructor() {
this.init();
}
MyConstructor.prototype = {
init: function() {
var self = this;
$(document).on('click', '#my_element', function() {
self.selectAllHandler.call(this);
});
},
selectAllHandler: function() {
// handler works fine
var ids_array = idsArray(checkboxes);
}
}
function idsArray() {
// code that works fine
}
One thing to note as well, is that in this scenario, by running console.log this refers to the element being clicked on, and not the constructor.
I have tried using call, apply, and bind, but have not had success, though I think it's been syntax related.
How can I build this so I can call my "helper" functions inside my object?
Not sure how you were using bind, since you said it didn't work for you.
If you want, you can use bind like below. Also, in your code snippet checkboxes was not defined. This way you don't need to use self.
function MyConstructor() {
this.init();
this.selectAllHandler();
}
MyConstructor.prototype = {
init: function() {
//var self = this;
$(document).on('click', '#my_element', function() {
//self.selectAllHandler.call(self);
this.selectAllHandler();
}.bind(this));
},
selectAllHandler: function() {
// handler works fine
var checkboxes;
var ids_array = this.idsArray(checkboxes); // error happening here
},
// helpers
idsArray: function(checkboxes) {
// trying to call
console.log('test');
}
}
var o = new MyConstructor();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I was able to figure it out. I thought I could call another function in the constructor just using this.functionName(). however, $(this) was referring to the element I was clicking on.
I remembered I defined self (this) in my init function which refers to the window object. Well, inside the window object is my object, and my function is on that object. So i was able to successfully call my object by doing
function MyConstructor() {
this.init();
}
MyConstructor.prototype = {
init: function() {
var self = this;
$(document).on('click', '#my_element', function() {
self.selectAllHandler.call(this);
});
},
selectAllHandler: function() {
// RIGHT HERE
var ids_array = self.MyConstructor.prototype.idsArray(checkboxes);
},
// helpers
idsArray: function(checkboxes) {
// some codes
}
}

About closure when using jQuery hover method?

My code:
function Demo (){
this.name = 'abc';
this.age = 20;
}
var demo = {
init : function(){
$('#test').hover(this.stop, this.start);
},
start: function(){
//here is the error
alert(this.name);
}
stop: function(){
alert(this.age); // 'this' does not mean the Demo object, but $('#test') object.
}
}
Demo.prototype = demo;
Demo.prototype.constructor = Demo;
(new Demo).init();
When the hover event of $('#test') is triggered, the stop method is called. However, 'this' in the method does not point to the demo object, but the $('#test') object. So, the alert is undefined. I need to have access to the attributes in the Demo object. And the stop and start method will be reused in other place, so I don not like to write the whole method code into hover's argument.
How should I solve this problem?
The this in the start and stop methods don't necessarily point to the same this as in the init. This is because they are callback functions. If you want to refer to the same object context then try the following:
var demo = {
init : function(){
$('#test').hover(this.stop.bind(this), this.start.bind(this));
},
start: function(){
//here is the error
alert(this.name);
}
stop: function(){
alert(this.age); // 'this' does not mean the Demo object, but $('#test') object.
}
}
Using bind will pass the this context through to the callbacks.
MDN docs for bind are here.
JQuery uses apply behind the scenes to call the event callbacks, that's why the context changes.
To mitigate this, you can do one of two things:
Use bind
var demo = {
init : function(){
$('#test').hover(this.stop.bind(this), this.start.bind(this));
},
start: function(){
alert(this.name);
}
stop: function(){
alert(this.age);
}
}
call the method directly
var demo = {
init : function(){
// Closure here
var self = this;
$('#test').hover(function() {
self.stop();
}, function() {
self.start();
});
},
start: function(){
alert(this.name);
}
stop: function(){
alert(this.age);
}
}
A None JSON implementation that will allow you to set the variable self
function Demo(){
this.name = 'abc';
this.age = 20;
}
Demo.prototype = new (function(){
var self = this;
this.init = function(){
$('#test').hover(self.stop, self.start);
}
this.start = function(){
//here is the error
alert(self.name);
}
this.stop = function(){
alert(self.age); // 'this' does not mean the Demo object, but $('#test') object.
}
})();
(new Demo()).init()
EDIT:
I have updated to show what i was meaning without the use of var demo = {...} the point i was trying to make was not to use the Object Literal aka JSON style so you could support a variable inside the prototype

How do I subscribe to a method in jQuery?

I would like to do something like this.
var foo = function() {
this.run = function() {
alert('got');
}
this.init = function() {
this.run();
}
this.init();
};
window.onload = function() {
var f = new foo();
$(f).bind('run', function() { // this doesn't work
alert('ran!');
});
};​
It doesn't work though. How can I subscribe to a method of another object?
You cannot bind event handlers directly to functions - you bind them to events! You will need to trigger a custom event in run():
this.run = function() {
// Trigger an event called "run"
$(this).triggerHandler('run');
// ...
};
Now you're able to subscribe to this event the way you already did:
var f = new foo();
$(f).on('run', function() { ... }); // "bind" is fine as well
This will work for events triggered after the handler has been bound, so the event triggered in the constructor will most likely not be caught.

Mootools class variable scope

Take this class:
var MyClass = new Class({
Implements: [Events, Options],
initialize: function() {
this.a = 1;
},
myMethod: function() {
var mc = new differentClass({
events: {
onClick: function() {
console.log(this.a); // undefined (should be 1, 2, 3 etc)
this.a ++;
}
}
});
}
});
How do I keep the value of this.a? I am basically trying to draw a line (using canvas) from the last point to the co-ordinates just clicked.
[EDIT]
I dont want to bind this as it's bad apparently and it will over-ride the differentClass options.
several patterns are available.
decorator via .bind()
var mc = new differentClass({
events: {
click: function() {
console.log(this.a);
this.a ++;
}.bind(this) // binds the scope of the function to the upper scope (myclass)
}
});
keeping a reference.
var self = this; // reference parent instance of myClass
var mc = new differentClass({
events: {
click: function() {
console.log(self.a);
self.a ++;
}
}
});
pointing to a myClass method that can deal with it:
handleClick: function() {
this.a++;
},
myMethod: function() {
var mc = new differentClass({
events: {
click: this.handleClick.bind(this)
}
});
}
the 2-nd one - by storing a reference is preferred due to the smaller footprint and universal support whereas .bind is not available in every browser and needs to be shimmed as well as the extra time to curry the function on execution.
self is what you will find in mootools-core itself when possible.
if performance is not at risk, method 3 can probably offer the best readability and code structure. the arguments to the method will remain what the click handler passes, i.e. event and event.target will be the handler.
in pattern #2 with self, this will point to the click handler within the anonymous function (or to the other class, for example), which may be useful as well - rebinding context can be a pain in the neck
You can refer proper context like this:
...
myMethod: function() {
var _this = this;
var mc = new differentClass({
events: {
onClick: function() {
console.log(_this.a);
_this.a ++;
}
}
});
}
...

Categories

Resources