Event bubbling and capture on a root element with nested elements

2.1k views Asked by At

I am trying to capture the id on each card from the root element. However, every time I click on a nested element I get empty string. However, I want the id from the wrapping card while listening on the root element cards. I want to handle both bubbling and capturing cases as this is part of a larger structure. I only want answers in vanilla js, and Javascript, no css please.

cards.addEventListener('click', evt => {
  if (evt.target !== evt.currentTarget) {
    var clickedItem = evt.target.id
    console.log(clickedItem);
  }
});
.card {
  /* Add shadows to create the "card" effect */
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
  transition: 0.3s;
}


/* On mouse-over, add a deeper shadow */

.card:hover {
  box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
}


/* Add some padding inside the card container */

.container {
  padding: 2px 16px;
}
<div id="cards" style="margin: auto; width: 50%;">
  <div class="card" id="1234567"><img src="img_avatar.png">
    <div class="container">
      <h4>1st Note</h4>
      <p>Note Body</p>
    </div>
  </div>
  <div class="card" id="1234547"><img src="img_avatar.png">
    <div class="container">
      <h4>2nd Note</h4>
      <p>Note Body2</p>
    </div>
  </div>
  <div class="card" id="721680"><img src="img_avatar.png">
    <div class="container">
      <h4>3Note Body</h4>
      <p>Note Body3</p>
    </div>
  </div>
</div>

2

There are 2 answers

3
Sheepy On BEST ANSWER

Yes, one listener is a good requirement. As it happens you only need to change the line where you get your target:

cards.addEventListener('click', evt => {
  console.log( evt.target.closest( '.card' ).id );
});

Complete example:

cards.addEventListener('click', evt => {
  alert( evt.target.closest( '.card' ).id );
});
.card {
  /* Add shadows to create the "card" effect */
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
  transition: 0.3s;
}


/* On mouse-over, add a deeper shadow */

.card:hover {
  box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
}


/* Add some padding inside the card container */

.container {
  padding: 2px 16px;
}
<div id="cards" style="margin: auto; width: 50%;">
  <div class="card" id="1234567"><img src="img_avatar.png">
    <div class="container">
      <h4>1st Note</h4>
      <p>Note Body</p>
    </div>
  </div>
  <div class="card" id="1234547"><img src="img_avatar.png">
    <div class="container">
      <h4>2nd Note</h4>
      <p>Note Body2</p>
    </div>
  </div>
  <div class="card" id="721680"><img src="img_avatar.png">
    <div class="container">
      <h4>3Note Body</h4>
      <p>Note Body3</p>
    </div>
  </div>
</div>

13
Scott Marcus On

The issue is that depending on where you click, you may wind up clicking on <div class="container">, which has no id.

Let's examine your HTML structure:

<!-- You have your click handler attached to this parent element. It's true
     that any clicks on descendant elements will bubble up to this parent, 
     but when you handle it here, this parent becomes event.currentTarget 
     and while this element does have an id, it's not the id you want. -->
<div id="cards" style="margin: auto; width: 50%;">

  <!-- This is the element level that has the id that you want, so
       this is the element level that should have the event handlers. -->
  <div class="card" id="1234567">

    <!-- If the user clicks in the area of the following elements (which 
         they are most likely to because it takes up most of the space 
         on the card) one of these elements will become `event.target` 
         and none of these elements has an id to get. That's why you are
         getting an empty string in your console. -->
    <img src="img_avatar.png">

    <div class="container">
      <h4>1st Note</h4>
      <p>Note Body</p>
    </div>

  </div>

  ... inner pattern repeats ...

</div>

You need to assign the click event to each .card, not the parent .cards and then you can get the currentTarget.id

Array.prototype.slice.call(document.querySelectorAll(".card")).forEach(function(c){
  c.addEventListener('click', evt => {
    console.log(evt.currentTarget.id);
  });
});
.card {
  /* Add shadows to create the "card" effect */
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
  transition: 0.3s;
}


/* On mouse-over, add a deeper shadow */

.card:hover {
  box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
}


/* Add some padding inside the card container */

.container {
  padding: 2px 16px;
}
<div id="cards" style="margin: auto; width: 50%;">
  <div class="card" id="1234567"><img src="img_avatar.png">
    <div class="container">
      <h4>1st Note</h4>
      <p>Note Body</p>
    </div>
  </div>
  <div class="card" id="1234547"><img src="img_avatar.png">
    <div class="container">
      <h4>2nd Note</h4>
      <p>Note Body2</p>
    </div>
  </div>
  <div class="card" id="721680"><img src="img_avatar.png">
    <div class="container">
      <h4>3Note Body</h4>
      <p>Note Body3</p>
    </div>
  </div>
</div>