bnassler
bnassler

Reputation: 651

ShadowDOM v1 - Input text changed in lightdom

I am trying to listen for text changes in a custom component

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
    <script type="text/javascript" src="component.js"></script>
  </head>
  <body>
    <fancy-p>
      <p>Bar</p>
      <!-- the input should be listened to -->
      <input id="foo" type="text" name="text" placeholder="Hello"></input>
    </fancy-p>
  </body>
</html>

component.js

customElements.define('fancy-p', class extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
  }
  get foo() {
    return "foo"
  }
  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
      p {
        color: red;
      }
      :host {
        background-color: blue;
      }
      </style>
      <div data-foo="bar">
        <slot></slot>
      </div>
    `;

    let input = this.querySelector("input");
    console.log("input is:" + input);
  }
});

I tried to listen for text changes and use a querySelector in connectedCallback but in Chrome 70.xx the selector returns null.

It seems like when I set a breakpoint that the lightDOM is not filled (i.e. the slot) but then I don't know how to add a event listener to the input.

How can I do that???

Upvotes: 3

Views: 587

Answers (2)

Supersharp
Supersharp

Reputation: 31181

When the connectedCallback() method is called, the <input> element is not appended to the light DOM yet. So you cannot get it with this.querySelector('input') at that time.

It's not really a problem: you can listen to the {input} event on the Custom Element itself, because the event will bubble to the parent element.

this.addEventListener( 'input', ev => console.log( ev.target, ev.target.value ) )  

See the running example:

customElements.define('fancy-p', class extends HTMLElement {
   constructor() {
      super()
      this.attachShadow({mode: 'open'})
          .innerHTML = `
             <style>
               ::slotted(p) { color: red; }
               :host { display:inline-block; background-color: blue; }
             </style>
             <div data-foo="bar">
               <slot></slot>
            </div>`
      this.addEventListener('input', ev => console.log( '%s value is %s', ev.target, ev.target.value))   
   }
})
<fancy-p>
  <p>Bar</p>
  <input id="foo" type="text" name="text" placeholder="Hello">
</fancy-p>

Upvotes: 1

Intervalia
Intervalia

Reputation: 10945

In your example the <input> is placed into a <slot> which means that the code outside of the custom element owns the <input> tag.

In the code below you see that I am using document.querySelector outside of the custom element to get the <input> element and not using this.querySelector or this.shadowRoot.querySelector inside of the custom element code.

customElements.define('fancy-p', class extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
  }
  get foo() {
    return "foo"
  }
  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
      p {
        color: red;
      }
      :host {
        background-color: blue;
      }
      </style>
      <div data-foo="bar">
        <slot></slot>
      </div>
    `;
    let input = this.shadowRoot.querySelector("input");
    console.log('inside input=',input); // input will be null
  }
});


//Since the `<input>` tag is *owned* by the outside DOM you need to get the events from the outside:


let input = document.querySelector("input");
console.log('outside input=', input);
input.addEventListener('input', () => {
  console.log("input is:" + input.value);
});
<fancy-p>
  <p>Bar</p>
  <input id="foo" type="text" name="text" placeholder="Hello"/></fancy-p>

If you want to access it through the shadowDOM then you need to define it in the shadowDOM:

customElements.define('fancy-p', class extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
  }
  get foo() {
    return "foo"
  }
  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
      p {
        color: red;
      }
      :host {
        background-color: blue;
      }
      </style>
      <div data-foo="bar">
        <slot></slot>
        <input id="foo" type="text" name="text" placeholder="Hello">
      </div>
    `;

    let input = this.shadowRoot.querySelector("input");
    console.log('inside input=',input);
    input.addEventListener('input', () => {
      console.log("input is:" + input.value);
    });
  }
});
<fancy-p>
  <p>Bar</p>
</fancy-p>

Upvotes: 3

Related Questions