I want to keep the scope at a module level, while having a reference to a function, so that I can add it to an event listener (as a callback), and also remove it:
export default () => ({
init() {
// Detect escape key press
window.addEventListener('keyup', this.escapeKeyPressListener, true);
},
whatever() {
// Do whatever
...
// Remove event listener
window.removeEventListener('keyup', this.escapeKeyPressListener);
},
escapeKeyPressListener: (event) => {
console.log('added');
if (event.key === 'Escape' || event.key === 'Esc') {
event.preventDefault();
this.whatever();
}
}
});
Just as additional info, even if it shouldn't matter, the module is being used with Alpine, as follows:
import lightbox from "./alpine-modules/lightbox";
Alpine.data('lightbox', lightbox);
But this is not working, when this.whatever()
is reached, the scope is incorrect.
How should I structure this so that I can add and remove the function to the listener and also keep the scope working as I want it to?
Did I try something or am I just guessing?
Well... after learning about arrow functions and reading this similar but not the same question, I tried using:
window.addEventListener('keyup', (event) => this.escapeKeyPressListener, true);
... and defining the function just as a normal one:
escapeKeyPressListener(event) {
It works as in... it triggers the listener and the scope is fine, but then, how do I remove the listener without having a reference to it?
UPDATE #1
Based on Quentin's feedback, I did it as follows (the method names are different because my previous example was a simplified/generalized version) with the inital aim of keeping this simple:
/**
* @property isOpen - controls the display state of the lightbox
*/
let isOpen = false;
/**
* Responsible for opening the lightbox
*/
const open = () => {
isOpen = true;
// Detect escape key press
window.addEventListener('keyup', escapeKeyPressListener, true /* Capture it for any part in the DOM */);
};
/**
* Responsible for closing the lightbox
*/
const close = () => {
isOpen = false;
// Stop detecting escape key press
window.removeEventListener('keyup', escapeKeyPressListener);
};
/**
* Responsible for adding listener function for escape keypress
* @param {object} event - keyup event listener
*/
const escapeKeyPressListener = (event) => {
console.log('added');
if (event.key === 'Escape' || event.key === 'Esc') {
event.preventDefault();
close();
}
};
const lightbox = () => ({
isOpen,
open,
close,
escapeKeyPressListener,
});
export default lightbox;
The module stopped working but shows no errors.
Interestingly, if I change the initial let isOpen = false;
to let isOpen = open;
, Alpine renders the component as opened, so... Alpine is actually getting and processing part of this.
UPDATE #2:
Now it works, by adding a property like:
export default () => ({
...
escapeListenerFunction: null,
escapeKeyPressListener(event) {
if (event.key === 'Escape' || event.key === 'Esc') {
event.preventDefault();
this.close();
document.removeEventListener('keyup', this.escapeListenerFunction);
}
}
...
Then doing:
// Save a reference to the function to remove the event later.
this.escapeListenerFunction = (event) => {
this.escapeKeyPressListener.apply(this, [event]);
};
// Detect escape key press
window.addEventListener('keyup', this.escapeListenerFunction, true);
And removing it with:
document.removeEventListener('keyup', this.escapeListenerFunction);
The trick was in using .apply(this, [event])
Crazy.