代码之家  ›  专栏  ›  技术社区  ›  Sean

在自定义元素中迭代HTMLCollection

  •  5
  • Sean  · 技术社区  · 7 年前

    如何在另一个自定义元素的影子dom中迭代一个自定义元素的实例?HTMLCollections的行为似乎与预期不符(我是一个jQuerian和一个新手当谈到香草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) 返回HTMLCollection:

    console.log(inputs)
    
    // output
    HTMLCollection []
      0: spk-input
      1: spk-input
      length: 2
      __proto__: HTMLCollection
    

    但是,HTMLCollection不能使用 for 循环,因为它没有长度。

    console.log(inputs.length)
    
    // output
    0
    

    搜索结果显示HTMLCollections类似于数组,而不是数组。尝试使用 Array.from(inputs)

    spk输入 spk根 更新() 方法?


    编辑 :要澄清,请致电 console.log(inputs.length) 在内部 这个 更新() 输出 0 2

    2 回复  |  直到 7 年前
        1
  •  3
  •   Community Mohan Dere    6 年前

    原因是 connectedCallback() 在某些情况下,一旦浏览器遇到 自定义元素的, 子级未被解析,因此不可用

    这就是为什么 let inputs = this.getElementsByTagName('spk-input') update() 外圆法 <spk-root> 找不到任何元素。不要让自己被误导的console.log输出所愚弄。

    我最近深入研究了这个主题,并建议使用 HTMLBaseElement 班级:

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

    document-register-element

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

    只要不需要动态创建自定义元素,最简单、最可靠的修复方法就是创建 升级 body .

    如果你对这个话题的讨论感兴趣(长时间阅读!):

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

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

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

    connectedCallback 在元素的子节点尚不可用时调用。

    这使得web组件在依赖其子组件进行设置的情况下无法正常工作。

    看到了吗 https://github.com/w3c/webcomponents/issues/551

    为了解决这个问题,我们创建了一个 HtmlBase元素 类作为新类从中扩展自治自定义元素。

    HtmlBase元素 反过来继承自 HTMLElement

    HtmlBase元素

    • A. setup 方法,然后调用 childrenAvailableCallback()
    • A. 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
      }
    }
    
        2
  •  -1
  •   JSONaLeo    7 年前

    HTMLCollection inputs 有一个length属性,如果将其记录在update函数中,您将看到它的值是2。您还可以在for循环中遍历inputs集合,只要它在update()函数中。

    如果要访问update函数外部的循环中的值,可以将HTMLCollection存储在spektualinput类作用域之外声明的变量中。

    我假设有其他方法来存储这些值,这取决于您试图完成的任务,但希望这能回答您的初始问题“如何从update()方法迭代spk root中的spk输入元素?”

    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>

    希望有帮助, 干杯!