Getting ancestor of element instead of using document.body

Instead of using
document.body.addEventListener('click', event => {

Open in new window

how can I get the most direct ancestor of all the elements that match .delete?


<% for (const products of pending) { %>
 <li>
    <a href="#" class="button gray delete" data-id="<%= products._id %>"><i class="sl sl-icon-close"></i> Delete</a>
 </li>
<% } %>

Open in new window

LVL 1
Black SulfurAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Zakaria AcharkiAnalyst DeveloperCommented:
Could you please give us more details about the context and what you're trying to achieve so we can provide a more specific solution.
Black SulfurAuthor Commented:
Hi, Zakaria. Sorry for being vague. I am displaying a list of records from mongoDB using node.js and ejs. I can't use document.querySelector() because that just seems to only let me click on the first database record. If I click on the second, third etc. then nothing happens at all. Therefore I needed to use document.body so now clicking on any record's buttons actually triggers an event.

However, I don't think it is best practice to use document.body if I can target something closer to the actual element, but I don't know how to do it in vanilla javascript. That is my first problem. I didn't add my second problem to the initial question but have just discovered it. When I click I also want to hide ALL buttons but again it is only working for the first record and none of the records after that.

document.body.addEventListener('click', e => {

    e.preventDefault();
    renderLoader(spinner);
    const deleteBtn = e.target;
    const productId = deleteBtn.dataset.id;
    const btnSet = document.querySelector('.buttons-to-right');
    btnSet.style.visibility = 'hidden';
});

Open in new window

Zakaria AcharkiAnalyst DeveloperCommented:
Ok you've described the problem well, could you please show us the HTML code structure sample, so we could know what is "buttons-to-right" and how it's related to the delete button.
Big Business Goals? Which KPIs Will Help You

The most successful MSPs rely on metrics – known as key performance indicators (KPIs) – for making informed decisions that help their businesses thrive, rather than just survive. This eBook provides an overview of the most important KPIs used by top MSPs.

Black SulfurAuthor Commented:
Sure!

                        <ul>
                            <% for (const products of pending) { %>
                            <li>
                                <div class="list-box-listing">
                                    <div class="list-box-listing-img"><a href="#"><img src="../<%= products.image %>" alt=""></a></div>
                                    <div class="list-box-listing-content">
                                        <div class="inner">
                                            <h3><a href="#"><%= products.title %></a></h3>
                                            <span><%= products.address.city %>, <%= products.address.suburb %></span>
                                            <div class="star-rating">
                                                <h5>Added <%= moment(products.createdAt).fromNow() %></h5>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                                <div class="buttons-to-right">
                                    <a href="edit-listing/<%= products._id %>" class="button gray edit"><i class="sl sl-icon-note"></i> Edit</a>
                                    <a href="#" class="button gray delete" data-id="<%= products._id %>"><i class="sl sl-icon-close"></i> Delete</a>
                                 
                                </div>
                            </li>
                            <% } %>
                        </ul>

Open in new window

Black SulfurAuthor Commented:
So, I tried this and it works for the second part of my question. But seems like overkill. Or is it correct?

    const btnSet = document.querySelectorAll('.buttons-to-right');
    btnSet.forEach(query => {
        query.style.visibility = 'hidden';
    });

Open in new window

Zakaria AcharkiAnalyst DeveloperCommented:
No it's ok if you want to hide all the elements, it's the correct way.

I will submit a sample about the first part of your question in a while.
Zakaria AcharkiAnalyst DeveloperCommented:
Last question to make sure I got you, do you want to hide the whole record when the user clicks on delete button?
Black SulfurAuthor Commented:
No, just the buttons which sit on the record. So, visually you can think of it as 3 columns. Column one is the image, column 2 is some text about the item and column 3 consists of the 2 buttons, edit and delete. When a user clicks on 'delete' I just want the buttons to hide so the user can't click on them while the request is happening in the background via axios. Then when there is a response sent back the buttons can be shown again if necessary.
Zakaria AcharkiAnalyst DeveloperCommented:
I got you,

The only way to attach an event to the new DOM elements added dynamically to the document is by using the event delegation event, so you need to attach the event to a constant element on the page (the must of times we chose body), but you need to control this event by condition since it will be attached to all the elements of your body that why we use the if statement like :

document.body.addEventListener('click', e => {
  const deleteBtn = e.target;
  
  if (deleteBtn.classList.contains("delete")) {
    e.preventDefault();

    deleteBtn.parentNode.style.visibility = 'hidden';
  }
});

Open in new window


Here is a very basic sample :

https://jsfiddle.net/z_acharki/rm0xg7dv/
Black SulfurAuthor Commented:
Okay, I just wondered if there was an alternative to 'body' because I read this:

Instead of body you should use the most direct ancestor of all the elements that match .delete so you're not wasting time running a handler for a lot of irrelevant click events.
Zakaria AcharkiAnalyst DeveloperCommented:
Yes in your case it could be the ul element if you're sure that it will be always there in your code, like :

document.querySelector('ul').addEventListener('click', e => {

Open in new window

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Black SulfurAuthor Commented:
Yes, this is what I am looking for!

document.querySelector('ul').addEventListener('click', e => {

Open in new window


Except for that nothing happens when I click the buttons and there is no error in console. Will querySelector work with multiple dynamically added records though?
Zakaria AcharkiAnalyst DeveloperCommented:
Yes, it should, here is an example of dynamically adding li's to the DOM after two seconds, you could see that the event work since the ul was there all the time.

https://jsfiddle.net/z_acharki/qdaz8rx7/
Black SulfurAuthor Commented:
Okay, I took a look at your jsfiddle but when you click on the delete button it only hides it from that list item. I want it to hide ALL the buttons from every list item so that the user can't interrupt the current process.
Zakaria AcharkiAnalyst DeveloperCommented:
You could use the code you already find by yourself, the logic will be like :

https://jsfiddle.net/z_acharki/qdaz8rx7/3/

if (deleteBtn.classList.contains("delete")) {
  e.preventDefault();

  document.querySelectorAll('.buttons-to-right').forEach(button => {
      button.style.visibility = 'hidden';
  });
}

Open in new window

Black SulfurAuthor Commented:
I see it working in you fiddle but doesn't work in my code. I click on the delete buttons and nothing happens. I will have to play around with it some more to see if I can find the issue.
Zakaria AcharkiAnalyst DeveloperCommented:
O try to start with sample like :

document.querySelector('ul').addEventListener('click', e => {
   alert('clicked');
)}

Open in new window

It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
JavaScript

From novice to tech pro — start learning today.