0

I currently have buttons dynamically generated onto the page with plain JavaScript.

Example

<button class="c-config__option c-config__option--button c-config__option--pack" data-index="0">
    Item 1
    <span>Subtext</span>
</button>
<button class="c-config__option c-config__option--button c-config__option--pack" data-index="1">
    Item 2
    <span>Subtext</span>
</button>
...

I am targeting the parent class (.c-config__wrapper) and using event.target to target the buttons. If I click on the span inside of the button THAT becomes the target rather than the button.

Using plain Javascript, how do I ensure that the parent button remains the target and not the span? OR how do I include the span as a target along with the button?

Federico klez Culloca
  • 26,308
  • 17
  • 56
  • 95
Douglas Rogers
  • 398
  • 2
  • 9

1 Answers1

0

Based on the MDN event documentation, the event object has a method called composedPath which stores:

An array of the objects on which listeners will be invoked. This does not include nodes in shadow trees if the shadow root was created with its ShadowRoot.mode closed.

While this seems to work in Chrome, it is not specified in the documentation how other browsers support this method.

Inspecting the event object further also revealed a event.path property which also contains the same path array, at least in Chrome, however, I could not find any documentation on event.path at all.

Having said that, I did see samples suggesting it might be saver to do var eventPath = event.path || (event.composedPath && event.composedPath()); instead of just using var eventPath = event.composedPath();


Assuming your browser does support it, you can iterate through the path of the clicked element and check if it is a child of the button or the button itself.

The below code demonstrates this and stores the button element reference for further use.

document.getElementById('wrapper').addEventListener('click', function() {
  // actual element clicked
  console.log(event.target);

  var buttonClicked = false;
  var button;

  var eventPath = event.composedPath();
  for (let i = 0; i < eventPath.length; i++) {
    if (eventPath[i].tagName && eventPath[i].tagName.toLowerCase() === 'button') {
      buttonClicked = true;
      button = eventPath[i];
      break;
    }
  }

  if (buttonClicked) {
    console.log(button);
    console.log('button was clicked');
  }
})
<div id="wrapper">
  <button class="c-config__option c-config__option--button c-config__option--pack" data-index="0">
    Item 1
    <span>Subtext</span>
</button>
  <button class="c-config__option c-config__option--button c-config__option--pack" data-index="1">
    Item 2
    <span>Subtext</span>
</button>
</div>

As an alternative you can replicate jQuery.closest using the code from this SO post: DOM / pure JavaScript solution to jQuery.closest() implementation?

Nope
  • 22,147
  • 7
  • 47
  • 72
  • So I DID notice `event.path` come up when you `console.log()` the event in Chrome. I was considering using that but wasn't sure of browser support. This project will be primarily used on a tablet (Android tablet). But also figured there had to be another way since this is pretty easily done with jQuery. – Douglas Rogers Feb 09 '18 at 15:30
  • @DouglasRogers jQuery is open source and if you look at the source code for `closest` that is the most efficient way to go back up the stack. If you develop mainly for android table, would jQuery mobile not suit. It wraps all that up as well for you without the headache of re-producing the code. https://jquerymobile.com/ – Nope Feb 09 '18 at 15:58
  • Alternative solution: https://stackoverflow.com/a/55628227/ – Max Jul 15 '23 at 17:51