I get an issue using custom elements.
Error : Uncaught DOMException: Failed to construct 'CustomElement': The result must not have children
'use strict';
class TestCard extends HTMLDivElement {
constructor() {
super();
this.headerNode = document.createElement('div');
this.bodyNode = document.createElement('div');
this.headerNode.className = 'card__header';
this.bodyNode.className = 'card__body';
this.appendChild(this.headerNode);
this.appendChild(this.bodyNode);
}
connectedCallback() {
this.classList.add('card');
}
static get observedAttributes() {
return ['data-header', 'data-body'];
}
attributeChangedCallback(attrName, oldValue, newValue) {
if (newValue !== oldValue) {
this[attrName.replace('data-', '')] = newValue;
}
}
set header(value) {
this.headerNode.textContent = value;
this.setAttribute('data-header', value);
}
set body(value) {
this.bodyNode.innerHTML = value;
this.setAttribute('data-body', value);
}
}
customElements.define('test-card', TestCard, {
extends: 'div'
});
<div is="test-card" data-header="Title" data-body="Content"></div>
Creating the WebComponent :
var cardNode = document.createElement('div');
cardNode.setAttribute('is', 'test-card');
cardNode.header = header;
cardNode.body = body;
Some things are not allowed in a custom element's constructor. For more info on this check an older answer by me to a similar question).
Amongst others, those are:
class
which is considered under control of the person consuming your component)To achieve what you want to do, use shadow DOM:
class TestComp extends HTMLElement {
headerNode = document.createElement('div');
bodyNode = document.createElement('div');
constructor() {
super();
this.attachShadow({mode: 'open'});
this.headerNode.className = 'card__header';
this.bodyNode.className = 'card__body';
this.bodyNode.part = 'body';
this.shadowRoot.append(this.headerNode, this.bodyNode);
}
connectedCallback() {
this.classList.add('card');
}
static get observedAttributes() {
return ['data-header', 'data-body'];
}
attributeChangedCallback(attrName, oldValue, newValue) {
if (newValue !== oldValue) {
this[attrName.replace('data-', '')] = newValue;
}
}
set header(value) {
this.headerNode.textContent = value;
this.dataset.header = value;
}
set body(value) {
this.bodyNode.innerHTML = value;
this.dataset.body = value;
}
}
customElements.define('test-comp', TestComp);
let newTestComp = new TestComp();
newTestComp.header = 'FOOO';
newTestComp.body = '<ul><li><i>BA</i><b>AA</b>R</ul>';
document.body.append(newTestComp);
test-comp::part(body) { color: green; }
<test-comp data-header="Titre de ma carte" data-body="<h1>Test</h1>"></test-comp>
Be aware that using shadow DOM means outside styles won't affect the styling of elements in the shadow tree. To apply styles to those, create a <style>
element in the constructor, set it's textContent
property to your styles, and append that next to your other elements in the shadow DOM.
Instead of using a style
element, you can also use Constructable Stylesheets. You'll probably need a polyfill because so far Chromium-based browsers are the only ones supporting it, but support is coming in other browsers (Firefox has had it for a while behind a flag: Open new tab, navigate to about:config
and then set layout.css.constructable-stylesheets.enabled
to true
).
To allow styling the component's inside from outside CSS, you can specify which elements are allowed to by styled from the outside using the part="name"
attribute in your shadow DOM and then style it using the ::part(name)
selector in CSS. Added that into the code example.