Remove document and window event handlers when removing custom elements

I realized that the event handlers that i was setting when creating the
tags input and the multi-select controls were not removed just because
these elements are no longer in the document, and kept firing again and
again.

I no longer can use an anonymous function, because removeEventListener
would not match it with the one passed to addEventListener.  I also have
to bind the handler to `this` in order to keep having access to the
object, and, again, can not do it in the call to addEventListener, or
i would get a different function each time.

I added the check to see if the element is connected inside the
connectedCallback because the documentation warns that this callback
“may be called once your element is no longer connected”[0], and i
understood it to mean that the connected and disconnected callbacks
could be called our of order, thus it would be possible to add event
listeners that would not be removed—again.

I am not actually sure where i have to do the same for the rest of the
“internal” events.

[0]: https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks
This commit is contained in:
jordi fita mas 2023-04-10 00:05:29 +02:00
parent ba880c6560
commit f945051f4a
1 changed files with 33 additions and 17 deletions

View File

@ -4,9 +4,13 @@ class Multiselect extends HTMLDivElement {
this.initialized = false;
this.toSearch = '';
this.highlighted = 0;
this.onFocusOutHandler = this.onFocusOut.bind(this);
}
connectedCallback() {
if (!this.isConnected) {
return;
}
if (!this.initialized) {
for (const child of this.children) {
switch (child.nodeName) {
@ -22,6 +26,19 @@ class Multiselect extends HTMLDivElement {
this.init();
}
}
window.addEventListener('focusin', this.onFocusOutHandler);
document.addEventListener('click', this.onFocusOutHandler);
}
disconnectedCallback() {
document.removeEventListener('click', this.onFocusOutHandler);
window.removeEventListener('focusin', this.onFocusOutHandler);
}
onFocusOut(e) {
if (this.contains(e.target)) return;
if (!e.target.isConnected) return;
this.close();
}
init() {
@ -93,17 +110,6 @@ class Multiselect extends HTMLDivElement {
this.options.setAttribute('role', 'listbox');
this.close();
window.addEventListener('focusin', (e) => {
if (this.contains(e.target)) return;
this.close();
});
document.addEventListener('click', (e) => {
if (this.contains(e.target)) return;
if (!e.target.isConnected) return;
this.close();
});
this.rebuild();
}
@ -278,9 +284,13 @@ class Tags extends HTMLDivElement {
super();
this.initialized = false;
this.tags = [];
this.onFocusOutHandler = this.onFocusOut.bind(this);
}
connectedCallback() {
if (!this.isConnected) {
return;
}
if (!this.initialized) {
for (const child of this.children) {
switch (child.nodeName) {
@ -296,6 +306,18 @@ class Tags extends HTMLDivElement {
this.init();
}
}
window.addEventListener('focusin', this.onFocusOutHandler);
}
disconnectedCallback() {
window.removeEventListener('focusin', this.onFocusOutHandler);
}
onFocusOut(e) {
if (this.contains(e.target)) return;
if (e.target.value && e.target.value.trim() !== '') {
this.createTag();
}
}
init() {
@ -339,12 +361,6 @@ class Tags extends HTMLDivElement {
break;
}
});
window.addEventListener('focusin', (e) => {
if (this.contains(e.target)) return;
if (e.target.value.trim() !== '') {
this.createTag();
}
});
// Must come after the search input
this.tagList.append(this.label);