How do I properly remove an event listener...
function createMaze() {
var x;
for (x = 0; x < 4; x++) {
var mazeBlock = document.createElement('div');
document.body.appendChild(mazeBlock);
mazeBlock.setAttribute('class', 'blockStyle');
mazeBlock.setAttribute('id', 'mazeBlock'+x);
mazeBlock.addEventListener( 'click', function(){ eventCall(this) } );
}
}
function eventCall(t) {
alert( t.id );
t.removeEventListener(); //...know that I'm missing something here.
// Also in my code, this remove will not happen here but be initiated somewhere else in the script.
}
I did bunch of digging and the top answer there suggest to add the listener to an object for easier removal but... I'm not sure how to accomplish that
While you could save a reference to the function you call addEventListener with so you can remove it:
for (let x = 0; x < 4; x++) {
const mazeBlock = document.createElement('div');
document.body.appendChild(mazeBlock);
mazeBlock.className = 'blockStyle';
mazeBlock.id = 'mazeBlock' + x;
mazeBlock.addEventListener('click', function handler() {
mazeBlock.removeEventListener('click', handler);
eventCall(mazeBlock);
});
}
(above, eventCall is called with the <div> as the first argument)
It would be easier to make sure the function can only be called once by passing { once: true } as a third argument to addEventListener:
mazeBlock.addEventListener( 'click', eventCall, { once: true });
(above, eventCall is called with the event as the first argument - to get to the <div>, access the .target of the argument)
If you need to remove listeners for all such elements, you might consider a different approach - rather than attaching lots of listeners and then removing them all, use event delegation instead. That way, all you have to do is remove the single delegated listener:
document.body.addEventListener('click', function handler(event) {
if (!event.target.matches('.blockStyle')) return;
// A block was clicked on, remove the listener:
document.body.removeEventListener('click', handler);
// Do stuff with the clicked element:
eventCall(event.target);
});
If you're forced by weird school rules to add listeners to each element, create the listener function outside the loop, then iterate over all elements and remove the listener from each when needed:
const handler = (event) => {
document.querySelectorAll('.blockStyle').forEach((div) => {
div.removeEventListener('click', handler);
});
// do stuff with event and event.target
};
...ended up doing this:
function createMaze() {
var x;
for (x = 0; x < 4; x++) {
const mazeBlock = document.createElement('div');
document.body.appendChild(mazeBlock);
mazeBlock.className = 'blockStyle';
mazeBlock.id = 'mazeBlock' + x;
mazeBlock.addEventListener( 'click', eventCall );
}
}
function eventCall() {
alert( this.id );
}
//...this is called from another piece of the script on a separate occasion
function removeListeners() {
var blocks = document.getElementsByClassName('blockStyle');
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
block.removeEventListener( 'click', eventCall );
}
}
#CertainPerformance Thanks for all your help! :)
I'm trying to remove an event listener after a function has been called. But the event listener for "keyup" stays attached to the body, no matter what I try. What is wrong with the code?
function displayImage() {
//this is a simplified version of the code
var outerFrame = document.createElement('div');
outerFrame.className = 'popup-outer';
document.body.appendChild(outerFrame);
document.body.addEventListener('keyup', hideImage.bind(outerFrame), false);
}
function hideImage(e) {
if (e.keyCode === 27) {
// this doesn't work, it stays attached to the body element
document.body.removeEventListener('keyup', hideImage, false);
document.body.removeChild(this);
}
e.preventDefault();
}
It's because technically
hideImage.bind(outerFrame)
is different from
hideImage
because the first one returns a copy of the function hideImage.
So when you try to unbind hideImage, the event manager does not find it because it registred a copy of it and thus nothing is removed :-/.
EDIT :
In your case, I guess you have no other choice but keeping track of your listeners. I went ahead and made this quickly, it should fix your problem.
var listeners = {};
function createDiv() {
var outerFrame = document.createElement('div');
outerFrame.className = 'popup-outer';
return outerFrame;
}
function displayImage() {
var div = createDiv();
bindEvent(div);
document.body.appendChild(div);
}
function bindEvent(el) {
var handler = function(e) {
hideImg.call(el, e);
}
listeners[el] = handler;
document.body.addEventListener('keyup', handler, false);
}
function hideImg(e) {
if (e.keyCode === 27) {
// listeners[this] refers to the "private" handler variable we created in the bindEvent function
document.body.removeEventListener('keyup', listeners[this], false);
delete listeners[this];
document.body.removeChild(this);
}
}
I am implementing a JS Event-Disabler class, to disable all Native and Programmable eventlisteners of a certain dom element and all its children.
So far I've been able to disable all JQuery events and the default browser events, but not the eventlisteners set like
document.getElementById('cin').addEventListener("click", function(){
alert('I should not alert when disabled');
});
So clicking on the element ('native element') shouldn't alert, but it does.
How do I stop that from happening, within my nothing function.
If there is away to not even need to call another function but just disable all events then that would also be fine, but need to be able to re-enable all again.
Also, I can assure you that the nothing() function executes first.
var tellme = function(who) {
//console.info('Event by: '+who+' #'+Date.now());
alert('Event by: ' + who + ' #' + Date.now());
}
$(window).load(function() {
/* SOME FUNCTION TO ENSURE OUR FUNCTIONS ARE THE FIRST TO BE CALLED */
$.fn.bindFirst = function(name, fn) {
this.on(name, fn);
this.each(function() {
var handlers = $._data(this, 'events');
for (var key in handlers) {
if (handlers.hasOwnProperty(key)) {
var listeners = handlers[key];
if (listeners.length > 1) {
var lastEvent = listeners.pop();
listeners.splice(0, 0, lastEvent);
if (listeners[1].handler.name === lastEvent.handler.name)
listeners.splice(1, 1);
}
}
}
});
};
function shouldbenothing() {
tellme('native catcher');
nothing();
}
/* THE DO NOTHING FUNCTION, NEEDS SOMETHING MORE, DOESN'T CANCEL ALL*/
function nothing() {
event.cancel = true;
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
//Needed for Jquery
throw new Error("NOT AN ERROR: Just forcefully stopping further events #" /*+Date.now()*/ ); //Add the Date.now to see that this code does run before the native function.
return false;
}
/* THIS WILL ONLY RETURN NON-NATIVE EVENTS, ONLY PROGRAMMED EVENTS*/
function getAllActiveEvents(element) {
var result = [];
var handlers = $._data(element, 'events');
for (var key in handlers) {
if (handlers.hasOwnProperty(key)) {
result.push(key);
}
}
return result.join(' ');
}
function getAllEvents(element) {
var result = [];
for (var key in element) {
if (key.indexOf('on') === 0) {
result.push(key.slice(2));
}
}
return result.join(' ');
}
/*SOME PROGRAMMED EVENTS, BESIDES THE NATIVE ONES*/
$('input').on('keyup', function() {
$('#text').html(this.value);
});
$('p').on('click', function() {
$('#text').html(this.innerHTML);
tellme('jquery');
});
document.getElementById('jsE').addEventListener("click", function() {
tellme('p:js');
});
document.getElementById('cin').addEventListener("click", function() {
tellme('input:js');
});
/* THE ACTUAL DISABLER CODE */
/*TOGGLE TO ACTIVE OR DISABLE EVENTS FROM TAKING PLACE NATIVE AND EXTRA*/
var isOn = false;
$('button').on('click', function() {
if (isOn)
$("#obj *").each(function() {
$(this).off(getAllEvents($(this)[0]), "", nothing);
$("#obj").css('pointerEvents','');
});
else {
$("#obj *").each(function() {
var elem = $(this)[0];
var events1 = getAllActiveEvents(elem); //Only programmed listeners
var events2 = getAllEvents(elem); //Native + other listeners
$(this).bindFirst(events2, nothing);
});
$("#obj").css('pointerEvents','none');
}
isOn = !isOn;
this.innerHTML = isOn;
});
});
p {
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<style>p {pointer:hand;}</style>
<div id="obj">
<p>jquery event</p>
<p id="jsE">js event</p>
<p onclick="tellme('native');">native event</p>
<input id='cin' type="text" />
<p id="text">3</p>
</div>
<p>not catched</p>
<input type="text">
<button>toggle</button>
There might be a very simple, non-js, pure css-solution ... like this:
.whatever {
-webkit-user-select:none;
-moz-user-select:none;
-ms-user-select:none;
user-select:none;
pointer-events:none;
}
... just add the whatever-class to any elements you want to disable completely from user-interaction.
So I found a solution shortly after.
By playing with the css code, I could disable all the relevant mouse events. This however doesn't stop the native events, say if you were to trigger the event via JS, but at least it stops it from user's point.
I actually also like the css method better, as it does allow me to still interact and trigger events, for instance when I want to show the user something without having the user interfere.
The css code:
//To Disable
$("#obj").css('pointerEvents','none');
//To Enable
$("#obj").css('pointerEvents','');
For anyone looking for the full Working Code: Here it is.
Make sure you add the css.
/* Event Disabler, disables all events */
/* How to use:
* Toggle Events: toggleEvents(selector);
* Disable all Events: toggleEvents('body',true);
* Enable all Events: toggleEvents('body',false);
*/
var toggleEvents = null;
$(window).load(function(){
/* SOME FUNCTION TO ENSURE OUR FUNCTIONS ARE THE FIRST TO BE CALLED */
$.fn.bindFirst = function(name, fn) {
this.on(name, fn);
this.each(function() {
var handlers = $._data(this, 'events');
for (var key in handlers) {
if (handlers.hasOwnProperty(key)) {
var listners = handlers[key];
if (listners.length > 1) {
var lastEvent = listners.pop();
listners.splice(0, 0, lastEvent);
//Removes duplicate eventListners
if (listners[1].handler.name === lastEvent.handler.name)
listners.splice(1, 1);
}
}
}
});
};
/* THE DO NOTHING FUNTION CANCELS ALL EVENTS, EVEN BY TRIGGERED*/
function nothing() {
event.cancel = true;
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
event.bubbles = false;
if(window.event){
window.event.cancelBubble=true;
}
//throw new Error("NOT AN ERROR: Forcefully stopping further events");
return false;
}
function getAllActiveEvents(element) {
var result = [];
var handlers = $._data(element, 'events');
for (var key in handlers) {
if (handlers.hasOwnProperty(key)) {
result.push(key);
}
}
return result.join(' ');
}
function getAllEvents(element) {
var result = [];
for (var key in element) {
if (key.indexOf('on') === 0) {
result.push(key.slice(2));
}
}
return result.join(' ');
}
var enabled = false;
toggleEvents = function(selector,flag) {
enabled = flag === undefined ? !enabled : flag;
if (enabled) {
$(selector+" *").each(function(){
//Only programmed and attached listners
var events1 = getAllActiveEvents($(this)[0]);
//All Native events attached or not
var events2 = getAllEvents($(this)[0]);
$(this).bindFirst(events2, nothing );
});
//Disabled most user pointer events
$(selector).addClass('eventsDisabled');
} else {
$(selector+" *").each(function() {
$(this).off(getAllEvents($(this)[0]), "", nothing );
});
$(selector).removeClass('eventsDisabled');
}
};
});
.eventsDisabled {
-webkit-user-select:none !important;
-moz-user-select:none !important;
-ms-user-select:none !important;
user-select:none !important;
pointer-events:none !important;
}
So my dilemma is that I don't want to write the same code twice. Once for the click event and another for the touchstart event.
Here is the original code:
document.getElementById('first').addEventListener('touchstart', function(event) {
do_something();
});
document.getElementById('first').addEventListener('click', function(event) {
do_something();
});
How can I compact this? There HAS to be a simpler way!
I thought some might find this approach useful; it could be applied to any similarly repetitive code:
ES6
['click','ontouchstart'].forEach( evt =>
element.addEventListener(evt, dosomething, false)
);
ES5
['click','ontouchstart'].forEach( function(evt) {
element.addEventListener(evt, dosomething, false);
});
You can just define a function and pass it. Anonymous functions are not special in any way, all functions can be passed around as values.
var elem = document.getElementById('first');
elem.addEventListener('touchstart', handler, false);
elem.addEventListener('click', handler, false);
function handler(event) {
do_something();
}
Maybe you can use a helper function like this:
// events and args should be of type Array
function addMultipleListeners(element,events,handler,useCapture,args){
if (!(events instanceof Array)){
throw 'addMultipleListeners: '+
'please supply an array of eventstrings '+
'(like ["click","mouseover"])';
}
//create a wrapper to be able to use additional arguments
var handlerFn = function(e){
handler.apply(this, args && args instanceof Array ? args : []);
}
for (var i=0;i<events.length;i+=1){
element.addEventListener(events[i],handlerFn,useCapture);
}
}
function handler(e) {
// do things
};
// usage
addMultipleListeners(
document.getElementById('first'),
['touchstart','click'],
handler,
false);
[Edit nov. 2020] This answer is pretty old. The way I solve this nowadays is by using an actions object where handlers are specified per event type, a data-attribute for an element to indicate which action should be executed on it and one generic document wide handler method (so event delegation).
const firstElemHandler = (elem, evt) =>
elem.textContent = `You ${evt.type === "click" ? "clicked" : "touched"}!`;
const actions = {
click: {
firstElemHandler,
},
touchstart: {
firstElemHandler,
},
mouseover: {
firstElemHandler: elem => elem.textContent = "Now ... click me!",
outerHandling: elem => {
console.clear();
console.log(`Hi from outerHandling, handle time ${
new Date().toLocaleTimeString()}`);
},
}
};
Object.keys(actions).forEach(key => document.addEventListener(key, handle));
function handle(evt) {
const origin = evt.target.closest("[data-action]");
return origin &&
actions[evt.type] &&
actions[evt.type][origin.dataset.action] &&
actions[evt.type][origin.dataset.action](origin, evt) ||
true;
}
[data-action]:hover {
cursor: pointer;
}
<div data-action="outerHandling">
<div id="first" data-action="firstElemHandler">
<b>Hover, click or tap</b>
</div>
this is handled too (on mouse over)
</div>
For large numbers of events this might help:
var element = document.getElementById("myId");
var myEvents = "click touchstart touchend".split(" ");
var handler = function (e) {
do something
};
for (var i=0, len = myEvents.length; i < len; i++) {
element.addEventListener(myEvents[i], handler, false);
}
Update 06/2017:
Now that new language features are more widely available you could simplify adding a limited list of events that share one listener.
const element = document.querySelector("#myId");
function handleEvent(e) {
// do something
}
// I prefer string.split because it makes editing the event list slightly easier
"click touchstart touchend touchmove".split(" ")
.map(name => element.addEventListener(name, handleEvent, false));
If you want to handle lots of events and have different requirements per listener you can also pass an object which most people tend to forget.
const el = document.querySelector("#myId");
const eventHandler = {
// called for each event on this element
handleEvent(evt) {
switch (evt.type) {
case "click":
case "touchstart":
// click and touchstart share click handler
this.handleClick(e);
break;
case "touchend":
this.handleTouchend(e);
break;
default:
this.handleDefault(e);
}
},
handleClick(e) {
// do something
},
handleTouchend(e) {
// do something different
},
handleDefault(e) {
console.log("unhandled event: %s", e.type);
}
}
el.addEventListener(eventHandler);
Update 05/2019:
const el = document.querySelector("#myId");
const eventHandler = {
handlers: {
click(e) {
// do something
},
touchend(e) {
// do something different
},
default(e) {
console.log("unhandled event: %s", e.type);
}
},
// called for each event on this element
handleEvent(evt) {
switch (evt.type) {
case "click":
case "touchstart":
// click and touchstart share click handler
this.handlers.click(e);
break;
case "touchend":
this.handlers.touchend(e);
break;
default:
this.handlers.default(e);
}
}
}
Object.keys(eventHandler.handlers)
.map(eventName => el.addEventListener(eventName, eventHandler))
Unless your do_something function actually does something with any given arguments, you can just pass it as the event handler.
var first = document.getElementById('first');
first.addEventListener('touchstart', do_something, false);
first.addEventListener('click', do_something, false);
Simplest solution for me was passing the code into a separate function and then calling that function in an event listener, works like a charm.
function somefunction() { ..code goes here ..}
variable.addEventListener('keyup', function() {
somefunction(); // calling function on keyup event
})
variable.addEventListener('keydown', function() {
somefunction(); //calling function on keydown event
})
I have a small solution that attaches to the prototype
EventTarget.prototype.addEventListeners = function(type, listener, options,extra) {
let arr = type;
if(typeof type == 'string'){
let sp = type.split(/[\s,;]+/);
arr = sp;
}
for(let a of arr){
this.addEventListener(a,listener,options,extra);
}
};
Allows you to give it a string or Array. The string can be separated with a space(' '), a comma(',') OR a Semicolon(';')
I just made this function (intentionally minified):
((i,e,f)=>e.forEach(o=>i.addEventListener(o,f)))(element, events, handler)
Usage:
((i,e,f)=>e.forEach(o=>i.addEventListener(o,f)))(element, ['click', 'touchstart'], (event) => {
// function body
});
The difference compared to other approaches is that the handling function is defined only once and then passed to every addEventListener.
EDIT:
Adding a non-minified version to make it more comprehensible. The minified version was meant just to be copy-pasted and used.
((element, event_names, handler) => {
event_names.forEach( (event_name) => {
element.addEventListener(event_name, handler)
})
})(element, ['click', 'touchstart'], (event) => {
// function body
});
I'm new at JavaScript coding, so forgive me if I'm wrong.
I think you can create an object and the event handlers like this:
const myEvents = {
click: clickOnce,
dblclick: clickTwice,
};
function clickOnce() {
console.log("Once");
}
function clickTwice() {
console.log("Twice");
}
Object.keys(myEvents).forEach((key) => {
const myButton = document.querySelector(".myButton")
myButton.addEventListener(key, myEvents[key]);
});
<h1 class="myButton">Button</h1>
And then click on the element.
document.getElementById('first').addEventListener('touchstart',myFunction);
document.getElementById('first').addEventListener('click',myFunction);
function myFunction(e){
e.preventDefault();e.stopPropagation()
do_something();
}
You should be using e.stopPropagation() because if not, your function will fired twice on mobile
This is my solution in which I deal with multiple events in my workflow.
let h2 = document.querySelector("h2");
function addMultipleEvents(eventsArray, targetElem, handler) {
eventsArray.map(function(event) {
targetElem.addEventListener(event, handler, false);
}
);
}
let counter = 0;
function countP() {
counter++;
h2.innerHTML = counter;
}
// magic starts over here...
addMultipleEvents(['click', 'mouseleave', 'mouseenter'], h2, countP);
<h1>MULTI EVENTS DEMO - If you click, move away or enter the mouse on the number, it counts...</h1>
<h2 style="text-align:center; font: bold 3em comic; cursor: pointer">0</h2>
What about something like this:
['focusout','keydown'].forEach( function(evt) {
self.slave.addEventListener(evt, function(event) {
// Here `this` is for the slave, i.e. `self.slave`
if ((event.type === 'keydown' && event.which === 27) || event.type === 'focusout') {
this.style.display = 'none';
this.parentNode.querySelector('.master').style.display = '';
this.parentNode.querySelector('.master').value = this.value;
console.log('out');
}
}, false);
});
// The above is replacement of:
/* self.slave.addEventListener("focusout", function(event) { })
self.slave.addEventListener("keydown", function(event) {
if (event.which === 27) { // Esc
}
})
*/
You can simply do it iterating an Object. This can work with a single or multiple elements. This is an example:
const ELEMENTS = {'click': element1, ...};
for (const [key, value] of Object.entries(ELEMENTS)) {
value.addEventListener(key, () => {
do_something();
});
}
When key is the type of event and value is the element when you are adding the event, so you can edit ELEMENTS adding your elements and the type of event.
Semi-related, but this is for initializing one unique event listener specific per element.
You can use the slider to show the values in realtime, or check the console.
On the <input> element I have a attr tag called data-whatever, so you can customize that data if you want to.
sliders = document.querySelectorAll("input");
sliders.forEach(item=> {
item.addEventListener('input', (e) => {
console.log(`${item.getAttribute("data-whatever")} is this value: ${e.target.value}`);
item.nextElementSibling.textContent = e.target.value;
});
})
.wrapper {
display: flex;
}
span {
padding-right: 30px;
margin-left: 5px;
}
* {
font-size: 12px
}
<div class="wrapper">
<input type="range" min="1" data-whatever="size" max="800" value="50" id="sliderSize">
<em>50</em>
<span>Size</span>
<br>
<input type="range" min="1" data-whatever="OriginY" max="800" value="50" id="sliderOriginY">
<em>50</em>
<span>OriginY</span>
<br>
<input type="range" min="1" data-whatever="OriginX" max="800" value="50" id="sliderOriginX">
<em>50</em>
<span>OriginX</span>
</div>
//catch volume update
var volEvents = "change,input";
var volEventsArr = volEvents.split(",");
for(var i = 0;i<volknob.length;i++) {
for(var k=0;k<volEventsArr.length;k++) {
volknob[i].addEventListener(volEventsArr[k], function() {
var cfa = document.getElementsByClassName('watch_televised');
for (var j = 0; j<cfa.length; j++) {
cfa[j].volume = this.value / 100;
}
});
}
}
'onclick' in the html works for both touch and click event. Here's the example.
This mini javascript libary (1.3 KB) can do all these things
https://github.com/Norair1997/norjs/
nor.event(["#first"], ["touchstart", "click"], [doSomething, doSomething]);
I have:
if (window.addEventListener) {
window.addEventListener('load',videoPlayer,false);
}
else if (window.attachEvent) {
window.attachEvent('onload',videoPlayer);
}
and then later I have:
if (window.addEventListener) {
window.addEventListener('load',somethingelse,false);
} else if (window.attachEvent) {
window.attachEvent('onload',somethingelse);
}
Is it preferred/functional to have them all together? Like
if (window.addEventListener) {
window.addEventListener('load',videoPlayer,false);
window.addEventListener('load',somethingelse,false);
} else if (window.attachEvent) {
window.attachEvent('onload',videoPlayer,false);
window.attachEvent('onload',somethingelse);
}
You can do how ever you want it to do. They don't have to be together, it depends on the context of the code. Of course, if you can put them together, then you should, as this probably makes the structure of your code more clear (in the sense of "now we are adding all the event handlers").
But sometimes you have to add event listeners dynamically. However, it is unnecessary to test multiple times whether you are dealing with IE or not.
Better would be to abstract from this and test only once which method is available when the page is loaded. Something like this:
var addEventListener = (function() {
if(document.addEventListener) {
return function(element, event, handler) {
element.addEventListener(event, handler, false);
};
}
else {
return function(element, event, handler) {
element.attachEvent('on' + event, handler);
};
}
}());
This will test once which method to use. Then you can attach events throughout your script with:
addEventListener(window, 'load',videoPlayer);
addEventListener(window, 'load',somethingelse);
I use this function:
function addEvent (obj, type, fn) {
if (obj.addEventListener) {
obj.addEventListener(type, fn, false);
} else if (obj.attachEvent) {
obj.attachEvent('on' + type, function () {
return fn.call(obj, window.event);
});
}
}
/**
* multipleEventsListeners.js
* Add the capability to attach multiple events to an element, just like jQuery does
* https://gist.github.com/juanbrujo/a1f77db1e6f7cb17b42b
*/
multipleEventsListeners(events, func, elem) {
elem = elem || document;
var event = events.split(' ');
for (var i = 0; i < event.length; i++) {
elem.addEventListener(event[i], func, false);
}
}
/*
Use:
var input = document.querySelector('input');
multipleEventsListeners(input, 'keyup change', function(e){
console.log = this.value;
});
*/
from: https://gist.github.com/juanbrujo/a1f77db1e6f7cb17b42b
by using a named function and passing that into your event listener, you can avoid having to write the same code over and over again.
// Setup our function to run on various events
var someFunction = function (event) {
// Do something...
};
// Add our event listeners
window.addEventListener('click', someFunction, false);
window.addEventListener('mouseover', someFunction, false);
addEventListener automatically passes the event object into your function as an