1

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?

Cetin Sert
  • 4,497
  • 5
  • 38
  • 76
  • While you can't do it now it sure would be a nice upgrade. And, it _shouldn't_ really be that hard to implement. Just add an option when calling `define`. – Intervalia May 30 '19 at 14:44

2 Answers2

3

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">
Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49
2

Self-closing tags as known as void elements.

AFAIK, it is not possible to create custom void elements. In summary, it needs changing browser HTML parsers which is not an easy thing to pull by the web community. Changes are required because of the way the browser implements a tag-soup algorithm.

Thus you will need a closing tag. You can read more about this here:

On a side note, if you have your own template compiler/parser like vue-compiler and ng-compiler, you can probably instruct it to understand self-closing custom elements at a build time. However, the benefits of achieving this are virtually non-existent.

Harshal Patil
  • 17,838
  • 14
  • 60
  • 126