I have some JS that creates a web component and then proceeds to add it to a very basic HTML page
class WordCount extends HTMLParagraphElement {
constructor() {
// Always call super first in constructor
super();
// count words in element's parent element
const wcParent = this.parentNode;
function countWords(node) {
const text = node.innerText || node.textContent;
return text
.trim()
.split(/s+/g)
.filter((a) => a.trim().length > 0).length;
}
const count = `Words: ${countWords(wcParent)}`;
// Create a shadow root
const shadow = this.attachShadow({ mode: "open" });
// Create text node and add word count to it
const text = document.createElement("span");
text.textContent = count;
// Append it to the shadow root
shadow.appendChild(text);
// Update count when element content changes
setInterval(function () {
const count = `Words: ${countWords(wcParent)}`;
text.textContent = count;
}, 200);
}
}
window.customElements.define("word-count", WordCount, { extends: "p" });
var c1 = document.getElementById('component1');
var header= document.createElement('h1')
header.innerText="Web Component 1"
c1.appendChild(header)
var article = document.createElement('article')
article.setAttribute('contenteditable', '')
c1.appendChild(article)
var h2 = document.createElement('h2');
h2.innerText = "Sample Heading";
article.appendChild(h2);
var p1 = document.createElement('p')
p1.innerText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
article.appendChild(p1)
var p2 = document.createElement('p')
p2.innerText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
article.appendChild(p2)
var p3 = document.createElement('p')
p3.setAttribute('is', 'word-count')
article.appendChild(p3)
customElements.upgrade(p3)
There is some VERY basic HTML that this attaches on to
<div id="component1">
</div>
Ultimately I’m puzzled why the word count doesn’t show. This is based upon the word count webcomponent example from mdn. The only difference here is that I’m building the HTML using JS rather than using the HTML directly.
I tried a few things, such as waiting until the HTML loads by wrapping the js in the DOMContentLoaded
event listener, or using or not using the customElements.upgrade()
method but that didn’t seem to make a difference.
2
Answers
Your
WordCount
class extendsHTMLParagraphElement
, which implies it should be used with an instance of<p>
tag as a customized built-in element. This requires you to first define this customized element using thewindow.customElements.define
method, and to specify the element to extend using the extends option henceis='word-count'
is not a valid custom element definition. Adding this line after you define the class should solve the issue.You can also use
connectedCallback
lifecycle method, which is called when the custom element is connected to the document’s DOM, to start your word count functionality. Try altering your class definition to something like this:First some corrections:
// Always call super first in constructor
The documenation is wrong on this one.
You can call any JS, just not the ‘this’ scope, because
super()
SETS and RETURNS the ‘this’ scopeSee my Dev.to post Web Components #102, 5 more lessons after #101
const wcParent = this.parentNode;
You can’t (always) do this in the
constructor
, there is no Element DOM in theconstructor
phase. If your script will run before there is any DOM in the page this line will return nothing.Use the
connectedCallback
; that is when then Custom Element is connected to the DOM, but do note it fires on the opening tag, thus any HTML inside has not been parsed yet.customElements.define('word-count', WordCount, { extends: 'p' });
is not supported in Safari. Since 2016 Apple has stated they will not implement Customized Built-In Elements, because those elements do not adhere to the (OOP) Liskov Principle.setInterval
is a bit blunt; just waisting CPU cycles.You want to use the MutationObserver API here to only monitor changes in HTML.
A word/letter count Web Component can be done like:
Note the shadowDOM is not required here; a hinderance when you want to style with global CSS