I want to add and event listener, I want the function the event listener calls to be bound to the calling scope and I want to be able to remove the listener at some arbitrary date in the future.
The obvious thing doesn't work:
function Thing(){
this.thingINeed = "important!"
}
// the function that does the thing.
Thing.prototype.handlerFunction = function(e){
console.log(this.thingINeed)
e.preventDefault;
}
// do the binding.
window.document.body.addEventListener('click', this.handlerFunction.bind());
// sometime later...this is my best guess. The event listener remains active.
window.removeEventListener('click', this.handlerFunction.bind());
// this also doesn't work:
window.removeEventListener('click', this.handlerFunction);
So I flogged together some code that does work:
function Thing(){
this.thingINeed = "important!"
}
Thing.prototype.handlerFunction = function(e){
console.log(this.thingINeed);
e.preventDefault;
}
// Where the 'magic' happens...
this.boundHandlerFunction = this.handlerFunction.bind(this);
window.document.body.addEventListener('click', this.boundHandlerFunction);
// sometime later...
window.removeEventListener('click', this.boundHandlerFunction);
MDN goes into some detail on matching event listeners with removal, but it doesn't mention .bind() and I can't find any examples of other people doing it this way. The code isn't exactly obvious without extensive commenting.
Is there a better way?
jQuery event listeners can be named, which makes them really easy to remove, but that isn't possible with vanilla?
Thanks.
The issue boils down to - Function.prototype.bind returns a new function. It works when you set the bound function to a variable and use it in both addEventListener and removeEventListener because both are referencing the same function. The first block of code does not work because they are referencing different functions. Here is a contrived example:
function foo () {}
// does not work because foo.bind() returns a new function each time
// these functions are not the same object
document.addEventListener('click', foo.bind())
document.removeEventListener('click', foo.bind())
//does work because both reference the same function
var boundFoo = foo.bind()
document.addEventListener('click', boundFoo)
document.removeEventListener('click', boundFoo)
I can't speak much to how jQuery handles events under the hood, but there is no getting around this behavior in vanilla JS.
Related
I am trying run custom code whenever a click event is triggered. This is what I have so far:
const origHandler = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (eventName, eventHandler, options) {
let handler = eventHandler;
const target = this;
origHandler.call(this, eventName, function (e) {
// Do something with e
doSomething(e);
// Run original function
handler.call(target, e);
}, options);
};
I am also using this multi-select dropdown plugin. With the code above, clicking on the dropdown element doesn't do anything until I click it a couple of times.
It works fine if I just do the following:
origHandler.call(this, eventName, handler, options);
However, the above doesn't allow me to run custom code whenever the handler is called. Is there anything I can do to create a wrapper that also works with these types of plugins?
This problem is not specific to this plugin, as I have seen a few other plugins in the application also breaking as a result of this code.
Incomplete Algorithm
The posted code calls (the original) addEventListener with an anonymous function argument. This means any removeEventListener in calling code which supplies handler as its function argument will fail - it never matches the anonymous function.
To successfully add a hook into addEventListener would require implementing a complementary hook into removeEventListener and additional logic to achieve correct removal of added listeners.
This doesn't mean the particular problems encountered are specifically caused by only patching addEventListener, but doing so is guaranteed to produce code failure.
In general patching prototype object properties of global functions is probably best avoided if at all possible.
Alternative using Capture
Adding a document click event listener that uses event capture, before including any library scripts, should allow inspection of every click event before being handled by anything else:
document.addEventListener("click", function(e){
// do something with event
console.log("click event type: %s on %s", e.type, e.target.tagName);
}, {capture:true});
body {background-color: white}
html {background-color: grey}
Click me!
Just wondering, is this a valid way to define an explicit function inside JavaScript's addEventListener function so that it could be removed at any time using removeEventListener?
var somefunction;
window.addEventListener('load', somefunction = function(){
//do something
}, false);
window.removeEventListener('load', somefunction, false);
In other words, is it ok to define a variable somefunction and then assign an anonymous function to it inside addEventListener, instead of defining somefunction outright from the get go? It seems to work in FF and Chrome, but just wanna make sure this is officially valid JavaScript syntax.
Yes, it will work. An assignment is an expression -- it assigns to the variable and also returns the value that it assigned.
Personally I think this is a confusing way to write it. If you're refer to the function by name, put the definition where you define the name, not where you use it. In particular, if you try to do this twice, you'll have a problem because each event listener will have a different function, but it has the same name.
window.addEventListener('event1', somefunction = function() {
//do something
});
window.addEventListener('event2', somefunction = function() {
//do something
});
Now you can only remove event2, because somefunction no longer refers to the function that was added to event1.
Calling removeEventListener() with arguments that do not identify any
currently registered EventListener on the EventTarget has no effect.
So as long as removeEventListener has say a 'click' event as an argument, any one eventListener registered to the 'click' event will be removed. This is evident in OP's case, therefore it is feasible according to the criteria previously mentioned.
The following Snippet demonstrates a registered eventListener added to #target1 to listen for the 'click' event. It will be functional until removeEventListener() is called to remove the eventListener within 4 seconds. Notice that this particular removeEventListener's arguments are:
the event object............: click
a named function..........: eventLog()
and it's capture boolean: false
The identifying argument is 'click' and the target.event is #target that allows removeEventListener() to identify it's target.
SNIPPET
var eventLog;
var tgt1 = document.getElementById('target1');
var term = document.getElementById('btn');
tgt1.addEventListener('click', eventLog = function(e) {
console.log('target1 has been clicked');
}, false);
setTimeout(function() {
tgt1.removeEventListener('click', eventLog, false);
eventLog('Target1 eventListener is removed');
}, 4000);
function eventLog(str) {
console.log(str);
}
#target1 {
border: 2px solid red;
}
<p>Start clicking TARGET1 several times and you'll notice that each `click` event is firing as displayed in the console. Within 4 seconds, TARGET1's eventListener should be removed.</p>
<div id='target1'>TARGET1</div>
I've been breaking my head on the following. In the MDN Documentation on EventTarget.addEventListener() the following is stated:
Multiple identical event listeners
If multiple identical EventListeners are registered on the same
EventTarget with the same parameters, the duplicate instances are
discarded. They do not cause the EventListener to be called twice, and
since the duplicates are discarded, they do not need to be removed
manually with the removeEventListener method.
Which is a great thing. So I cannot add a duplicate EventListener on an element. But how do I find out if there is already an EventListener attached to another element somewhere in the DOM?
Say, I have an event called expandNavigationGroup, which is fired whenever my navigationpanel is clicked to open a navigationgroup. I can attach a listener for this event anywhere in my DOM. For instance:
var el = document.getElementById("somediv");
el.addEventListener("expandNavigationGroup", doSomething, false);
The doSomething method does something relevant, like cleaning something up. It needs to be done once, but only once, when a navigationgroup is opened. But if my code does the following:
var el = document.getElementById("someotherdiv");
el.addEventListener("expandNavigationGroup", doSomething, false);
The EventListener is added twice, and my cleanup code is executed twice.
How can I prevent this from happening?
#Chris is right. The only thing you could do is use some kind of "_.once" function.
Here's an implementation:
var once = (function (func) {
var wasExecuted = false
return function onceExecuter () {
if(wasExecuted) {
wasExecuted = true
return func.apply(null, arguments)
}
}
})()
Now you can do this:
var doSomethingOnce = once(doSomething)
doSomething()
doSomething()
And doSomething is executed once
According to MDN Documentation If multiple identical EventListeners are registered on the same EventTarget with the same parameters, the duplicate instances are discarded.
You are registering event on different targets somediv and someotherdiv and hence the cleanup code is executed twice.
Since you are not registering the EventListeners on the same EventTarget you would have to remove the EventListeners manually.
var el = document.getElementById("somediv");
el.addEventListener("expandNavigationGroup", doSomething, false);
el.removeEventListener('expandNavigationGroup', doSomething, false);
var el = document.getElementById("someotherdiv");
el.addEventListener("expandNavigationGroup", doSomething, false);
el.removeEventListener('expandNavigationGroup', doSomething, false);
So I've been going through Head First JavaScript and I came to a section on Event Handling with Function Literals. The book explains that you can wire all your event handling in your 'script' tags. But I am confused on how I get multiple functions to fire off on one event. Here's my code:
//Event Handling with Function Literals
window.onload = function(evt) {
//THIS IS BROKEN
document.body.onresize = resizeImg();reportImgHeight();
//Onload: Functions to Execute -- THESE WORK
resizeImg();
reportImgHeight();
}
So specifically for this example, how do I get an "onresize" event to execute BOTH resizeImg and reportImgHeight (functions which I have defined elsewhere in my code). Thank you!
The cleanest solution is to use addEventListener :
window.addEventListener('resize', resizeImg);
window.addEventListener('resize', reportImgHeight);
This way you can decouple both bindings.
Note also that you should bind the resize event to the window, not to a document part.
You will have to do this
document.body.onresize = function(){
resizeImg();
reportImgHeight();
};
And if you want to call them like they would be if they were separate you could do something like this
document.body.onresize = function(){
resizeImg.apply(this, arguments);
reportImgHeight.apply(this, arguments);
};
This passes through the this that you would have had if it were one, and the arguments passes through all of the arguments that were passed to the event.
I understand the difference between an addEventListener and the onclick property and know how to use both. I am wondering if there is a draw back to always using EventListener's instead of using the onclick property. The EventListener appears to be much more powerful than just using the onclick atleast when dynamically generating the HTML from javascript.
Is there a memory/cpu drawback or am I safe to only use EventListeners?
This probably isn't the direction you were going in, but there are a few instances where you would be unable to remove an event listener.
Event handlers are completely public, and can be modified (to a certain extent) by anyone:
// You do this
myLink.onclick = function () {
alert('hello, world');
};
// Another developer who hates you because
// he thinks that you're hitting on his girlfriend
// but you're not, you're just friends, but
// he's jealous so he doesn't understand
// does this
myLink.onclick = function () {
alert('muahahahaha');
};
// Someone else could even get rid of
// the handler entirely:
myLink.onclick = null;
But there is no publically accessible list of event listeners. The only way to remove an event listener is if you still have access to the original function:
myLink.addEventListener('click', function () {
alert('hello, world');
}, false);
There is now no way to remove that event listener. You gave it an anonymous function, so even you wouldn't be able to remove it if you wanted to.