0

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>
Max Pattern
  • 1,430
  • 7
  • 18

3 Answers3

2

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');
  }
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • 1
    Actually this is a better option than mine, but it only solves one of the issues - OP also needs to update the `ev.target.classList`, because they are identifying the operation based on the class of the topmost element that was clicked – Joel Peltonen Jan 19 '23 at 13:28
  • Thank you Barmar! Im very happy for the help! – Max Pattern Jan 19 '23 at 13:34
1

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>
Joel Peltonen
  • 13,025
  • 6
  • 64
  • 100
1

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>
mplungjan
  • 169,008
  • 28
  • 173
  • 236