Search code examples
javascripthtmlweb-componentshadow-domlit-element

get value from selected option in web component


I'm trying to get the value of my selected option, so when someone uses my web component they can access it. I think the problem has to do with the shadow root

__createOptions() {
    const SELECT = this.shadowRoot.querySelector('select');
    SELECT.addEventListener('change', event => {
        this.value= event.target.value;
    });
    this.shadowRoot.addEventListener('slotchange', () => {
        const OPTION = this.querySelector('option');
        if (OPTION) {
            SELECT.append(OPTION);
        }
    });
}

render() {
    return html`
    <div class="selectWrapper">
        <select id="typeDropdown"></select>
    </div>
    <slot></slot>
`;
}

  <wc-select value="">
   <option value="1">Option 1</option>
   <option value"2">Option 2</option>
   <option value="3">Option 3</option>
  </wc-select>

Solution

  • I was too quick in my comment.

    <SLOTs> can not be targeted like 'normal' DOM elements

    (like many) You are running into the trap of thinking slotted content
    is MOVED to ShadowDOM <slots>

    It is NOT.

    slotted content is only REFLECTED in shadowDOM, it still remains invisible! in lightDOM

    You can not access the reflected content with .querySelector or .children[]
    ... because it is not there (in shadowDOM).. it still is in lightDOM.

    For the same reason you style slotted content in lightDOM:
    Use CSS selectors like :first-child inside shadow dom


    To append lightDOM <OPTIONs> into a shadowDOM <SELECT`>

    1. You either move them from lightDOM to shadowDOM:

        let select = this.shadowRoot.querySelector('select');
        let host = this.shadowRoot.getRootNode().host;
        let options = host.querySelectorAll('option');
        select.append(...options);
    

    #1 is the easiest one, as it does not require any <slots> in shadowDOM

    2. You were on the right track with the slotchange event
    it requires a (named/unnamed) <slot></slot> in the shadowDOM template.
    You find your lightDOM nodes in:

    BE AWARE, this gets you ALL nodeTypes including text nodes because there are linebreaks and whitespace in the <my-element> innerHTML!

        <my-element>
          <option>Grow up</option>
          <option>Learn React</option>
          <option>Learn Lit</option>
          <option>Forget W3C standard Custom Elements API</option>
          <H1 slot=title>My Never Todo List</hH>
        </my-element>
    

    Luckily the <SELECT> doesn't care, so you can dump assignedNodes straight in..

        this.shadowRoot.addEventListener('slotchange', (evt) => {
          if (!evt.target.name) { // only for unnamed slot
            this.shadowRoot.querySelector('select')
                .append(...evt.target.assignedNodes());
          }
        });
    

    note! The <options> were reflected to the UNnamed slot,
    <H1 slot=title> reflected to the <slot name=title>

    (they should have named them reflections instead of slots)

    Click Show Code Snippet for complete code

    customElements.define("my-element", class extends HTMLElement {
      connectedCallback() {
        let template = document.getElementById(this.nodeName);
        this.attachShadow({
          mode: 'open'
        }).append(template.content.cloneNode(true));
    
        this.shadowRoot.addEventListener('slotchange', (evt) => {
          if (!evt.target.name) { // only for unnamed slot
            let select = this.shadowRoot.querySelector('select');
            select.append(...evt.target.assignedNodes());
          }
        });
    
      }
    })
    <template id=MY-ELEMENT>
      <style>
        :host {
          display: block;
        }
    
        select{
          font-size:1.5em;
        }
    
      </style>
      <slot name=title></slot>
      <select multiple>
      </select>
      <slot></slot>
    </template>
    
    <my-element>
      <option>Grow up</option>
      <option>Learn React</option>
      <option>Learn Lit</option>
      <option>Forget W3C standard Custom Elements API</option>
      <h1 slot=title>My Never Todo List</h1>
    </my-element>

    JSFiddle playground with both options: https://jsfiddle.net/CustomElementsExamples/v2f9zmu5/


    More SLOT related answers can be found with StackOverflow Search: Custom Elements SLOTs