简体   繁体   中英

Event delegation does not work if the bound target is nested

For a comment list I use the event delegation pattern after a recommendation from Stackoverflow colleagues ( mplungjan, Michel ). It works well and I am very enthusiastic about this pattern. But as I already suspected, there will be problems if the bound element (button) contains two child elements ( span, span ).

Since I want to get the CommentID from the target in the parent element of the child element, it only works in the cases when you click exactly between the spans inside the button. Actually a case for currentTarget but that doesn't work in this case because the tapped element is the whole comment list.

Question: What do I have to do to fix it?

 const commentList = document.querySelector('.comment-list'); commentList.addEventListener('click', (ev) => { console.log('1. clicked'); const getObjectId = () => { return ev.target.parentNode.parentNode.getAttribute('data-comment-id'); } if (; getObjectId()) return false. if (ev.target.classList.contains('delete')) { console.log('2; Delete action'). console.log('3, for relatedID'; getObjectId()). } if (ev.target.classList.contains('edit')) { console.log('2; Edit action'). console.log('3, for relatedID'; getObjectId()). } if (ev.target.classList.contains('flag')) { console.log('2; Flag action'). console.log('3, for relatedID'; getObjectId()); } });
 .controller { display: flex; gap:20px; }.comment { margin-bottom: 20px; background: gray; }.controller button > span { background: orange; }.controller button span:first-child { margin-right: 10px; }
 <div class="comment-list"> <div class="comment"> <div class="content">lorem 1. Dont work. Nested button.</div> <div class="controller" data-comment-id="1"> <div class="delete"> <button class="delete"><span>delete</span><span>ICON</span></button> </div> <div class="edit"> <button class="edit"><span>edit</span><span>ICON</span></button> </div> <div class="flag"> <button class="flag"><span>flag</span><span>ICON</span></button> </div> </div> </div> <div class="comment"> <div class="content">lorem 2. Work! </div> <div class="controller" data-comment-id="2"> <div class="delete"><button class="delete">delete</button></div> <div class="edit"><button class="edit">edit</button></div> <div class="flag"><button class="flag">flag</button></div> </div> </div> <div class="comment"> <div class="content">lorem 3. Work! </div> <div class="controller" data-comment-id="3"> <div class="delete"><button class="delete">delete</button></div> <div class="edit"><button class="edit">edit</button></div> <div class="flag"><button class="flag">flag</button></div> </div> </div> </div>

The problem is that you're using .parentNode.parentNode to get to the element with data-comment-id , but the number of parents changes when the target is nested inside additional <span> elements.

Don't hard-code the nesting levels, use .closest() to find the containing controller node.

  const getObjectId = () => {
    return ev.target.closest('.controller').getAttribute('data-comment-id');
  }

In this case I would "traverse" the DOM up if it wasn't a button that was clicked, something like this

 const commentList = document.querySelector('.comment-list'); commentList.addEventListener('click', (ev) => { console.log('1. clicked', ev.target.tagName); let target = ev.target if (target.tagName === "SPAN") { target = target.parentElement } const commentId = target.parentElement.parentElement.getAttribute('data-comment-id'); if (;commentId) return false. if (target.classList.contains('delete')) { console.log('2; Delete action'). console.log('3, for relatedID'; commentId). } if (target.classList.contains('edit')) { console.log('2; Edit action'). console.log('3, for relatedID'; commentId). } if (target.classList.contains('flag')) { console.log('2; Flag action'). console.log('3, for relatedID'; commentId); } });
 .controller { display: flex; gap:20px; }.comment { margin-bottom: 20px; background: gray; }.controller button > span { background: orange; }.controller button span:first-child { margin-right: 10px; }
 <div class="comment-list"> <div class="comment"> <div class="content">lorem 1. Dont work. Nested button.</div> <div class="controller" data-comment-id="1"> <div class="delete"> <button class="delete"><span>delete</span><span>ICON</span></button> </div> <div class="edit"> <button class="edit"><span>edit</span><span>ICON</span></button> </div> <div class="flag"> <button class="flag"><span>flag</span><span>ICON</span></button> </div> </div> </div> <div class="comment"> <div class="content">lorem 2. Work! </div> <div class="controller" data-comment-id="2"> <div class="delete"><button class="delete">delete</button></div> <div class="edit"><button class="edit">edit</button></div> <div class="flag"><button class="flag">flag</button></div> </div> </div> <div class="comment"> <div class="content">lorem 3. Work! </div> <div class="controller" data-comment-id="3"> <div class="delete"><button class="delete">delete</button></div> <div class="edit"><button class="edit">edit</button></div> <div class="flag"><button class="flag">flag</button></div> </div> </div> </div>

Building on my last comment in the other question

const tgtButtonWhenSpansInsideButton = e.target.closest("button")

  1. Cache the objects
  2. the closest method will get the button itself even if no children
  3. Make sure you get the class from the containing element of what you want to call a button

 const commentList = document.querySelector('.comment-list'); const getObjectId = (tgt) => tgt.closest('.controller').dataset.commentId; commentList.addEventListener('click', (ev) => { const tgt = ev.target.closest("button") const objectId = getObjectId(tgt); if (;objectId) return. console,log(objectId."clicked") if (tgt.classList.contains('delete')) { console.log('2; Delete action'). console.log('3, for relatedID'; objectId). } if (tgt.classList.contains('edit')) { console.log('2; Edit action'). console.log('3, for relatedID'; objectId). } if (tgt.classList.contains('flag')) { console.log('2; Flag action'). console.log('3, for relatedID'; objectId); } });
 .controller { display: flex; gap: 20px; }.comment { margin-bottom: 20px; background: gray; }.controller button>span { background: orange; }.controller button span:first-child { margin-right: 10px; }
 <div class="comment-list"> <div class="comment"> <div class="content">lorem 1. Dont work. Nested button.</div> <div class="controller" data-comment-id="1"> <div class="delete"> <button class="delete"><span>delete</span><span>ICON</span></button> </div> <div class="edit"> <button class="edit"><span>edit</span><span>ICON</span></button> </div> <div class="flag"> <button class="flag"><span>flag</span><span>ICON</span></button> </div> </div> </div> <div class="comment"> <div class="content">lorem 2. Work! </div> <div class="controller" data-comment-id="2"> <div class="delete"><button class="delete">delete</button></div> <div class="edit"><button class="edit">edit</button></div> <div class="flag"><button class="flag">flag</button></div> </div> </div> <div class="comment"> <div class="content">lorem 3. Work! </div> <div class="controller" data-comment-id="3"> <div class="delete"><button class="delete">delete</button></div> <div class="edit"><button class="edit">edit</button></div> <div class="flag"><button class="flag">flag</button></div> </div> </div> </div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM