Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically add elements inside web component

I would like to create a web component that contains a list of elements that can be added to. For example if I had an initial template like:

const template = document.createElement("template");
template.innerHTML = `<input type="text"></input><button>add div</button>`;

class MyElement extends HTMLElement {
  constructor() {
    super();
    this._shadowRoot = this.attachShadow({ mode: "open" });
    this._shadowRoot.appendChild(template.content.cloneNode(true));
    const button = this._shadowRoot.querySelector("button");
    button.addEventListener("click", this.addDiv);
  }
  addDiv(e) {
    // ...
  }
}
customElements.define("my-element", MyElement);

and each time the button is clicked, a <div> is added which contains the text from the input field, thereby creating something like:

<input type="text"></input><button>add div</button>
<div>first text from input added</div>
<div>second text from input added</div>
...
like image 417
Max888 Avatar asked Sep 04 '26 02:09

Max888


2 Answers

In your case, you cannot use insertAjacentHTML() on the Shadow DOM shadowRoot property because the Shadow Root doesn't implement the Element interface.

Use bind( this )

A better solution is to use appendChild() on the shadowRoot property. However, you'll need to add a special bind() operation on the click event callback.

In an event callback, this indeed references the element that triggered the event, not the object which the callback is defined in.

In order to get the reference to the custom element (in order to access the input element in the Shadow DOM shadowRoot, call bind(this) inside addEventListener().

button.addEventListener( "click", this.addDiv.bind( this ) )

See a full example below:

const template = document.createElement("template");
template.innerHTML = `<input type="text"></input><button>add div</button>`;

class MyElement extends HTMLElement {
  constructor() {
    super();
    this._shadowRoot = this.attachShadow({ mode: "open" });
    this._shadowRoot.appendChild(template.content.cloneNode(true));
    const button = this._shadowRoot.querySelector("button");
    button.addEventListener("click", this.addDiv.bind( this ) );
  }
  addDiv(e) {
    var div = document.createElement( 'div' )
    div.textContent = this.shadowRoot.querySelector( 'input' ).value
    this.shadowRoot.appendChild( div )
  }
}
customElements.define("my-element", MyElement);
<my-element></my-element>

Use arrow function

Another solution whould be to use an arrow function. With arrow function, this is not redefined, so you don't need to use bind().

class MyElement extends HTMLElement {
  constructor() {
    super()
    const sh = this.attachShadow( { mode: "open" } )
    sh.innerHTML = `<input type="text"></input><button>add div</button>`
    const button = sh.querySelector( "button" )
    button.onclick = ev => {
      let div = document.createElement( "div" )
      div.textContent = sh.querySelector( "input" ).value
      sh.appendChild( div )
    }
  }
}
customElements.define( "my-element", MyElement )
<my-element></my-element>
like image 57
Supersharp Avatar answered Sep 05 '26 16:09

Supersharp


Thanks to Danny's answer I realised I don't need to create this._shadowRoot, so my question and Supersharp's answer can be simplified to the below. I've kept the template because it is good practice to create web components from templates because performance is better than using shadowRoot.innerHTML.

const template = document.createElement("template");
template.innerHTML = `<input type="text"></input><button>add div</button>`;

class MyElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.shadowRoot.appendChild(template.content.cloneNode(true));
    const button = this.shadowRoot.querySelector("button");
    button.addEventListener("click", this.addDiv.bind(this));
  }
  addDiv(e) {
    const div = document.createElement("div");
    div.textContent = this.shadowRoot.querySelector("input").value;
    this.shadowRoot.appendChild(div);
  }
}
customElements.define("my-element", MyElement);
like image 31
Max888 Avatar answered Sep 05 '26 16:09

Max888



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!