Nothing yet
Nothing yet
Nothing yet

Change custom element content before and after attaching to the DOM

54 Views Asked by At

I created a custom HTML element that creates a new element from the following template:

<template id="track">
  <div class="record">Nothing yet</div>
</template>

Here the JS code that defines the custom element:

class Track extends HTMLElement {

  constructor() {
    super();    
    this.template = document.getElementById("track");
    this.clone = this.template.content.cloneNode(true);
  }
    
  connectedCallback() {    
    this.append( this.clone );
  }
  
  set record(html) { 
    // Case 1: Works only for element 1 (before appending to the DOM)
    this.clone.querySelector('.record').innerHTML = html;
    // Case 2: Works only for element 2 (after appending to the DOM)
    //this.querySelector('.record').innerHTML = html;
  }
}


customElements.define('my-track', Track);

Note the record property that changes the HTML content of the <div>. I would like to be able to update the element content whatever it has been attached or not the DOM. I can't write a code that works in both cases (before and after appending the custom element to the DOM). The following code only works in case 1:

const el1 = document.createElement('my-track');
el1.record = "Bla<b>bla</b> #1";
document.body.append(el1);

The following code only works in case 2

const el2 = document.createElement('my-track');
document.body.append(el2);
el2.record = "Bla<b>bla</b> #2";

Of course, in the method set record(), I could check if the element has already been attached to the DOM and switch to case 1 or case 2 accordingly. But I feel this is not the right way to build custom elements.

What is the best option to update element content in the method set record() whether or not the element has been attached to the DOM ?

I create a JSFiddle here: https://jsfiddle.net/Alphonsio/gL8a239o/13/

1

There are 1 best solutions below

3
Danny '365CSI' Engelman On

You can not write anything to DOM when there is no DOM.

Just like you can not put a sofa in your livingroom when there is no house yet.

That means you have to check with this.isConnected if the element is attached to the DOM, in the set record method.

Its an instance of a JavaScript class/object; You can of course attach anything you want to it and have the constructor/attributeChangedCallback/connectedCallback process it (but really think trough the house/livingroom scenario)

Note shadowDOM allows you to create any DOM you want and not have it display in the main DOM (yet) (same applies to a DocumentFragment)

const createElement = (tag, props = {}) => Object.assign(document.createElement(tag), props);

customElements.define('my-track', class extends HTMLElement {
  connectedCallback() {
    console.warn("connectedCallback" , this.id);
    this.append(
      this.recordDIV = createElement("div", {
        innerHTML: `Default Record content`
      }))
  }

  set record(html) {
    if (this.isConnected)
      this.recordDIV.innerHTML = html;
    else
      console.warn(this.id, "Not in the DOM yet")
  }
});

const el1 = createElement('my-track', { id: "el1" });
el1.record = "Record " + el1.id;
document.body.append(el1);

const el2 = createElement('my-track', { id: "el2" });
document.body.append(el2);
el2.record = "Record " + el2.id;

Added shadowDOM example

Or build your own https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment

const createElement = (tag, props = {}) => Object.assign(document.createElement(tag), props);

customElements.define('my-track', class extends HTMLElement {
  constructor(){
    super().attachShadow({mode:"open"})
    .append(
      this.recordDIV = createElement("div",{
        innerHTML: `Default Record content ` + this.id      
      })
    )
  }

  set record(html) {
    this.recordDIV.innerHTML = html;
  }
});

const el1 = createElement('my-track', { id: "el1" });
el1.record = "Record " + el1.id;
document.body.append(el1);

const el2 = createElement('my-track', { id: "el2" });
document.body.append(el2);
el2.record = "Record " + el2.id;