function doSomethingWith(param)
{
document.body.addEventListener(
'scroll',
function()
{
document.write(param);
},
false
); // An event that I want to remove later
}
setTimeout(
function()
{
document.body.removeEventListener('scroll', HANDLER ,false);
// What HANDLER should I specify to remove the anonymous handler above?
},
3000
);
doSomethingWith('Test. ');
You can't. You have to use a named function or store the reference somehow.
var handler;
function doSomethingWith(param) {
handler = function(){
document.write(param);
};
document.body.addEventListener('scroll', handler,false);
}
setTimeout(function() {
document.body.removeEventListener('scroll', handler ,false);
}, 3000);
The best would be to do this in a structured way, so that you can identify different handlers and remove them. In the example above, you obviously could only remove the last handler.
Update:
You could create your own handler handler (:)) :
var Handler = (function(){
var i = 1,
listeners = {};
return {
addListener: function(element, event, handler, capture) {
element.addEventListener(event, handler, capture);
listeners[i] = {element: element,
event: event,
handler: handler,
capture: capture};
return i++;
},
removeListener: function(id) {
if(id in listeners) {
var h = listeners[id];
h.element.removeEventListener(h.event, h.handler, h.capture);
delete listeners[id];
}
}
};
}());
Then you can use it with:
function doSomethingWith(param) {
return Handler.addListener(document.body, 'scroll', function() {
document.write(param);
}, false);
}
var handler = doSomethingWith('Test. ');
setTimeout(function() {
Handler.removeListener(handler);
}, 3000);
i
and listeners
"private". –
Delorasdelorenzo You can't, you need a reference to the function:
function doSomethingWith(param) {
var fn = function(){ document.write(param); };
document.body.addEventListener('scroll', fn, false);
setTimeout(function(){ document.body.removeEventListener('scroll', fn, false); }, 3000);
}
doSomethingWith('Test. ');
var fn = function(event){ document.write(param); };
–
Fellow You could also do this like that:
const ownAddEventListener = (scope, type, handler, capture) => {
scope.addEventListener(type, handler, capture);
return () => {
scope.removeEventListener(type, handler, capture);
}
}
Then you can remove the event listener like this:
// Add event listener
const disposer = ownAddEventListener(document.body, 'scroll', () => {
// do something
}, false);
// Remove event listener
disposer();
const disposerFn = () => { scope.removeEventListener(type, handler, capture); }
disposerFn.type = type;
return disposerFn;
–
Hermaphroditus This significantly improves upon the top @FelixKling answer. Improvements are:
(1) Aligned with MDN docs which refer to type, listener, useCapture
(2) Removes old school IIFE and adopts a class based approach
(3) Added addEventListenerById
method which supports using your own custom ID (and avoids the need to retrieve ID from return value)
(4) removeEventListener
method returns ID of removed listener or null
(5) Added length
method to return the number of active listeners
(6) Added SO code snippet (to prove it works)
Code follows:
class Listeners {
#listeners = {} // # in a JS class signifies private
#idx = 1
// add event listener, returns integer ID of new listener
addEventListener(element, type, listener, useCapture = false) {
this.#privateAddEventListener(element, this.#idx, type, listener, useCapture)
return this.#idx++
}
// add event listener with custom ID (avoids need to retrieve return ID since you are providing it yourself)
addEventListenerById(element, id, type, listener, useCapture = false) {
this.#privateAddEventListener(element, id, type, listener, useCapture)
return id
}
#privateAddEventListener(element, id, type, listener, useCapture) {
if (this.#listeners[id]) throw Error(`A listener with id ${id} already exists`)
element.addEventListener(type, listener, useCapture)
this.#listeners[id] = {element, type, listener, useCapture}
}
// remove event listener with given ID, returns ID of removed listener or null (if listener with given ID does not exist)
removeEventListener(id) {
const listen = this.#listeners[id]
if (listen) {
listen.element.removeEventListener(listen.type, listen.listener, listen.useCapture)
delete this.#listeners[id]
}
return !!listen ? id : null
}
// returns number of events listeners
length() {
return Object.keys(this.#listeners).length
}
}
// For demo purposes only, a button to which the click listeners will be attached
const testBtn = document.querySelector('.test-button')
// Usage
const listeners = new Listeners() // in a modular environment ... export const listeners = new Listeners()
const listener1 = listeners.addEventListener(testBtn, 'click', e => console.log('hello from click listener #1', e.offsetX, e.offsetY))
const listener2 = listeners.addEventListener(testBtn, 'click', e => console.log('hello from click listener #2', e.offsetX, e.offsetY))
listeners.addEventListenerById(testBtn, 'listener-id', 'click', e => console.log('hello from click listener #listener-id', e.offsetX, e.offsetY))
// Click function for the 3 remove listener buttons (not for the testBtn to which the listeners are attached)
const onRemoveClick = (id) => {
const removed = listeners.removeEventListener(id)
if (removed == null) {
console.log(`cannot remove listener #${id} (does not exist)`)
} else {
console.log(`ID of removed listener #${removed}`)
}
const listenersCount = listeners.length()
console.log(`there are ${listenersCount} listeners still listening`)
}
.test-button {
width: 35%;
height: 40px;
float: left;
}
.remove-listener-button {
width: 15%;
height: 40px;
float: left;
}
<button class="test-button">click to prove multiple listeners are listening to my click</button>
<button class="remove-listener-button" onclick="onRemoveClick(1)">remove listener #1</button>
<button class="remove-listener-button" onclick="onRemoveClick(2)">remove listener #2</button>
<button class="remove-listener-button" onclick="onRemoveClick('listener-id')">remove #listener-id</button>
FOR TYPESCRIPT LOVERS
Here's the same in TypeScript with some enhancements to the above JS version:
(1) removeEventListener
method returns details of the removed listener (including ID) as opposed to only the ID of the removed listener
(2) Added ids
method which returns the ids of all active listeners
(3) Added 3 methods addEventListeners
addEventListenersByIds
removeEventListeners
which allow you to add / remove multiple listeners in one call (see usage example below)
(4) Added removeAllEventListeners
and destroy
methods for cleanup (essentially these 2 are the same but the latter does not return a value)
Here's the TypeScript code (just copy and paste into a new .ts file):
interface IInternalListener {
element: HTMLElement
id: string
type: string
listener: EventListenerOrEventListenerObject
useCapture: boolean
}
export interface IListener extends Omit<IInternalListener, 'id'> {
id: string | number
}
class Listeners {
#listeners: { [key: string]: IInternalListener } = {}
#idx = 1 // # in a JS class signifies private
// add event listener, returns integer ID of new listener
addEventListener(element: HTMLElement, type: string, listener: EventListenerOrEventListenerObject, useCapture = false): number {
this.#privateAddEventListener(element, this.#idx.toString(), type, listener, useCapture)
return this.#idx++
}
addEventListeners(element: HTMLElement, types: string[], listener: EventListenerOrEventListenerObject, useCapture = false): number[] {
const returnIds: number[] = []
types.forEach((type: string) => {
const returnId: number = this.addEventListener(element, type, listener, useCapture)
returnIds.push(returnId)
})
return returnIds
}
// add event listener with custom ID (avoids need to retrieve return ID since you are providing it yourself)
addEventListenerById(element: HTMLElement, id: string | number, type: string, listener: EventListenerOrEventListenerObject, useCapture = false): string | number { // eslint-disable-line max-len
return this.#privateAddEventListener(element, id.toString(), type, listener, useCapture)
}
addEventListenersByIds(element: HTMLElement, ids: Array<string | number>, types: string[], listener: EventListenerOrEventListenerObject, useCapture = false): Array<string | number> { // eslint-disable-line max-len
const returnIds: Array<string | number> = []
if (ids.length !== types.length) throw Error(`Cannot add ${types.length} event listeners using ${ids.length} ids - ids and types must be of equal length`)
types.forEach((type: string, idx: number) => {
const id: string | number = ids[idx]
const returnId: string | number = this.addEventListenerById(element, id, type, listener, useCapture)
returnIds.push(returnId)
})
return returnIds
}
// remove event listener with given ID, returns removed listener or null (if listener with given ID does not exist)
removeEventListener(id: string | number): IListener | null {
const strId: string = id.toString()
const internalListener: IInternalListener = this.#listeners[strId]
if (internalListener) {
internalListener.element.removeEventListener(internalListener.type, internalListener.listener, internalListener.useCapture)
const listener: IListener = this.#privateGetListener(internalListener)
delete this.#listeners[strId]
return listener
}
return null
}
// noinspection JSUnusedGlobalSymbols
removeEventListeners(ids: Array<string | number>): Array<IListener | null> {
const returnListeners: Array<IListener | null> = []
ids.forEach((id: string | number) => {
const returnListener: IListener | null = this.removeEventListener(id)
returnListeners.push(returnListener)
})
return returnListeners
}
// removes all event listeners and resets idx
removeAllEventListeners(): Array<IListener | null> {
const ids: Array<string | number> = this.ids()
const returnListeners: Array<IListener | null> = this.removeEventListeners(ids)
this.#idx = 1
return returnListeners
}
// same as removeAllEventListeners but no return value
destroy(): void {
this.removeAllEventListeners()
}
// returns ids of events listeners
ids(): Array<string | number> {
const ids: string[] = Object.keys(this.#listeners)
return ids.map(id => this.#privateExternalId(id))
}
// returns number of events listeners
length(): number {
return Object.keys(this.#listeners).length
}
#privateAddEventListener(element: HTMLElement, id: string, type: string, listener: EventListenerOrEventListenerObject, useCapture: boolean): string | number {
if (this.#listeners[id]) throw Error(`A listener with id ${id} already exists`)
element.addEventListener(type, listener, useCapture)
this.#listeners[id] = { id, element, type, listener, useCapture }
return this.#privateExternalId(id)
}
#privateGetListener(listener: IInternalListener): IListener {
return {
...listener,
id: this.#privateExternalId(listener.id)
}
}
#privateExternalId(id: string): string | number {
const idIsInteger = /^\d+$/.test(id)
if (idIsInteger) return parseInt(id, 10)
return id
}
}
export const listeners: Listeners = new Listeners()
And TypeScript usage:
import { listeners, IListener } from './your-path/listeners'
// Add and remove listener
const listenerId: number = listeners.addEventListener(mainVideo, 'timeupdate', (evt: Event) => console.log('hi', evt))
listeners.removeEventListener(listenerId)
// Add and remove listener by custom ID
listeners.addEventListenerById(mainVideo, 'custom-id', 'timeupdate', (evt: Event) => console.log('hello', evt))
listeners.removeEventListener('custom-id')
// Log id of all active listeners
console.log(listeners.ids())
// Log active listeners count
console.log(listeners.length())
// Get details of removed listener
listeners.addEventListenerById(mainVideo, 'fred', 'timeupdate', (evt: Event) => console.log('bye', evt))
const removedListener: IListener | null = listeners.removeEventListener('fred')
console.log('removed listener was', removedListener)
// clean up
const removedListeners: Array<IListener | null> = listeners.removeAllEventListeners()
console.log('removed listeners were', removedListeners)
// simple quick clean up
listeners.destroy()
Advanced TypeScript usage for adding / removing multiple listeners in one call:
// Add multiple event listeners
const listenerIds: number[] = listeners.addEventListeners(this.video, ['timeupdate', 'seeking', 'pause', 'play', 'playing'], (evt: Event) => {
const target = evt.target as HTMLVideoElement
this.currentTime = target.currentTime
})
console.log(listenerIds)
// Add multiple event listeners with custom IDs
listeners.addEventListenersByIds(this.video, ['id-one', 'id-two', 'id-three'], ['timeupdate', 'seeking', 'pause'], (evt: Event) => {
console.log(evt)
})
// Remove multiple event listeners
const removedListeners: Array<IListener | null> = listeners.removeEventListeners([...listenerIds, 'id-two'])
console.log(removedListeners)
if you dont have to support IE, you can use the once option
[Element].addEventListener('click', () => {...}, {
capture: false,
once: true
});
For completeness sake, since many people seem to look for this post:
You can directly use an abort controller to abort the event handler on an anonymous function, instead of going over removeEventListener()
In your example:
const controller = new AbortController();
const signal = controller.signal;
function doSomethingWith(param) {
document.body.addEventListener(
'scroll',
function()
{
document.write(param);
},
{signal}
); // An event that I want to remove later }
setTimeout(
function()
{
controller.abort();
},
3000 );
doSomethingWith('Test. ');
© 2022 - 2024 — McMap. All rights reserved.