ShadowDom experiments

by Nicholas Berlette

HTML

<div contenteditable="true">
  Custom Web Components! Try editing this text and watch the counter keep up!
  
  <div is="word-count"></div>
</div>

JavaScript

class WordCount extends HTMLDivElement {
	static get observedAttributes() {
  	return ["textContent", "innerText"];
  }
  
  static count(text){
      return text.trim().split(/\s+/g).filter(a => a.trim().length > 0).length;
  }
  
  constructor() {
    // Always call super first in constructor
    super();
    
    this._shadow = this.attachShadow({mode: 'open'});
	}
  
  connectedCallback() {
    const text = document.createTextNode(this.textContent);
    this._shadow.appendChild(text);
    this.update();
    setInterval(() => this.update(), 500);
  }
  update() {
    if (!this._count) {
      this._count = document.createElement("span");
      this._count.id = "__word_count";
      this._count.style.fontSize = "0.8em";
      this._count.style.border = "1px dashed #999";
      this._count.style.display = "block";
      this._count.style.marginTop = "5em";
      this._count.style.padding = "0.5em 1em";
      this._count.style.borderRadius = "1em";
      this._count.contenteditable = true;    
      this._count.innerText = this.render();
      this._shadow.appendChild(this._count);
    } else {
      this._count.innerText = this.render();
    }
  }
  render() {
  	return `Words: ${WordCount.count(this.parentNode.textContent)}`;
  }
  
  attributeChangedCallback(name, oldValue, newValue) {
  	console.log("Attribute Changed: (%s) - %s -> %s", name, oldValue, newValue); 
    // count words in element's parent element
		if (name === "textContent") {
      this._count.innerText = `Words: ${WordCount.count(newValue)}

Old Value: ${oldValue}
New Value: ${newValue}
`;
    }
  }
}

// Define the new element
customElements.define('word-count', WordCount, { extends: 'div' });