Event delegation will be ineffective when the target element is nested within another element

After receiving a recommendation from my colleagues on Stackoverflow (mplungjan, Michel), I implemented the event delegation pattern for a comment list. It has been working well and I am quite excited about this approach. However, I have encountered an issue with buttons that contain two child elements - spans.

The problem arises when I try to retrieve the CommentID from the parent element of the child element. This only works if the click happens exactly between the spans inside the button. Using currentTarget does not solve the issue as the tapped element is the entire comment list.

Question: What steps should I take to address this issue?

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>

Answer №1

The issue lies in the use of .parentNode.parentNode to access the element with data-comment-id, as the number of parent elements may vary when the target is nested within additional <span> tags.

Avoid hard-coding the levels of nesting by utilizing .closest() method to locate the nearest controller node.


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

Answer №2

Expanding upon my previous statement from the previous query

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

  1. Optimize the variables
  2. The closest function will target the button itself even if there are no child elements
  3. Ensure you retrieve the class from the parent element of the desired 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. Does not 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. Works!</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. Works!</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>

Answer №3

In this scenario, I would navigate through the DOM hierarchy if the element clicked was not a button. Here is an example of how I would approach it:

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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to retrieve the document DOM object within an EJS template?

I am attempting to display a list of participants when the user clicks on the button. However, every time I try, I encounter an error stating "document is not defined". (Please refrain from suggesting the use of jQuery!). <% var btn = document.getEle ...

How to retrieve the row index from a table using JavaScript

This question has cropped up numerous times, and despite trying various solutions suggested before, none of them seem to be effective in my case. Within a modal, I have a table where users can make changes that are then used to update the database. The ta ...

Guidelines for integrating Pinia seamlessly into Vue 3 components

How should Pinia's store be correctly used in Vue 3 components? Option A const fooStore = useFooStore(); function bar() { return fooStore.bar } const compProp = computed(() => fooStore.someProp) Option B function bar() { return useFooStore( ...

Having issues with Firefox rendering JavaScript and CSS correctly

I'm trying to figure out if it's the row class from Bootstrap that's causing issues, or if there's a problem with my JS/CSS not loading properly in Firefox. It seems to be working fine in Chrome and Safari. Any ideas on what could be go ...

The vacant array suddenly fills up with content once it is accessed

I encountered a strange scenario where the console.log indicated that the array was empty. However, upon inspection, I found 2 elements within it. This unexpected behavior prevented the component from rendering as it believed that conversations was empty. ...

Navigating Form Submission in Next.js

In this code snippet, I attempted to perform simple addition (ket=name + names). The desired outcome is a numerical sum displayed as “ket”. However, when entering 3 and 6 into the input fields, the result appears as 36 instead of 9. export default fu ...

What steps should I take to make this slider functional?

How can I achieve a sliding effect on this code? I want the div to slide out when the button is clicked, but I also want the button itself to move. Additionally, how can I ensure that the image and text are displayed in two columns? In my IDE, it appears a ...

Can you explain the process of utilizing a JavaScript function that has been fetched via Ajax

When the AJAX function is loaded on the page, I am attempting to execute another function. The code snippet for the AJAX call: $.ajax({ type: "POST", url: "loginpersonal.asp", data: "id=<%=request("id")%>", beforeSend: function() { $("#personal ...

The hyperlink element is failing to load in a particular frame

I've been attempting to load the URL of an anchor tag in a specific frame. I've tried various methods through online searches, but have not found a satisfactory solution. Can someone please assist me with how to load the href URL in a particular ...

Determine the instance's name as a string in JavaScript

Currently, I am utilizing Three.js in combination with javascript. Upon running the following line of code: console.log(this.scene.children[1]) I receive the following output in the console within Chrome: https://i.stack.imgur.com/6LBPR.png Is there a w ...

Turn off pagination for tables in Material-UI

Currently, I am utilizing the TablePagination component from Material-UI in my React project. Unfortunately, this component does not come with a built-in disabled prop. I do have a boolean variable called loading that I would like to utilize as a paramet ...

Can functions be used as keys in a collection in JavaScript's map?

Using functions as keys in JavaScript can be tricky because for js objects, functions are converted to their "toString" form. This poses a problem if two functions have the same body. var a = function() {}; var b = function() {}; var obj={}; obj[a] = 1; o ...

Obtaining a URL in JavaScript

Hey there, I'm diving into the world of JavaScript and could use some guidance. I'm currently working on implementing forward and backward buttons on a webpage. The URL structure is http://webaddress.com/details?id=27 My goal is to write two fun ...

I encountered an issue where the font awesome icons failed to load on my website

My website is experiencing an issue where all the Font Awesome icons are not displaying despite having added Font Awesome. Here is the link to my site: https://i.stack.imgur.com/RcNtv.png .login-top .lg-in::before { font-family: "Fontawesome"; ...

Show a plethora of images using the express framework

I have two closely related questions that I am hoping to ask together. Is there a way for express (such as nodejs express) to handle all requests in the same manner, similar to how http treats requests with code like this: pathname = url.parse(request.url ...

Navigating through external JSON data in a Node.js and Express application, and rendering the data using Jade

After going through various examples and solutions in related questions on StackExchange in an attempt to solve my issue, I have decided to post my question. My ultimate goal is to access complex JSON data via an API and REST. I intend to import this data ...

The function you are trying to call in Javascript is currently unavailable

I encountered an issue with a JavaScript script. I have an object that contains some functions and variables. Within one of these functions, I make an Ajax request. However, in the error handler, when trying to call a function defined within the same objec ...

Function for Duplicating jQuery Events

I'm currently facing an issue where every time the browser is resized, a function is triggered. This function can turn a side panel into an accordion if the screen width meets certain criteria, or it can just display as an open side panel on larger sc ...

Create a variety of tables populated with numerous hyperlinks within each table on a webpage

I have a website where I need to display multiple tables with different data, along with appropriate links within the table cells. Plan: Each table on the webpage represents a different school subject, showcasing performance metrics such as SAT scores, fi ...

Unable to fetch Rails response through Ajax

Whenever I make a post request from my phonegap app (using ajax in javascript) to my rails server, the post goes through successfully. However, I do not receive any response from the server which ultimately leads to failure. It's puzzling why I'm ...