0
votes

I need to apply the events delegation approach in my script.js. So, I need to catch all 'click' events on the body tag. It cannot be changed. I use:

document.body.addEventListener('click', function(event) {
  ... // here I catch clicks on different elements
}, true);

I have a parent block and unknown amount of internal nested blocks, children / grandchildren / grand-grand..., etc.

I want to click anywhere on parent area, on any of child, grandchild area, and then I want to catch an event with 'parent' class name in event.target.classList array.

Please, see example code https://codepen.io/appalse/pen/xxbqWLr

document.body.addEventListener('click', function(event) {
  alert(event.target.classList);
  if (event.target.classList.contains('parent')) {
    /* I want to catch certain target with 'parent' class name */
    event.target.classList.toggle('yellow-background');
  }

}, true);
div {
  border: 1px solid black;
  margin: 10px;
  padding: 10px;
}

div.parent {
  border-width: 3px;
}

.container {
  width: 300px;
  background-color: lightgrey;
}

.parent {
  background-color: lightblue;
  cursor: pointer;
}

.child {
  background-color: lightgreen;
}

.grandchild {
  background-color: cyan;
}

.yellow-background {
  background-color: yellow;
}
<div class="container"> Container
  <div class="parent"> Parent - it catches click. OK
    <div class="child">Child - doesn't catch click.
      <div class="grandchild">Grandchild - doesn't catch click</div>
      <div class="grandchild">Grandchild - doesn't</div>
    </div>
  </div>
</div>

I have read about events bubbling and capturing. I used a 'useCapture = true' value in addEventListener in order to catch an event during capturing phase. But it works another way. I thought that addEventListener method is called for every tag which is under clicked area, but I see that the addEventListener method is called for only the last child event.

I cannot use event.target.parentNode because I don't know how deeply this targeted child is. However, I can recursively call for parentNode until I get necessary parent. But this solution looks strange for me, like a hack. Am I wrong? Is this recursive approach is common and good?

The question seems really basic, but in the articles "capturing and bubbling", "event delegation" I see only the simplest examples without an unknown amount of nested elements. Or they propose the solution with the addEventListener method on parent tag, which I cannot use (because I use document.body.addEventListener).

Please, help me to find a solution and to get a better understanding of this issue. Any link or google keywords would be appreciated.

2

2 Answers

1
votes

Use event.target.closest('.parent') to see if the clicked element has an ancestor which is a .parent. If so, change the parent's class, otherwise do nothing:

document.body.addEventListener('click', function(event) {
  const parent = event.target.closest('.parent');
  if (parent) {
    parent.classList.toggle('yellow-background');
  }
}, true);
div {
  border: 1px solid black;
  margin: 10px;
  padding: 10px;
}

div.parent {
  border-width: 3px;
}

.container {
  width: 300px;
  background-color: lightgrey;
}

.parent {
  background-color: lightblue;
  cursor: pointer;
}

.child {
  background-color: lightgreen;
}

.grandchild {
  background-color: cyan;
}

.yellow-background {
  background-color: yellow;
}
<h1>How to catch an event on Parent element (event.target.classList should contain 'parent') if I click on any of Grandchild / Child / Parent?</h1>
<div class="container"> Container
  <div class="parent"> Parent - it catches click. OK
    <div class="child">Child - doesn't catch click.
      <div class="grandchild">Grandchild - doesn't catch click</div>
      <div class="grandchild">Grandchild</div>
    </div>
    <div class="child">
      <div class="grandchild">Grandchild</div>
      <div class="grandchild">Grandchild</div>
    </div>
  </div>
</div>
<div class="container">
  <div class="parent"> Parent - should catch click
    <div class="child">
      <div class="grandchild">Grandchild</div>
      <div class="grandchild">Grandchild</div>
    </div>
    <div class="child">
      <div class="grandchild">Grandchild</div>
      <div class="grandchild">Grandchild</div>
    </div>
  </div>
</div>

Bubbling vs capturing doesn't make much of a difference here - at least in this situation, you can attach the listener in the bubbling phase too, and it'll work just as well.

I thought that addEventListener method is called for every tag which is under clicked area, but I see that the addEventListener method is called for only the last child event.

Every ancestor element of a clicked element will have a click event listener run, if it has one attached to the element - but here, you only have a single click listener, attached to the document.body, so only one callback runs when there's a click.

0
votes

Just add listener to document.body, and let event bubbling solves your problem.