Search code examples
javascriptweb-component

Javascript Web-Components - add subitems


class Table extends HTMLElement {
  // attributes
  constructor() {
    super();
    this.name = 'undefined';
    this.icon = 'bi-patch-question';
  }

  // component attributes
  static get observedAttributes() {
    return ['name', 'icon', 'properties'];
  }

  // attribute change
  attributeChangedCallback(property, oldValue, newValue) {
    if (oldValue == newValue) return;
    this[ property ] = newValue;
  }

  connectedCallback() {
    const shadow = this.attachShadow({ mode: 'open' });

    shadow.innerHTML = `
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
    <div class="card">
    <table class="table">
      <tr>
          <caption>${this.name}<i class="bi ${this.icon}"></i></caption>
      </tr>
      <!-- here should be the em-tds -->
    </table></div>
    
    <style>
    .card {
      border: 1px solid lightgray;
      border-radius: 15px;
      margin: 10px 0;
      padding: 15px;
    }
  
    table {
        width: 100%;
        border-collapse: collapse;
    }

    tr {
      border-top: 1px solid lightgrey;
    }

    tr:first-child {
      border: none;
    }

    td {
      width: 100%;
      padding: 7px;
      font-size: 18px;
      vertical-align: middle;
    }

    caption {
        position: relative;
        font-family: ExtraBold;
        padding: 7px;
        margin-bottom: 5px;
        text-align: left;
        font-size: 18px;
        text-decoration: 2px underline;
    }

    caption i {
      position: absolute;
      right: 6px;
      font-size: 22px;
    }
    </style>
    `
  }
}

class TableTds extends HTMLElement {
  // attributes
  constructor() {
    super();
    this.name = 'undefined';
    this.value = 'undefined';
  }

  // component attributes
  static get observedAttributes() {
    return ['name', 'value'];
  }

  // attribute change
  attributeChangedCallback(property, oldValue, newValue) {
    if (oldValue == newValue) return;
    this[ property ] = newValue;
  }

  connectedCallback() {
    const shadow = this.attachShadow({ mode: 'open' });

    shadow.innerHTML = `
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
    <td>${this.name}</td>
    <td>${this.value}></td>
    `
  }
}




customElements.define('em-table', Table);
customElements.define('em-td', TableTds);
<em-table name="test">
  <em-td name="test" value="10"></em-td>
  <em-td name="test" value="10"></em-td>
</em-table>

I'm working on new web-components for my plattform and ran in some kind of issue. Creating web-components works fine for me but I wanted to create sub-components inside the tags of a web-component. Obviously that has not worked, because the component is protected from everything else...

In my case its about a table web-component, in which I would like to have the html-tds as subcomponents, to later use them properly.

I've tried to use slots but that has not worked...


Solution

  • This should get you started, you need to add more yourself.

    Main point is not to wrap Everything in a shadowDOM,
    let your em-td find their "table", without having to pierce UP through a shadowroot boundary
    with:

        connectedCallback() {
          this.closest("em-table")
              .shadowRoot
              .querySelector("table")
              .append(this.tr);
        }
    
    Working snippet:

    Note: using a declarative shadowDOM <template shadowroot="open"> for em-table here.
    You can move it all to its constructor if you don't want to start from SSR/HTML

    <em-table name="test">
      <template shadowroot="open">
        <div class="card">
          <table class="table">
            <caption></caption>
          </table>
        </div>
        <style>
          tr{background:pink}
        </style>
      </template>
      <em-td name="test1" value="10"></em-td>
      <em-td name="test2" value="20"></em-td>
    </em-table>
    <script>
      customElements.define('em-table', class extends HTMLElement {
        caption(name, icon) {
          let html = `${name}<i class="bi ${icon}"></i>`;
          this.shadowRoot.querySelector("caption").innerHTML = html;
        }
        connectedCallback() {
          this.caption('caption', 'bi-patch-question');
        }
        static get observedAttributes() {
          return ['name', 'icon', 'properties'];
        }
        attributeChangedCallback(property, oldValue, newValue) {
          if (oldValue == newValue) return;
          this[property] = newValue;
        }
      });
      customElements.define('em-td', class extends HTMLElement {
        static get observedAttributes() {
          return ['name', 'value'];
        }
        constructor() {
          super();
          this.tr = document.createElement("tr");
          this._name = document.createElement("td");
          this._value = document.createElement("td");
          this.tr.append(this._name, this._value);
        }
        attributeChangedCallback(property, oldValue, newValue) {
          if (oldValue == newValue) return;
          this[property] = newValue;
        }
        set name(v) {
          this._name.innerText = v;
        }
        set value(v) {
          this._value.innerText = v;
        }
        connectedCallback() {
          this.closest("em-table")
              .shadowRoot.querySelector("table")
              .append(this.tr);
        }
      });
    </script>

    And be aware:

    From the <TR> documentation on MDN:

    https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr

    Permitted parents

    <table> (only if the table has no child <tbody> element, and even then only after any <caption>, <colgroup>, and <thead> elements); otherwise, the parent must be <thead>, <tbody> or <tfoot>

    So

    <em-table>
      <tr>
    

    is not valid HTML