How to body on click but exclude a particular element and its children using Javascript?

Viewed 628

When attaching a click event listener to the body using javascript, how do you write a conditional(s) that only trigger if e.target is not the element that contains the class active or any of that elements children? There are children elements within active that need to be clickable. Similar to when you click anywhere on a modal it stays open, but anywhere else on the DOM it is removed?

document.body.addEventListener("click", e => {
    if (!e.target ...) {
       // trigger functions
    }
}
2 Answers

One way to achieve this effect is to listen for a click and then cycle upwards through the DOM from e.target all the way to document.body.

As you cycle upwards, check each element to see if it contains the class .active. If it does, you can end the function with a break statement.

This means you can still have children of .active which remain clickable and will respond to other Event Listeners firing other functions.

Working Example:

const myFunction = (e) => {

  let containsActiveClass = false;
  let reviewNode = e.target;
  
  while (reviewNode.nodeName !== 'BODY') {
  
    if (reviewNode.classList.contains('active')) {
      containsActiveClass = true;
      break;
    }
    
    reviewNode = reviewNode.parentNode;
  }
  
  if (containsActiveClass === false) {
    window.alert('You clicked! (But not on .active or any of its child elements.)');
  }
}

document.body.addEventListener('click', myFunction, false);
.container {
  display: flex;
  justify-content: space-around;
  align-items: center;
  height: 180px;
  background-color: rgb(127, 127, 191);
  cursor: pointer;
}

.active {
  cursor: default;
}

.active,
.inactive {
  position: relative;
  display: inline-block;
  flex: 0 0 120px;
  height: 120px;
  text-align: center;
  font-family: sans-serif;
  font-size: 14px;
  background-color: rgb(255, 0, 0);
}

.active div,
.inactive div {
  position: absolute;
  top: 30px;
  left: 30px;
  width: 60px;
  height: 60px;
  background-color: rgb(255, 255, 0);
}
<div class="container">
<div class="inactive">Inactive
<div>Inactive Child</div>
</div>

<div class="active">Active
<div>Active Child</div>
</div>

<div class="inactive">Inactive
<div>Inactive Child</div>
</div>
</div>

You can match it with css selector

document.querySelectorAll('div').forEach(elem=>{
 console.log(elem.id, elem.matches('.active, .active *'))
})
<div id=d1>
  <div id=d2 class=active>
    <div id=d3></div>
  </div>
  <div id=d4>
    <div id=d5></div>
    <div id=d6 class=active></div>
  </div>
</div>

Related