[英]Iterate over HTMLCollection in custom element
如何在另一个自定义元素的影子dom中迭代一个自定义元素的实例? HTMLCollections似乎不符合预期。 (关于vanilla js,我是jQuerian和新手,所以我确定自己在某个地方犯了一个明显的错误)。
的HTML
<spk-root>
<spk-input></spk-input>
<spk-input></spk-input>
</spk-root>
自定义元素定义
对于spk-input
:
class SpektacularInput extends HTMLElement {
constructor() {
super();
}
}
window.customElements.define('spk-input', SpektacularInput);
对于spk-root
:
let template = document.createElement('template');
template.innerHTML = `
<canvas id='spektacular'></canvas>
<slot></slot>
`;
class SpektacularRoot extends HTMLElement {
constructor() {
super();
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(template.content.cloneNode(true));
}
update() {
let inputs = this.getElementsByTagName('spk-input')
}
connectedCallback() {
this.update();
}
}
window.customElements.define('spk-root', SpektacularRoot);
这是我不明白的部分。 在update()
方法内部: console.log(inputs)
返回一个HTMLCollection:
console.log(inputs)
// output
HTMLCollection []
0: spk-input
1: spk-input
length: 2
__proto__: HTMLCollection
但是,HTMLCollection不能使用for
循环进行迭代,因为它没有长度。
console.log(inputs.length)
// output
0
搜索SO发现HTMLCollections类似于数组,但不是数组。 尝试使用Array.from(inputs)
或spread运算符将其设置为数组会导致一个空数组。
这里发生了什么? 如何从update()
方法迭代spk-root
的spk-input
元素?
我正在使用gulp-babel和gulp-concat并使用Chrome。 让我知道是否需要更多信息。 提前致谢。
编辑 :澄清console.log(inputs.length)
从update()
内调用console.log(inputs.length)
update()
输出0
而不是2
。
原因是在某些情况下,自定义元素的connectedCallback()
将在浏览器遇到该自定义元素的开始标记后立即调用, 而子元素不会被解析,因此不可用 。 例如,如果您先定义了元素,然后浏览器解析HTML,则确实会在Chrome中发生这种情况。
这就是为什么let inputs = this.getElementsByTagName('spk-input')
外部<spk-root>
update()
方法中的let inputs = this.getElementsByTagName('spk-input')
找不到任何元素的原因。 不要因为误导那里的console.log输出而迷惑自己。
我最近刚刚深入研究了这个主题,并提出了使用HTMLBaseElement
类的解决方案:
https://gist.github.com/franktopel/5d760330a936e32644660774ccba58a7
Andrea Giammarchi(非支持浏览器中用于自定义元素的document-register-element
polyfill的作者)接受了该解决方案建议,并从中创建了一个npm包:
只要不需要动态创建自定义元素,最简单,最可靠的解决方案就是通过将元素定义脚本放在body
末尾来创建升级方案。
如果您对该主题的讨论感兴趣(请多读!):
这是全部要点:
Web组件规范v1存在一个巨大的实际问题:
在某些情况下,当元素的子节点尚不可用时,将调用connectedCallback
。
这使得Web组件在依赖子组件进行设置的情况下无法正常工作。
请参阅https://github.com/w3c/webcomponents/issues/551以获取参考。
为了解决这个问题,我们在团队中创建了一个HTMLBaseElement
类,该类用作扩展自定义元素的新类。
HTMLBaseElement
继而继承自HTMLElement
(这些自定义元素必须从其原型链中的某个位置派生)。
HTMLBaseElement
添加了两件事:
setup
方法,它负责正确的时间安排(即确保子节点可访问),然后在组件实例上调用childrenAvailableCallback()
。 parsed
布尔属性,默认为false
,在组件初始设置完成后应设置为true
。 这是为了确保例如子事件侦听器的连接不会超过一次。 class HTMLBaseElement extends HTMLElement {
constructor(...args) {
const self = super(...args)
self.parsed = false // guard to make it easy to do certain stuff only once
self.parentNodes = []
return self
}
setup() {
// collect the parentNodes
let el = this;
while (el.parentNode) {
el = el.parentNode
this.parentNodes.push(el)
}
// check if the parser has already passed the end tag of the component
// in which case this element, or one of its parents, should have a nextSibling
// if not (no whitespace at all between tags and no nextElementSiblings either)
// resort to DOMContentLoaded or load having triggered
if ([this, ...this.parentNodes].some(el=> el.nextSibling) || document.readyState !== 'loading') {
this.childrenAvailableCallback();
} else {
this.mutationObserver = new MutationObserver(() => {
if ([this, ...this.parentNodes].some(el=> el.nextSibling) || document.readyState !== 'loading') {
this.childrenAvailableCallback()
this.mutationObserver.disconnect()
}
});
this.mutationObserver.observe(this, {childList: true});
}
}
}
扩展上述内容的示例组件:
class MyComponent extends HTMLBaseElement {
constructor(...args) {
const self = super(...args)
return self
}
connectedCallback() {
// when connectedCallback has fired, call super.setup()
// which will determine when it is safe to call childrenAvailableCallback()
super.setup()
}
childrenAvailableCallback() {
// this is where you do your setup that relies on child access
console.log(this.innerHTML)
// when setup is done, make this information accessible to the element
this.parsed = true
// this is useful e.g. to only ever attach event listeners once
// to child element nodes using this as a guard
}
}
HTMLCollection inputs
确实具有length属性,如果将其记录在update函数中,您将看到它的值为2。您还可以在for循环中迭代input集合,只要它在update()函数中即可。
如果要在update函数之外的循环中访问值,则可以将HTMLCollection存储在SpektacularInput类范围之外声明的变量中。
我想还有其他方法来存储值,具体取决于您要完成的工作,但是希望这能回答您的最初问题“如何从update()方法迭代spk-root中的spk-input元素?”
class SpektacularInput extends HTMLElement { constructor() { super(); } } window.customElements.define('spk-input', SpektacularInput); let template = document.createElement('template'); template.innerHTML = ` <canvas id='spektacular'></canvas> <slot></slot> `; // declare outside variable let inputsObj = {}; class SpektacularRoot extends HTMLElement { constructor() { super(); let shadowRoot = this.attachShadow({mode: 'open'}); shadowRoot.appendChild(template.content.cloneNode(true)); } update() { // store on outside variable inputsObj = this.getElementsByTagName('spk-input'); // use in the function let inputs = this.getElementsByTagName('spk-input'); console.log("inside length: " + inputs.length) for(let i = 0; i < inputs.length; i++){ console.log("inside input " + i + ": " + inputs[i]); } } connectedCallback() { this.update(); } } window.customElements.define('spk-root', SpektacularRoot); console.log("outside length: " + inputsObj.length); for(let i = 0; i < inputsObj.length; i++){ console.log("outside input " + i + ": " + inputsObj[i]); }
<spk-root> <spk-input></spk-input> <spk-input></spk-input> </spk-root>
希望能有所帮助,干杯!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.