javascripthtmlweb-component

How to Get the Contents of a Custom Element


I'm creating a custom element that will be able to convert its contents from markdown to HTML. However, I'm not able to get the contents of my custom elements.

<!doctype html>
<html>
<body>
   <template id="mark-down">
      <div class="markdown"></div>
   </template>
   <!-- Converts markdown to HTML -->
   <script src="https://cdn.jsdelivr.net/gh/showdownjs/showdown/dist/showdown.js"></script>
   <script>
      customElements.define('mark-down',
         class extends HTMLElement {
            constructor() {
               super()
               let template = document.querySelector('#mark-down').content
               this.attachShadow({ mode: 'open' }).appendChild(template.cloneNode(true))
            }
            connectedCallback() {
               console.log(this) // Returns the whole <mark-down> node and its contents
               console.log(this.innerHTML) // So why does this return a blank string?
               // This should theoretically work --> let markdown = this.innerHTML
               let markdown = '## Test'
               let converter = new showdown.Converter()
               let html = converter.makeHtml(markdown)
               this.shadowRoot.innerHTML = html;
            }
         });
   </script>

   <main>
      <mark-down>
## Our Markdown

These contents should get converted

* One
* Two
* Three
      </mark-down>
   </main>
</body>
</html>

My issue is in the connectedCallback(). When logging this, I get the whole <mark-down> node with its contents in markdown. However, it doesn't seem to have valid properties. Using innerHTML returns a blank, where it should return the markdown; other combinations, like this.querySelector('mark-down'), return null.

What can I do to get the contents of my custom element?


Solution

  • I wrote a (very) long Dev.to Blogpost:
    Web Component Developers do not connected with the connectedCallback yet


    The easiest workaround is a setTimeout in the connectedCallback

    <script>
      customElements.define('my-element', class extends HTMLElement {
        connectedCallback() {
          console.log(this.innerHTML);// "" in all Browsers
          setTimeout(() => {
            // now runs asap 
            console.log(this.innerHTML); // "A"
          });
        }
      })
    </script>
    
    <my-element>A</my-element>
    

    What this and all mentioned workarounds do is postpone code execution until the DOM is fully parsed.
    setTimeout runs after DOMContentLoaded, but if you wrap everything in DOMContentLoaded the whole Element creation runs late, same applies for defer or placing <script> at the end of your page

    Supersharp explains the why better in:

    wait for Element Upgrade in connectedCallback: FireFox and Chromium differences