Goal: reload only the div after ajax calls (add or remove tags) without losing event listeners on the reloaded div. Using insertAdjacentHTML
does not seem optimal.
Front-end: I have a sidebar with ul/li for tags. There's a modal to add/create tags; there is an icon "X" next to each tag to remove it on click.
<ul>
<li><span>A</span><span class=li-untag>X</span>
<li><span>B</span><span class=li-untag>X</span>
<li><span>C</span><span class=li-untag>X</span>
<li><span>D</span><span class=li-untag>X</span>
</ul>
My failed setup was:
send an ajax (fetch) call to the php back-end to add or remove tags
if no error, the back-end sends an updated and twig-formatted list of tags for the article
- updated the div with
innerHTML
.
The innerHTML
update would lose all the event listeners of the list (such as remove tag when clicking X) until a real refresh of the page. I tried to run the addeventlisteners after the ajax call but it didn't work.
My current setup based on previous SO answers (see below) uses insertAdjacentHTML
. That (sort of) works, but it's clunky.
- to remove a tag is not an insert issue and all I could think of was to hide a tag on click until the page is refreshed.
- to add tags with
insertAdjacentHTML
is to append new tags to the list, but it's no longer alphabetically ordered and I have to use some hacky javascript to format the output to match the existing list.
I would really prefer having php send a whole updated and formatted list for the div.
Any suggestions for a more elegant way to do this in vanilla js?
FYI: The main answers I have relied on:
Why can event listeners stop working after using element.innerHTML?
Is it possible to append to innerHTML without destroying descendants' event listeners?
If a DOM Element is removed, are its listeners also removed from memory?
JS eventListener click disappearing
Edit: the listener is attached to the "li-untag" class
Edit 2: answering the comment request for some more code. Here is for the removing tag, using the atomic ajax library:
for(let liUntag of document.querySelectorAll('.li-untag')){
liUntag.addEventListener("click", () => altUntag(liUntag))};
const altUntag = (el) => {
atomic("/tags", {
method: 'POST',
data: {
type: 'untag',
slug: el.getAttribute('data-slug'),
tag: el.getAttribute('data-untag')
}
}).then(function (response) {
el.closest('li').style.display = 'none';
console.log(response.data); // xhr.responseText
console.log(response.xhr); // full response
})
.catch(function (error) {
console.log(error.status); // xhr.status
console.log(error.statusText); // xhr.statusText
});
};
`? Then just replace the `
– CertainPerformance Apr 27 '19 at 19:18`'s `innerHTML`, and it should stay attached. If you attach a listener to a child element that you replace, then the listener will be lost, as expected (I'd avoid that, use event delegation instead)