How do I subscribe to a method in jQuery? - javascript

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.

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
}
}

Node.js - Listen for when listeners are registered

In Node.js, we have the standard event emitter:
const EE = require('events');
const ee = new EE();
ee.on('x', function(){
});
what I would like to do, is listen for when a client registers a listener. The purpose is so that if the emitter is in a particular state, it will take actions upon registration.
To be clear, when ee.on() is called, for a particular event 'x', I want to take some actions.
How can I do this without monkey-patching all event emitters in Node.js?
If I do the following, am I patching the prototype method or the instance method?
let on = ee.on;
ee.on = function(){
if(ee.y === true){
process.next(function(){
ee.emit('done');
});
}
return on.apply(ee, arguments);
};
this is of course the same as:
let on = ee.on;
ee.on = function(){
if(this.y === true){
process.next(() => {
this.emit('done');
});
}
return on.apply(this, arguments);
};
I think this will work, but I am not sure. I think this will only effect this one particular event emitter, not all event emitters, because it will not change the prototype...
You can listen for a newListener event:
The EventEmitter instance will emit its own 'newListener' event before a listener is added to its internal array of listeners.
See EventEmitter.newListener.
To have the same behavior replicated you should create a new class/prototype from where all other instances will inherit.
EDIT:
<file_my_class.js>:
var Util = require('util');
const EventEmmiter = require('events');
// private data goes here
...
// public data follows
module.exports = MyClass;
function MyClass() {
...
}
Util.inherits (MyClass, EventEmmiter);
MyClass.prototype.mymethod = function () {
someobj.on('somevent', () => {
...
if (test_condition_ok) {
this.emit ('myeventname');
}
});
}
<main.js>:
var MyClass = require('./file_my_class.js');
var myclass_instance = new MyClass();
myclass_instance.mymethod();
myclass_instance.on('myeventname', () => {
// Do Something
});

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

// 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!

How to use keyword this in a mouse wrapper in right context in Javascript?

I'm trying to write a simple wrapper for mouse behaviour. This is my current code:
function MouseWrapper() {
this.mouseDown = 0;
this.OnMouseDownEvent = null;
this.OnMouseUpEvent = null;
document.body.onmousedown = this.OnMouseDown;
document.body.onmouseup = this.OnMouseUp;
}
MouseWrapper.prototype.Subscribe = function (eventName, fn) {
// Subscribe a function to the event
if (eventName == 'MouseDown') {
this.OnMouseDownEvent = fn;
} else if (eventName == 'MouseUp') {
this.OnMouseUpEvent = fn;
} else {
alert('Subscribe: Unknown event.');
}
}
MouseWrapper.prototype.OnMouseDown = function () {
this.mouseDown = 1;
// Fire event
$.dump(this.OnMouseDownEvent);
if (this.OnMouseDownEvent != null) {
alert('test');
this.OnMouseDownEvent();
}
}
MouseWrapper.prototype.OnMouseUp = function () {
this.mouseDown = 0;
// Fire event
if (this.OnMouseUpEvent != null) {
this.OnMouseUpEvent();
}
}
From what I gathered it seems that in MouseWrapper.prototype.OnMouseUp and MouseWrapper.prototype.OnMouseDown the keyword "this" doesn't mean current instance of MouseWrapper but something else. And it makes sense that it doesn't point to my instance but how to solve the problem?
I want to solve the problem properly I don't want to use something dirty.
My thinking:
* use a singleton pattern (mouse is only one after all)
* pass somehow my instance to OnMouseDown/Up - how?
Thank you for help!
The above solution is fine but a more re-usable way is to "bind" your method to a context by creating a method that creates that closure for you. For example
Function.bind = function(method, context) {
return function() {
return method.apply(context, arguments);
}
}
Following Slaks's example, you would have:
document.body.onmousedown = Function.bind(this.OnMouseDown,this);
document.body.onmouseup = Function.bind(this.OnMouseUp, this);
or you could do it to Function.prototype for syntactic sugar.
Function.prototype.bind = function(context) {
return function() {
// since this is a prototype method, "this" is the method to be called
return this.apply(context, arguments);
}
}
document.body.onmousedown = this.OnMouseDown.bind(this);
document.body.onmouseup = this.OnMouseUp.bind(this);
You could also bind arguments to be called as well as the context... ask if you're interested.
The singleton approach, though it works, is a hack. It's adding globals instead of properly addressing the issues at hand. It means if you needed the behavior in two places, it wouldn't work.
In Javascript, unlike most languages, the value of the this keyword is determined when the function is called.
For example:
var dumper = function() { alert(this); };
var str1 = "A";
str1.dump = dumper;
str1.dump(); //Alerts A
var str2 = "B";
str2.dump = str1.dump;
str2.dump(); //Alerts B
When the browser calls your event handler, it calls it in the context of the DOM element, so this isn't what you think it is.
To work around this, you need to use an anonymous method that calls your handlers in the right context.
For example:
var self = this;
document.body.onmousedown = function(e) { return self.OnMouseDown(e); };
document.body.onmouseup = function(e) { return self.OnMouseUp(e); };
Also, you should not handle events like this.
Instead, call attachEvent / addEventListener.

Categories

Resources