How do I remove the click listener I bound to window
in the constructor
below? I need it to listen on window
, and I need access to the button instance inside it.
class MyEl extends HTMLButtonElement {
constructor() {
super();
this.clickCount = 0;
window.addEventListener('click', this.clickHandler.bind(this));
}
clickHandler(e) {
if (e.target === this) {
this.textContent = `clicked ${++this.clickCount} times`;
window.removeEventListener('click', this.clickHandler);
}
}
disconnectedCallback() {
window.removeEventListener('click', this.clickHandler);
}
}
customElements.define('my-el', MyEl, { extends: 'button' });
<button is="my-el" type="button">Click me</button>
Event listeners can also be removed by passing an AbortSignal to an addEventListener() and then later calling abort() on the controller owning the signal.
Removing the event listener first always results in lower memory usage (no leaks). Results: IE6 - memory leak.
The addEventListener() method allows you to add event listeners on any HTML DOM object such as HTML elements, the HTML document, the window object, or other objects that support events, like the xmlHttpRequest object.
It's not possible with your current implementation - every call of .bind
creates a new separate function, and you can only call removeEventListener
to remove a listener if the passed function is the same (===
) as the one passed to addEventListener
(just like .includes
for arrays, or .has
for Sets):
const fn = () => 'foo';
console.log(fn.bind(window) === fn.bind(window));
As a workaround, you could assign the bound function to a property of the instance:
class MyEl extends HTMLButtonElement {
constructor() {
super();
this.clickCount = 0;
this.boundListener = this.clickHandler.bind(this);
window.addEventListener('click', this.boundListener);
}
clickHandler(e) {
this.textContent = `clicked ${++this.clickCount} times`;
window.removeEventListener('click', this.boundListener);
}
}
customElements.define('my-el', MyEl, { extends: 'button' });
<button is="my-el" type="button">Click me</button>
Create a wrapper func for your clickHandler like so.
class MyEl extends HTMLButtonElement {
constructor() {
super();
this.clickCount = 0;
this.wrapper = e => this.clickHandler.apply(this, e);
window.addEventListener('click', this.wrapper);
}
clickHandler(e) {
this.textContent = `clicked ${++this.clickCount} times`;
window.removeEventListener('click', this.wrapper);
}
}
customElements.define('my-el', MyEl, { extends: 'button' });
<button is="my-el" type="button">Click me</button>
Another pattern is to keep your Listener inside the constructor.
To remove an Event Listener (no matter what pattern) you can add a 'remove' function the moment you create an Event Listener.
Since the remove function is called within the listen
scope, it uses the same name
and func
tion
pseudo code:
listen(name , func){
window.addEventListener(name, func);
return () => window.removeEventListener( name , func );
}
let remove = listen( 'click' , () => alert('BOO!') );
//cleanup:
remove();
Run Code Snippet below to see it being used with multiple buttons
to save you an hour once you do more with events...
Note that WebComponents (ie CustomElements with shadowDOM) need CustomEvents with the composed:true
property if you want them to bubble up past its shadowDOM boundary
new CustomEvent("check", {
bubbles: true,
//cancelable: false,
composed: true // required to break out of shadowDOM
});
Note: this example does not run on Safari, as Apple refuses to implement extending elements : extends HTMLButtonElement
class MyEl extends HTMLButtonElement {
constructor() {
let ME = super();// super() retuns this scope; ME makes code easier to read
let count = 0;// you do not have to stick everything on the Element
ME.mute = ME.listen('click' , event => {
//this function is in constructor scope, so has access to ALL its contents
if(event.target === ME) //because ALL click events will fire!
ME.textContent = `clicked ${ME.id} ${++count} times`;
//if you only want to allow N clicks per button you call ME.mute() here
});
}
listen(name , func){
window.addEventListener( name , func );
console.log('added' , name , this.id );
return () => { // return a Function!
console.log( 'removeEventListener' , name , 'from' , this.id);
this.style.opacity=.5;
window.removeEventListener( name , func );
}
}
eol(){ // End of Life
this.parentNode.removeChild(this);
}
disconnectedCallback() {
console.log('disconnectedCallback');
this.mute();
}
}
customElements.define('my-el', MyEl, { extends: 'button' });
button{
width:12em;
}
<button id="One" is="my-el" type="button">Click me</button>
<button onclick="One.mute()">Mute</button>
<button onclick="One.eol()">Delete</button>
<br>
<button id="Two" is="my-el" type="button">Click me too</button>
<button onclick="Two.disconnectedCallback()">Mute</button>
<button onclick="Two.eol()">Delete</button>
Notes:
count
is not available as this.count
but is available to all functions defined IN constructor scope. So it is (kinda) private, only the click function can update it.
onclick=Two.disconnectedCallback()
just as example that function does NOT remove the element.
Also see: https://pm.dartus.fr/blog/a-complete-guide-on-shadow-dom-and-event-propagation/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With