I'm struggling with removeEventListener
in an object literal.
I have an object which deals with adding and removing custom outlines when the user is tabbing through a website.
const OUTLINE = {
enableOutline: function () {
if (event.key === 'Tab' || (event.shiftKey && event.key === 'Tab')) {
document.addEventListener('focusin', () => this.showOutline());
}
},
showOutline: function () {
this.showLogoOutline(event);
},
showLogoOutline: function () {
if (event.target === this.logo) {
this.logo.classList.add('logo_outline');
}
}
};
document.addEventListener('keydown', () => OUTLINE.enableOutline(event));
The same object contains method which removes outline and unsuccesfully tries to remove the EventListener
.
const OUTLINE = {
hideOutline: function () {
this.hideLogoOutline();
// BELOW DOESN'T WORK
document.removeEventListener('focusin', this.showOutline);
},
hideLogoOutline: function () {
if (this.logo.classList.contains('logo_outline')) {
this.logo.classList.remove('logo_outline');
}
}
};
document.addEventListener('mousedown', () => OUTLINE.hideOutline());
First off: never overload the tab key (or any key with universal behaviour across all browsers). Tab
is used by the browser to navigate through tabIndexable elements, and code like this "traps" the tab key. Not cool. Especially not for people who can't use a mouse. Especially if this is "just for you" or "just for [people who you know don't fall in that category]", be in the habit of not overloading built-in behaviour because that way you'll never write bad code when it does matter.
Having said that, you're not actually removing the listener you added. The function () => OUTLINE.hideOutline()
is a new function, scoped to the declaration context (as opposed to execution context that you'd get if you used function() { ...}
), and so points to a completely different thing than OUTLINE.hideOutline
points to.
There's no searching or matching of any kind when removing listeners, it can only remove exactly the same thing that you told it to add earlier (i.e. it has to have same event name, the same function handle, and the same priority flag if you specified one).
Since this is a singleton object primitive (e.g. there is no meaningful this
), don't use this
, directly refer to itself using its const
name, so you can add and remove its function through direct reference:
const OUTLINE = {
...
enableOutline: function(event) {
const { key } = event;
if (key.toLowerCase() === `f`) {
document.addEventListener('focusin', OUTLINE.showOutline);
}
}
hideOutline: function(_event) {
document.removeEventListener('focusin', OUTLINE.showOutline);
},
};
But of course if you're going to have a setup where you need to add and remove event listeners a lot, write a simple event manager. E.g. start with something like this and customize it as needed.
class EventManager {
constructor() {
this.events = {};
}
listen(target, evtName, handler) {
if (!this.events[evtName]) this.events[evtName] = [];
const evtList= this.events[evtName];
const position = this.events[evtName].length;
const entry = { target, handler, removed:false, remove: () => {
// removeEventListener always returns undefined
evtList[position] = target.removeEventListener(evtName, handler);
entry.removed = true;
}});
target.addEventListener(evtName, handler);
this.events[evtName].push(entry);
return entry;
}
forget(target, evtName, handler=false) {
const evtList = this.events[evtName];
evtList
.filter(e => (e.target===target && (handler ? e.handler===handler : true)))
.forEach(e => e.remove())
}
}
const Events = new EventManager();
export default Events
which would turn your code into something like this:
import Events from "globalManager";
const OUTLINE = {
...
enableOutline: function(event) {
const { key } = event;
if (key.toLowerCase() === `f` && !OUTLINE.listener) {
OUTLINE.listener = Events.listen(document, `focusin`, OUTLINE.showOutline);
}
}
hideOutline: function() {
if (OUTLINE.listener) {
OUTLINE.listener = OUTLINE.listener.remove();
}
},
};