遍历自定义元素中的HTMLCollection

时间:2018-11-03 20:28:44

标签: javascript html shadow-dom custom-element

如何在另一个自定义元素的影子dom内迭代一个自定义元素的实例? HTMLCollections似乎不符合预期。 (关于香草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)或散布运算符将其设置为数组会导致一个空数组。

这是怎么回事?如何通过spk-input方法遍历spk-root中的update()元素?

我正在使用gulp-babel和gulp-concat并使用Chrome。让我知道是否需要更多信息。预先感谢。


编辑:为澄清起见,在内部中调用console.log(inputs.length)而不是update(),而是输出0

2 个答案:

答案 0 :(得分:2)

原因是在某些情况下,只要浏览器遇到自定义元素的开始标签 (其中没有子元素),就会在某些情况下立即调用自定义元素的connectedCallback()正在解析,因此不可用。这确实例如如果您先定义了元素,然后在浏览器中解析HTML,就会在Chrome中发生这种情况。

这就是为什么外部let inputs = this.getElementsByTagName('spk-input')的{​​{1}}方法中的update()无法找到任何元素的原因。不要让误导那里的console.log输出迷惑自己。

我最近刚刚深入探讨了该主题,并建议使用<spk-root>类的解决方案:

  

https://gist.github.com/franktopel/5d760330a936e32644660774ccba58a7

Andrea Giammarchi(HTMLBaseElement polyfill的作者,在不支持的浏览器中用于自定义元素)已经接受了该解决方案建议,并从中创建了一个npm包:

  

https://github.com/WebReflection/html-parsed-element

只要不需要动态创建自定义元素,最简单,最可靠的解决方案就是通过将元素定义脚本放在{{的末尾)来创建升级方案。 1}}。

如果您对该主题的讨论感兴趣(请多读!)

  

https://github.com/w3c/webcomponents/issues/551

这是全部要点:

HTMLBaseElement类解决了在解析子级之前调用connectedCallback的问题

Web组件规范v1存在一个巨大的实际问题:

在某些情况下,当元素的子节点尚不可用时,将调用document-register-element

这在某些情况下会导致Web组件无法正常运行。

请参阅https://github.com/w3c/webcomponents/issues/551以供参考。

为解决此问题,我们在团队中创建了一个body类,该类用作扩展自定义元素的新类。

connectedCallback依次继承自HTMLBaseElement(自治自定义元素必须在其原型链中的某个位置派生)。

HTMLBaseElement添加了两件事:

  • 一种HTMLElement方法,它负责正确的时间安排(即确保子节点可访问),然后在组件实例上调用HTMLBaseElement
  • 一个setup布尔属性,默认为childrenAvailableCallback(),在组件初始设置完成后应设置为parsed。这是为了确保例如子事件侦听器的连接从未超过一次。

HTMLBaseElement

false

扩展上述内容的示例组件:

true

答案 1 :(得分:-1)

HTMLCollection inputs确实具有length属性,如果将其记录在update函数中,您将看到它的值为2。您也可以在for循环中遍历input集合,只要它在内部即可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>

希望有帮助, 干杯!

相关问题