Lorenzo Marcon
Lorenzo Marcon

Reputation: 8169

JavaScript events and bubbling

I have the following (simplified) markup code:

<div id="container">
    <div data-value="1">
        <span>Click me 1</span>
    </div>
    <div data-value="2">
        <span>Click me 2</span>
    </div>
</div>
<div id="messages"></div>

I want to take advantage of bubbling by attaching an event listener only on the #container, and get the clicked children's data-value.

document.getElementById('container').addEventListener('click', function(e){
    document.getElementById('messages').innerHTML = 'you clicked ' + e.target.dataset.value;
}, false);

Everything works fine if the clicked area is the div area (in red in the fiddle). How can I get the data-value also when the click comes from a children of the div (e.g. click on a blue span) with the data value without changing the event listener?

Here's the fiddle: http://jsfiddle.net/hgLagy31/1/

Upvotes: 5

Views: 101

Answers (3)

Daniel Imms
Daniel Imms

Reputation: 50189

e.target is the element the user clicked, it is showing undefined since the <span> does not have a data-value attribute. You could go up the tree and find the nearest ancestor that does contain a data-value.

document.getElementById('container').addEventListener('click', function(e) {
  // Find nearest ancestor with data-value defined
  var node = e.target;
  while (!node.dataset.value) {
    node = node.parentNode;
  }
  document.getElementById('messages').innerHTML =
    'you clicked ' + node.dataset.value;
}, false);
#container > div {
  background: red;
}
#container > div > span {
  background: blue;
  color: white;
}
<div id="container">
  <div data-value="1">
    <span>Click me 1</span>
  </div>
  <div data-value="2">
    <span>Click me 2</span>
  </div>
</div>
<div id="messages"></div>

To make this more robust, you could check whether node is undefined before continuing in the while loop and exit if so:

while (!node.dataset || !node.dataset.value) {
  node = node.parentNode;
  if (!node) {
    document.getElementById('messages').innerHTML =
        'Could not find data-value';
    return;
  }
}

document.getElementById('container').addEventListener('click', function(e) {
  // Find nearest ancestor with data-value defined
  var node = e.target;
  while (!node.dataset || !node.dataset.value) {
    node = node.parentNode;
    if (!node) {
      document.getElementById('messages').innerHTML = 'Could not find data-value';
      return;
    }
  }
  document.getElementById('messages').innerHTML =
    'you clicked ' + node.dataset.value;
}, false);
#container > div {
  background: red;
}
#container > div > span {
  background: blue;
  color: white;
}
<div id="container">
  <div data-value="1">
    <span>Click me 1</span>
  </div>
  <div data-value="2">
    <span>Click me 2</span>
  </div>
  <div>
    <span>Click me 3</span>
  </div>
</div>
<div id="messages"></div>

Upvotes: 4

Jay
Jay

Reputation: 1033

As others have mentioned get the target element and then traverse through the .parentNode

Not all browsers support target/toElement, so I use the following polyfill when getting the target element:

var target = e.toElement || e.relatedTarget || e.target || function () { throw "Failed to attach an event target!"; }

Where e is the event

Upvotes: 0

sg.cc
sg.cc

Reputation: 1816

As you asked for a solution that doesn't involve changing the event listener...

#container > div > span {
    background: blue;
    color: white;
    pointer-events: none;
}

This will let the click event bubble straight down to the container.

Upvotes: 0

Related Questions