I would like to create a web ponent 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>
...
I would like to create a web ponent 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>
...
Share
Improve this question
asked Dec 15, 2019 at 20:49
Max888Max888
3,8004 gold badges34 silver badges76 bronze badges
1
- This can be isolated from webponents. You may have tried appendChild &cie, what was the problem – grodzi Commented Dec 15, 2019 at 21:14
3 Answers
Reset to default 2In 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>
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 ponents 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);
Extending on Supersharps answer:
attachShadow()
setsthis.shadowRoot
by defaultattachShadow()
returns the shadowRoot, so you can chain.innerHTML
on itappendChild()
returns the appended DIV, so you can chain it
class MyElement extends HTMLElement {
constructor() {
super() // sets AND return 'this' scope
.attachShadow({mode: "open"})
.innerHTML = '<input type="text"></input><button>add div</button>';
this.shadowRoot.querySelector('button').onclick = evt =>
this.shadowRoot
.appendChild(document.createElement("div"))
.innerHTML = this.shadowRoot.querySelector("input").value
}
}
customElements.define("my-element", MyElement)
<my-element></my-element>
Or rewrite it to one helper function $append
which makes the rest of the code readable
class MyElement extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: "open"});
let $append = ( tag, html = '' ) => (
tag = this.shadowRoot.appendChild(document.createElement(tag)),
tag.innerHTML = html,
tag // return tag, so onclick can be chained
);
let input = $append('input');
$append('button', 'add div').onclick = evt => $append("div", input.value);
}
}
customElements.define("my-element", MyElement)
<my-element></my-element>