Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How are Shadow DOM events from under <content> targeted?

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:

  1. Is this the way it is supposed to work, and
  2. If so, is there an alternative way to get events that bubble up from the light DOM retargeted to the shadow DOM?

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.

like image 214
rhashimoto Avatar asked Jun 29 '14 22:06

rhashimoto


1 Answers

Events with shadow dom are tricky. I try to capture a braindump below.

  1. 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:

  1. this.shadowRoot.querySelector('#internalcontainer') -> this.$.internalcontainer. this.$.ID is Polymer's "automatic node finding" feature.
  2. You don't need to use addEventListener() at all. Instead , use <div id="internalcontainer" on-click="{{clickHandler}}">. This is a declarative event handler.
like image 141
ebidel Avatar answered Nov 16 '22 01:11

ebidel