11

Anchor elements (<a>) are created when the user interacts with a web component. The problem is, that I cannot get the anchor element returned from the "outside" of the web component when an anchor is clicked.

I add an event listener to document listening for click events. When an element somewhere in the DOM is clicked I expect the e.target to be the clicked element. In the case of a click somewhere inside the web component the custom element (<fancy-list></fancy-list>) will be returned - not the clicked element.

When the mode of the shadow DOM is set to open the DOM should be accessible.

class Fancylist extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });

    const wrapper = document.createElement('div');
    wrapper.innerHTML = `<ul></ul><button>Add item</button>`;

    shadow.appendChild(wrapper);

    this.on_root_click = this.on_root_click.bind(this);
  }

  connectedCallback() {
    this.ul_elm = this.shadowRoot.querySelector('ul');
    this.shadowRoot.addEventListener('click', this.on_root_click, false);
  }

  on_root_click(e){
    switch(e.target.nodeName){
      case 'BUTTON':
        this.ul_elm.innerHTML += '<li><a href="p1">List item</a></li>';
        break;
      case 'A':
        e.preventDefault();
        console.log('You clicked a link!');
        break;
    }
  }
}

customElements.define('fancy-list', Fancylist);
<!DOCTYPE html>
<html>
  <head>
    <title>List</title>
    <meta charset="utf-8" />
    <script type="text/javascript">
      document.addEventListener('DOMContentLoaded', e => {
        document.body.addEventListener('click', e => {
          //console.log(e.composedPath());
          console.log(e.target); // why is this not returning an anchor element when an anchor is clickend inside the <fancy-list>?
        }, false);
      }, false);
    </script>
  </head>
  <body>
  <h1>List</h1>
  <fancy-list></fancy-list>
  </body>
</html>
leonheess
  • 16,068
  • 14
  • 77
  • 112
chrwahl
  • 8,675
  • 2
  • 20
  • 30

1 Answers1

24

The purpose of the Shadow DOM is precisely to mask the HTML content the Shadow DOM from the containter point of view.

That's also why inner events are retargeted in order to expose the Shadow DOM host.

However, you can still get the real target by getting the first item of the Event.path Array property.

event.path[0]

NB: of course it will work only with open Shadow DOM.

class Fancylist extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });

    const wrapper = document.createElement('div');
    wrapper.innerHTML = `<ul></ul><button>Add item</button>`;

    shadow.appendChild(wrapper);

    this.on_root_click = this.on_root_click.bind(this);
  }

  connectedCallback() {
    this.ul_elm = this.shadowRoot.querySelector('ul');
    this.shadowRoot.addEventListener('click', this.on_root_click, false);
  }

  on_root_click(e){
    switch(e.target.nodeName){
      case 'BUTTON':
        this.ul_elm.innerHTML += '<li><a href="p1">List item</a></li>';
        break;
      case 'A':
        e.preventDefault();
        break;
    }
  }
}

customElements.define('fancy-list', Fancylist);
<!DOCTYPE html>
<html>
  <head>
    <title>List</title>
    <meta charset="utf-8" />
    <script type="text/javascript">
      document.addEventListener('DOMContentLoaded', e => {
        document.body.addEventListener('click', e => {
          console.log(e.path[0]);
        }, false);
      }, false);
    </script>
  </head>
  <body>
  <h1>List</h1>
  <fancy-list></fancy-list>
  </body>
</html>

Update 2021

As commented now you should use event.composedPath().

Supersharp
  • 29,002
  • 9
  • 92
  • 134
  • 2
    Thank you for the answer. It does not work in Firefox and it made me search for more answers -- I found this: https://stackoverflow.com/a/39245638/322084 apparently we have different implementations of that functionality, Event.path and Event.composedPath(). – chrwahl Sep 18 '19 at 06:53
  • 2
    Use `event.composedPath()` instead of `event.path` – leonheess Mar 06 '21 at 01:16
  • Regrettably, the event `:target` is missing in the shadow root of the web components. There are no sign that any of the browsers will fix this so far. I wonder if they are waiting for the W3C to do something or some other standard entity. – acarlstein Apr 29 '21 at 19:33