I'm trying to understand what events that originate in light DOM look like when received in shadow DOM via a <content>
element. I'm reading the Shadow DOM W3C Draft, and I don't entirely understand it but it sounds like events are to be "retargeted" from the point of view of the EventListener attachment.
In the cases where event path is across multiple node trees, the event's information about the target of the event is adjusted in order to maintain encapsulation. Event retargeting is a process of computing relative targets for each ancestor of the node at which the event is dispatched. A relative target is a node that most accurately represents the target of a dispatched event at a given ancestor while maintaining the encapsulation.
and
At the time of event dispatch:
- The Event target and currentTarget attributes must return the relative target for the node on which event listeners are invoked
So here's a simple Polymer custom element that just puts its children into a container, and adds a click EventListener to the container (in the shadow DOM). In this case the child is a button.
<!DOCTYPE html>
<html>
<head>
<script src="bower_components/platform/platform.js"></script>
<link rel="import" href="bower_components/polymer/polymer.html">
</head>
<body unresolved>
<polymer-element name="foo-bar">
<template>
<div id="internal-container" style="background-color:red; width:100%;">
<content></content>
</div>
</template>
<script>
Polymer("foo-bar", {
clickHandler: function(event) {
console.log(event);
var element = event.target;
while (element) {
console.log(element.tagName, element.id);
element = element.parentElement;
}
},
ready: function() {
this.shadowRoot.querySelector('#internal-container').addEventListener('click', this.clickHandler);
}
});
</script>
</polymer-element>
<foo-bar id="custom-element">
<button>Click me</button>
</foo-bar>
</body>
</html>
When I run this on Chrome 38.0.2075.0 canary, when I click on the button I get:
MouseEvent {dataTransfer: null, toElement: button, fromElement: null, y: 19, x: 53…}altKey: falsebubbles: truebutton: 0cancelBubble: falsecancelable: truecharCode: 0clientX: 53clientY: 19clipboardData: undefinedctrlKey: falsecurrentTarget: nulldataTransfer: nulldefaultPrevented: falsedetail: 1eventPhase: 0fromElement: nullkeyCode: 0layerX: 53layerY: 19metaKey: falsemovementX: 0movementY: 0offsetX: 45offsetY: 10pageX: 53pageY: 19path: NodeList[0]relatedTarget: nullreturnValue: truescreenX: 472screenY: 113shiftKey: falsesrcElement: buttontarget: buttontimeStamp: 1404078533176toElement: buttontype: "click"view: WindowwebkitMovementX: 0webkitMovementY: 0which: 1x: 53y: 19__proto__: MouseEvent test.html:17
BUTTON test.html:20
FOO-BAR custom-element test.html:20
BODY test.html:20
HTML test.html:20
and when I click on the container I get:
MouseEvent {dataTransfer: null, toElement: div#internal-container, fromElement: null, y: 15, x: 82…} test.html:17
DIV internal-container test.html:20
So I get an event target in either the light or shadow DOM, depending on which DOM the source element was in. I was expecting to get a target from the shadow DOM in both cases because that's where the EventListener is attached. My questions are:
In case someone wants to ask, "What are you trying to do?", I'm not trying to do anything specifically other than understand the behavior.
Events with shadow dom are tricky. I try to capture a braindump below.
- Is this the way it is supposed to work
Yep. If you're testing in Chrome, you get native shadow dom.
I wrote a section on event retargeting in the HTML5Rocks - Shadow DOM 301 article. Basically, retargeting means that events that originate in the shadow dom look like they come from the element itself.
In your example, you're logging the event internal to the shadow dom, so it's still seen there. If you also add a 'click' listener outside of the element, the target will look as if it came from the element:
<script>
var el = document.querySelector('#custom-element');
el.addEventListener('click', function(e) {
console.log(e.target.tagName); // logs FOO-Bar
});
</script>
http://jsbin.com/womususe/1/edit
The 'click' event bubbles. This is why you see BUTTON
in your top example. Why do you see it at all? You see it because the button is not part of your element's shadow dom. It's in the light dom and the target of the element. It's important to remember that light DOM nodes are still logically in the main document. They're not moved into the shadow dom, merely rendered at <content>
insertion points.
BTW, there are a couple of Polymerized fixes to your examples:
this.shadowRoot.querySelector('#internalcontainer')
-> this.$.internalcontainer
. this.$.ID
is Polymer's "automatic node finding" feature.addEventListener()
at all. Instead , use <div id="internalcontainer" on-click="{{clickHandler}}">
. This is a declarative event handler.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With