在自定义元素中迭代 HTMLCollection
Iterate over HTMLCollection in custom element
如何在另一个自定义元素的阴影 dom 中迭代一个自定义元素的实例? HTML集合似乎没有按预期运行。 (我是一个 jQuerian 和 vanilla js 的新手,所以我确定我在某个地方犯了一个明显的错误)。
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)
returns 一个 HTML 集合:
console.log(inputs)
// output
HTMLCollection []
0: spk-input
1: spk-input
length: 2
__proto__: HTMLCollection
但是,HTML集合无法使用 for
循环迭代,因为它没有长度。
console.log(inputs.length)
// output
0
搜索 SO 显示 HTML集合类似于数组,但不是数组。尝试使用 Array.from(inputs)
或扩展运算符使其成为一个数组会导致一个空数组。
这是怎么回事?如何从 update()
方法迭代 spk-root
中的 spk-input
元素?
我正在使用 gulp-babel 和 gulp-concat 并使用 Chrome。让我知道是否需要更多信息。提前致谢。
编辑:澄清一下,在中从调用console.log(inputs.length)
,update()
输出0
而不是 2
.
原因是,在某些情况下,一旦浏览器遇到自定义元素的 开始标记,就会调用自定义元素的 connectedCallback()
,子项未被解析,因此不可用。这确实例如发生在 Chrome 如果你预先定义元素然后浏览器解析 HTML.
这就是为什么 let inputs = this.getElementsByTagName('spk-input')
在外部 <spk-root>
的 update()
方法中找不到任何元素。不要让自己被那里误导性的 console.log 输出所愚弄。
我最近刚刚深入研究了这个主题,并提出了一个使用 HTMLBaseElement
class:
的解决方案
https://gist.github.com/franktopel/5d760330a936e32644660774ccba58a7
Andrea Giammarchi(document-register-element
polyfill for custom elements in non-supporting browsers 的作者)采纳了该解决方案建议并从中创建了一个 npm 包:
只要您不需要动态创建您的自定义元素,最简单和最可靠的修复方法是创建 upgrade 方案,方法是将您的元素定义脚本放在body
.
结束
如果您对该主题的讨论感兴趣(请阅读!):
完整要点如下:
HTMLBaseElementclass解决connectedCallback在children解析前被调用的问题
Web 组件规范 v1 存在一个巨大的实际问题:
在某些情况下,当元素的子节点尚不可用时,将调用 connectedCallback
。
这使得 Web 组件在依赖其子组件进行设置的情况下无法正常工作。
参考https://github.com/w3c/webcomponents/issues/551。
为了解决这个问题,我们在我们的团队中创建了一个 HTMLBaseElement
class 作为新的 class 来扩展自主自定义元素。
HTMLBaseElement
又继承自 HTMLElement
(自治自定义元素必须从其原型链中的某个点派生)。
HTMLBaseElement
添加两件事:
- 一个
setup
方法负责正确的计时(即确保子节点可访问),然后在组件实例上调用 childrenAvailableCallback()
。
- a
parsed
布尔值 属性,默认为 false
,当组件初始设置完成时,将设置为 true
。这是为了充当警卫以确保例如。子事件侦听器永远不会附加超过一次。
HTML基础元素
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
确实有一个长度 属性,如果您在更新函数中记录它,您会看到它的值为 2。您还可以在 for 中循环访问输入集合只要它在 update() 函数内就循环。
如果您想在更新函数之外的循环中访问值,您可以将 HTMLCollection 存储在声明在 SpektacularInput 范围之外的变量中 class。
我想还有其他方法可以存储值,具体取决于您要完成的任务,但希望这能回答您最初的问题 "How can I iterate over the spk-input elements within spk-root from the update() method?"
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>
希望对您有所帮助,
干杯!
如何在另一个自定义元素的阴影 dom 中迭代一个自定义元素的实例? HTML集合似乎没有按预期运行。 (我是一个 jQuerian 和 vanilla js 的新手,所以我确定我在某个地方犯了一个明显的错误)。
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)
returns 一个 HTML 集合:
console.log(inputs)
// output
HTMLCollection []
0: spk-input
1: spk-input
length: 2
__proto__: HTMLCollection
但是,HTML集合无法使用 for
循环迭代,因为它没有长度。
console.log(inputs.length)
// output
0
搜索 SO 显示 HTML集合类似于数组,但不是数组。尝试使用 Array.from(inputs)
或扩展运算符使其成为一个数组会导致一个空数组。
这是怎么回事?如何从 update()
方法迭代 spk-root
中的 spk-input
元素?
我正在使用 gulp-babel 和 gulp-concat 并使用 Chrome。让我知道是否需要更多信息。提前致谢。
编辑:澄清一下,在中从调用console.log(inputs.length)
,update()
输出0
而不是 2
.
原因是,在某些情况下,一旦浏览器遇到自定义元素的 开始标记,就会调用自定义元素的 connectedCallback()
,子项未被解析,因此不可用。这确实例如发生在 Chrome 如果你预先定义元素然后浏览器解析 HTML.
这就是为什么 let inputs = this.getElementsByTagName('spk-input')
在外部 <spk-root>
的 update()
方法中找不到任何元素。不要让自己被那里误导性的 console.log 输出所愚弄。
我最近刚刚深入研究了这个主题,并提出了一个使用 HTMLBaseElement
class:
https://gist.github.com/franktopel/5d760330a936e32644660774ccba58a7
Andrea Giammarchi(document-register-element
polyfill for custom elements in non-supporting browsers 的作者)采纳了该解决方案建议并从中创建了一个 npm 包:
只要您不需要动态创建您的自定义元素,最简单和最可靠的修复方法是创建 upgrade 方案,方法是将您的元素定义脚本放在body
.
如果您对该主题的讨论感兴趣(请阅读!):
完整要点如下:
HTMLBaseElementclass解决connectedCallback在children解析前被调用的问题
Web 组件规范 v1 存在一个巨大的实际问题:
在某些情况下,当元素的子节点尚不可用时,将调用 connectedCallback
。
这使得 Web 组件在依赖其子组件进行设置的情况下无法正常工作。
参考https://github.com/w3c/webcomponents/issues/551。
为了解决这个问题,我们在我们的团队中创建了一个 HTMLBaseElement
class 作为新的 class 来扩展自主自定义元素。
HTMLBaseElement
又继承自 HTMLElement
(自治自定义元素必须从其原型链中的某个点派生)。
HTMLBaseElement
添加两件事:
- 一个
setup
方法负责正确的计时(即确保子节点可访问),然后在组件实例上调用childrenAvailableCallback()
。 - a
parsed
布尔值 属性,默认为false
,当组件初始设置完成时,将设置为true
。这是为了充当警卫以确保例如。子事件侦听器永远不会附加超过一次。
HTML基础元素
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
确实有一个长度 属性,如果您在更新函数中记录它,您会看到它的值为 2。您还可以在 for 中循环访问输入集合只要它在 update() 函数内就循环。
如果您想在更新函数之外的循环中访问值,您可以将 HTMLCollection 存储在声明在 SpektacularInput 范围之外的变量中 class。
我想还有其他方法可以存储值,具体取决于您要完成的任务,但希望这能回答您最初的问题 "How can I iterate over the spk-input elements within spk-root from the update() method?"
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>
希望对您有所帮助, 干杯!