簡體   English   中英

遍歷自定義元素中的HTMLCollection

[英]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-rootspk-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包:

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以獲取參考。

為了解決這個問題,我們在團隊中創建了一個HTMLBaseElement類,該類用作擴展自定義元素的新類。

HTMLBaseElement繼而繼承自HTMLElement (這些自定義元素必須從其原型鏈中的某個位置派生)。

HTMLBaseElement添加了兩件事:

  • 一種setup方法,它負責正確的時間安排(即確保子節點可訪問),然后在組件實例上調用childrenAvailableCallback()
  • 一個parsed布爾屬性,默認為false ,在組件初始設置完成后應設置為true 這是為了確保例如子事件偵聽器的連接不會超過一次。

HTMLBaseElement

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM