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
insertAdjacentHTMLis 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)