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?
I have been bitten by this so much, I purposely asked a StackOverflow question for people to find
wait for Element Upgrade in connectedCallback: FireFox and Chromium differences
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
After some research online, I found the following nugget about connectedCallback
: each time the custom element is appended into a document-connected element. This will happen each time the node is moved, and may happen before the element's contents have been fully parsed.
Therefore, depending on the browser, innerHTML
may not in fact be defined when being used. That's why the above snippet, while fine in Firefox, doesn't work in Chrome or Edge.
To solve this problem, place the script
tags at the bottom of the body
, in which case the element will be parsed first, and the script will know what innerHTML
contains.
Another way around this is to wrap the custom element constructor inside a DOM loaded event. That event would look like so:
document.addEventListener('DOMContentLoaded', (e) => {
class markDown extends HTMLElement {
...
}
}
Yet another way of doing this is putting your script in a separate file, and marking the script
tag with the defer
attribute.
All three solutions work whether or not the class is explicitly named and defined by a separate statement, as mentioned by Triby's answer, or anonymous and wrapped by the custom element definition function, as in the original question.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With