I am stuck on trying to set an attribute for a custom web component called ws-dialog
I designed. It always adds an attribute undefined
on the code:
<ws-dialog class="global" undefined="add-page-content"></ws-dialog>
What I want to do here is to declare an attribute like template
instead of undefined
. Here is the code that I used for adding an onclick
event to all dialog target elements:
function addDialogEvents(target) {
target.addEventListener("click", () => {
let dialog = document.createElement(Dialog.getName());
let body = document.querySelector("body");
dialog.template = "add-page-content";
body.append(dialog);
});
}
And here is the code for my custom component:
"use strict";
class Dialog extends HTMLElement {
connectedCallback() {
//Styles for this custom component are declared in a separate CSS module
}
updateContent(id) {
let content = document.querySelector(`template#${id}`).content;
if (content)
this.append(content);
}
attributeChangedCallback(name, oldValue, newValue) {
this.TEMPLATE = "template";
switch (name) {
case this.TEMPLATE:
this.updateContent(newValue);
break;
}
}
static get observedAttributes() {
return [this.TEMPLATE];
}
static getName() {
return "ws-dialog";
}
get template() {
return this.getAttribute(this.TEMPLATE);
}
set template(template) {
this.setAttribute(this.TEMPLATE, template);
}
}
customElements.define(Dialog.getName(), Dialog);
Here is a playground to help you see when what method is fired.
Note when and how many times observedAttributes
is called.
That means your this.TEMPLATE
is undefined
, and thus becomes a String undefined
in the Array
<script>
const log = (...args) => {
let div=document.body.appendChild(document.createElement('DIV'));
div.style = `background:${args.shift()};color:white;font:13px Arial`;
div.append(args.join(" "));
}
customElements.define('my-element', class extends HTMLElement {
log() {
log(this.getAttribute("color"), this.outerHTML.split(">")[0],'>', ...arguments);
}
static get observedAttributes() {
log('red', `my-element observedAttributes`);// NO 'this' / Element here!
return ["color"];
}
constructor() { super().log("constructor") }
connectedCallback() {
this.log("connectedCallback" , this.innerHTML || "No innerHTML" );//FireFox difference!
setTimeout(() => this.log(`delayed connectedCallback ${this.innerHTML}`), 0);
}
attributeChangedCallback(name, oldValue, newValue) { // 4th W3C parameter = Namespace (not implemented in Browsers)
this.log("attributeChangedCallback", name, oldValue || "null", newValue);
}
disconnectedCallback(){ this.log("disconnectedCallback") }
})
document.body.onload = () => {
log('magenta', 'onload event');
A.setAttribute("color", "darkolivegreen");
B.innerHTML = "<my-element id=C color=hotpink>Charlie replaced Bravo</my-element>";
B.remove();
}
</script>
<my-element id=A color=green>Alfa</my-element>
<my-element id=B color=blue>Bravo</my-element>
Notes:
Helpful diagram at: https://andyogo.github.io/custom-element-reactions-diagram/
The execution order is slightly different in FireFox only, my advice is to not Develop, only Test in Firefox
In FireFox you can access the inner Elements from the connectedCallback
. In all other Browsers you need that setTimeout
. Different tech interpretations of the same W3C standard. And in this case Apple engineers were correct source. Chromium is based on the same engine.
Note how (delayed) code from the C connectedCallback
(or any method) runs AFTER the disconnectedCallback
.
You need to write code that does not error when DOM elements no longer exist.
JSFiddle playground: https://jsfiddle.net/WebComponents/67pduja9/