18

I am trying to find out the path through which an event has bubbled. For example , I have a mark up like

 <div id="container" onclick="func">
        <div id="div1"></div>
        <div id="div2">
            <div id="div2.1"></div>
            <span id="span2.2"></span>
            <div id="div2.3">
                <button id="btn2.3.1"></button>
            </div>
        </div>
    </div>

Now if btn2.3.1 was clicked, I wish to see the entire path the event has bubbled up through which is btn2.3.1 -> div2.3 -> div2 ->container . Is there a way of doing this with only putting a handler on the container ? (No Jquery please)

I found a event.path array.Which does this stuff, but couldn't find much details about it.Is it cross browser? What is the correct way to achieve this ?

T J
  • 41,966
  • 13
  • 81
  • 134
Abhik
  • 1,870
  • 7
  • 27
  • 46

5 Answers5

34

event.path || event.composedPath()

event.path

Dis/Un-covered by a note in the polymer project documentation and via an HTML5Rocks article, path is a family tree in the form of an Array.

It appears to be an "extension to the event interface" only exposed via the Web Component Shadow DOM, and is standard only in this respect (apparently), not a lot of documentation seems available, and it isn't exposed (by default) in all browsers.

event.composedPath() to the rescue!

Another question about the use of path was answered with a suggestion to use composedPath...

MDN's documentation about event.composedPath() describes it as follows:

The composedPath() method of the Event interface returns the event’s path which is 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.

It is described by WHATWG in their "DOM specs" documentation about the "event path" as follows:

Returns the invocation target objects of event’s path (objects on which listeners will be invoked), except for any nodes in shadow trees of which the shadow root’s mode is "closed" that are not reachable from event’s currentTarget.

Can I use... states that browser support of composedPath() is widespread, with IE and Edge trailing behind with no foreseeable support, and MDN agrees.

WHATWG's documentation about "dispatching events" details the conditions under which "event's path" will have items appended.

Details correct September 25, 2019

Practical demo

const green = document.getElementById( 'green' ),
      msg = document.querySelector( 'output' );

document.getElementById( 'red' ).addEventListener( 'click', evt => {
  msg.innerHTML = '"' + evt.target.id + '" got poked, and "green" was' +
  
  /* access to the event path */
  ( ~evt.composedPath().indexOf( green ) ? '' : "<b>n't</b>" )
  
  + ' in the path.';
} );
div { display: inline-block; padding: 1em 3em 1em 1em; cursor: pointer }
output { font-family: monospace; display: block; margin-top: 1em }
#red { background: red }
#green { background: green }
#blue { background: blue }
<div id="red">
  <div id="green">
    <div id="blue"></div>
  </div>
</div>
<output>Poke the DOM!</output>
Community
  • 1
  • 1
Fred Gandt
  • 3,948
  • 2
  • 31
  • 39
  • exactly this is the answer I was looking for – Abhik May 21 '15 at 05:12
  • 2
    This works great in Chrome, but doesn't seem to be implemented in Firefox or IE yet? Or am I missing something? – Adrian Schmidt Jul 14 '15 at 10:10
  • 2
    @AdrianSchmidt Nope, you're not missing *something*. Bummer right? Yup. – Fred Gandt Jul 15 '15 at 14:24
  • @FredGandt To say the least... :( – Adrian Schmidt Jul 16 '15 at 09:14
  • I added a note about it. I suppose it's worth mentioning that it's not yet the be all and end all for this *problem*. Who knows though - next week it could be. We're on the bleeding edge! Dun dun duuuun! Alternatively it could be like another `font-stretch`. Wouldn't that be just spiffy? -_- – Fred Gandt Jul 16 '15 at 21:38
  • 2
    `event.path` is **not standard**. The `event path` in the quote is just like a local variable created when dispatching an event. But it's not exposed in the event. – Oriol Jan 22 '16 at 03:48
  • @Oriol - It is standard, but apparently only to the Shadow DOM; it's there, but hidden. I have added notes to the answer. Although I haven't made any efforts to establish how, it should be possible to use `event.path` in any modern browser with a bit of jiggery-pokery. – Fred Gandt Jan 23 '16 at 06:06
  • 2
    @FredGandt I don't see `path` defined in the Shadow DOM draft neither. There is a [`deepPath`](https://w3c.github.io/webcomponents/spec/shadow/#widl-Event-deepPath-sequence-EventTarget) method, but it does something different. – Oriol Jan 23 '16 at 16:18
  • Moar links - https://www.w3.org/Bugs/Public/show_bug.cgi?id=21066 via https://code.google.com/p/chromium/issues/detail?id=234030 ... I dunno. As usual, Chrome is way ahead of the rest. – Fred Gandt Jan 24 '16 at 05:43
22
function handleClicks(e) {
    var path = [];
    var node = e.target;
    while(node != document.body) {
       path.push(node);
       node = node.parentNode;
    }
    console.log(path);
}

document.body.addEventListener('click', handleClicks);
Andrei Lesnitsky
  • 958
  • 6
  • 12
  • So no other way other than looping ?? – Abhik Oct 04 '14 at 18:40
  • Yes, I think there is no other same perfomance and memory cost way – Andrei Lesnitsky Oct 04 '14 at 22:04
  • 6
    Some browsers (Chrome) now supports [event.path](https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#h-event-flow) property, which is event's propagation path `NodeList`. – raidendev Feb 06 '15 at 09:04
  • What would be the best way to add this as a polyfill? – Nikos Mar 22 '17 at 11:04
  • 2
    This solution won't work if the elements are not children of each other, but overlapping based on absolute positioning for example. – HammerNL Sep 11 '17 at 09:57
  • While not the list the op is requesting, be aware that `Element.closest()` polyfill exists and may help in other situations .. https://developer.mozilla.org/en-US/docs/Web/API/Element/closest – farinspace Sep 29 '20 at 23:37
1

I had a similar requirement where I was listening to event on document and wanted to know if the event originated in a particular div. I handled it by adding and later checking a specific class name on event.target.

var div1 = document.getElementById('div1');
var div2 = document.getElementById('div2');

document.addEventListener('click', function(e) {
  if (e.target.classList.contains('via-div1')) {
    alert('Event came through div1');
  } else if (e.target.classList.contains('via-div2')) {
    alert('Event came through div2');
  } else {
    alert('Event came from outside the divs');
  }
});

div1.addEventListener('click', function(e) {
  e.target.classList.add('via-div1');
});

div2.addEventListener('click', function(e) {
  e.target.classList.add('via-div2');
});
<div id="div1" style="background: #8bc34a"><span>div 1</span></div>
<div id="div2" style="background: #00bcd4">
  <span>div 2</span>
  <div id="div2-1"><span>div 2-1</span></div>
  <button id="btn2-2">button 2-2</button>
</div>
0

There is now a small GitHub project / NPM module called event-propagation-path that acts as a polyfill. Check it out here:

event-propagation-path @ GitHub

event-propagation-path @ NPM

zbr
  • 6,665
  • 3
  • 30
  • 42
0

Let's assume that we what to find the event path inside the HTML table tag.

<tabe id="tab">
.
.
.
</table>

The following JavaScript code will return the event's element after every event.

window.onload = function(){
var tab = document.getElementById('tab');
tab.onclick = function(event) {
var target = getTargetElement(event);
console.log(target);
};
}
function getTargetElement(e) {
e = e || window.event;
return e.target || e.srcElement;
}
Inamur Rahman
  • 2,264
  • 22
  • 22