Search code examples
htmldomweb-component

web component - accept no child elements


How can I define a web component that works like <img> in that it accepts no child elements?

  <script>
    const QA = (q, d) => Array.prototype.slice.call((d||document).querySelectorAll(q), 0);
    const QS = (q, d) => (d||document).querySelector(q);
  </script>

  <template id="push-message">
    <style>
      message { display: grid; font-family: sans-serif; background: #eee; outline: 1px solid; }
      .badge {  }
    </style> 
    <message>
      <img class="badge">
      <img class="icon">
      <img class="image">
    </message>
  </template>

  <script>    
    const wpm = 'push-message';
    customElements.define(wpm,
      class extends HTMLElement {
        constructor() { 
          super();
          const l = QS(`#${wpm}`).content.cloneNode(true);
          const s = this.attachShadow({ mode: 'open' }); s.appendChild(l);
        }
        QS(q) { return QS(q, this.shadowRoot); }
        QA(q) { return QA(q, this.shadowRoot); }  
        static get observedAttributes() { return [ "badge", "icon", "image" ]; }
        attributeChangedCallback(a, o, n) {
          if (/^(badge|icon|image)$/.test(a))
            this.QS(`.${a}`).src = n;
        }
      });
  </script>

  <push-message 
     badge="//google.com/favicon.ico"
      icon="//google.com/favicon.ico"
     image="//google.com/favicon.ico">
  <p>ok</p>

DOM should be

<push-message></push-message>
<p></p>

not

<push-message><p></p></push-message>

and ok should display in the result.


Is there a way to change customElements.define to avoid having to explicitly close <push-message></push-message> and just use <push-message> but have it implicitly self-close?


Solution

  • Autonomous Custom Elements require a closing tag: Do custom elements require a close tag?

    You can create a Customized Built-In Element extended from HTMLImageElement
    to get a a self-closing IMG tag:

    <img is="push-message" badge="//google.com/favicon.ico">
    <img is="push-message" icon="//google.com/favicon.ico">
    <img is="push-message" image="//google.com/favicon.ico">
    <p>ok</p>
    

    But an IMG can only have one src, so you might as well create 3 elements and use

    <img is="message-badge">
    <img is="message-icon">
    <img is="message-image">