I have a situation similar to the example below: from a template
element, I clone a node tree containing custom elements. One custom element is passed data during initialization, represented here by the line infobox.setData(getData())
. The function I use to pass the data (setData
) is added by my custom class, so I make sure the custom element is upgraded before calling it, by passing the node tree to customElements.upgrade
. (I have also tried passing infobox
itself.)
Unfortunately, when I try running my code or the example below I receive the error infobox.setData is not a function
. I have confirmed infobox instanceof InfoBox
is false and the element has no custom properties or methods prior to being connected to the document, so it seems customElements.upgrade
is not upgrading the elements in the tree. What might be preventing it from doing so?
document.getElementById('addWidgetErr').onclick = addWidgetErr document.getElementById('addWidgetWorkaround').onclick = addWidgetWorkaround class InfoBox extends HTMLElement { _data = "" connectedCallback() { this.render() } setData(val) { this._data = val this.render() } render() { if (!this?.isConnected) { return } this.replaceChildren(...this._data.split(' ').map(datum => { const el = document.createElement('span') el.innerText = datum return el })) } } customElements.define('info-box', InfoBox) function addWidgetErr() { const widget = document.getElementById('widgetTemplate').content.cloneNode(true) const infobox = widget.querySelector('info-box') // From my understanding, this should convert all unknown elements in `widget` // into custom elements. customElements.upgrade(widget) console.assert(!(infobox instanceof InfoBox)) console.assert(!('setData' in infobox)) // TypeError: infobox.setData is not a function infobox.setData(getData()) document.getElementById('container').append(widget) } function addWidgetWorkaround() { const widget = document.getElementById('widgetTemplate').content.cloneNode(true) const infobox = widget.querySelector('info-box') document.getElementById('container').append(widget) // works because infobox has been upgraded after being added to the document infobox.setData(getData()) } function getData() { return ('lorem ipsum dolor sit amet consectetur adipiscing elit proin at ' + 'vestibulum enim vestibulum ante ipsum primis in faucibus orci luctus') }
#container { background: lightgrey; padding: 2em; } info-box { display: flex; flex-flow: row wrap; gap: .5em; padding: .5em; background: darkgrey; } info-box>span { background: lightblue; border-radius: .5em; padding: .5em; }
<template id="widgetTemplate"> <details> <info-box></info-box> </details> </template> <button id="addWidgetErr">Add via `upgrade`</button> <button id="addWidgetWorkaround">Add post-hoc</button> <div id="container"></div>
Advertisement
Answer
I narrowed it down to <template>
/ cloneNode
Submit it here: https://github.com/WICG/webcomponents/issues
(Your credits if it is a bug, in other words I don’t have a clue 🙂
class myEl extends HTMLElement {} let el1 = document.createElement("my-el"); let el2 = Object.assign(document.createElement("template"), { innerHTML: "<my-el></my-el>" }).content.cloneNode(true).querySelector("my-el"); customElements.define("my-el", myEl); customElements.upgrade(el1); // works as documented customElements.upgrade(el2); // doesn't upgrade! console.assert(el1 instanceof myEl, "element not upgraded"); console.assert(el2 instanceof myEl, "template not upgraded");