如何等待自定义元素引用被升级"?

时间:2016-08-28 22:51:40

标签: javascript dom polymer web-component custom-element

我引用了一个元素,该元素在某些时候会升级为自定义元素。我如何等待它升级?

例如,假设el是参考。如果它假设为此目的附加了一个承诺,代码可能类似于

await el.upgradePromise
// do something after it has been upgraded.

那当然不存在,但描述了我想做的事情。也许没有投票就没有办法做到这一点?如果我使用轮询,我会轮询什么(假设我没有引用它应该升级到的类构造函数)。也许我可以轮询el.constructor并等待它不是HTMLElement,或等待它不是HTMLUnknownElement

编辑:对于后台,我有一些代码,如下所示,其中使用setTimeout是一个hack,以便代码工作。第一个console.log输出为false,而超时中的一个输出为真。

import OtherElement from './OtherElement'

class SomeElement extends HTMLElement {
    attachedCallback() {
        console.log(this.children[0] instanceof OtherElement) // false

        setTimeout(() => {
            console.log(this.children[0] instanceof OtherElement) // true
        }, 0)
    }
}

其中OtherElement是对某个自定义元素类的引用,该类将在某个时刻进行注册。请注意,我在我的案例中使用了Chrome v0 document.registerElement。需要超时,因为如果首先注册SomeElement,如下面的代码所示,那么OtherElement将不会被注册,因此如果SomeElement元素的子元素是预期的,那么OtherElement的实例,然后在下次升级这些元素之前不会出现这种情况。

document.registerElement('some-el', SomeElement)
document.registerElement('other-el', OtherElement)

理想情况下,这样的超时是不可取的,因为如果升级需要花费更长的时间(由于某些未知原因可能取决于浏览器的实现),那么超时黑客也将失败。

我想要一种绝对的方式来等待升级而不会出现可能的故障,并且如果可能的话不进行轮询。也许它需要在一段时间后取消?

编辑:理想的解决方案将允许我们等待任何第三方自定义元素的升级,而无需在运行时之前修改这些元素,也无需在运行时进行修补。

编辑:通过观察Chrome的v0行为,似乎第一次调用document.registerElement('some-el', SomeElement)会导致升级这些元素并将其attachedCallback方法解析为之前 OtherElement的注册,因此孩子的类型不正确。然后,通过推迟逻辑,我可以在孩子们升级到OtherElement类型后运行逻辑。

编辑:这是一个显示the problem的jsfiddle,这里是一个显示the timeout hack solution的jsfiddle。两者都是使用Chrome Canary中的Custom Elements v1 API编写的,并且无法在其他浏览器中使用,但问题是使用Chrome稳定版的自定义元素v0 API document.registerElement和{{1而不是attachedCallbackcustomElements.define。 (请参阅两个小提琴中的控制台输出。)

4 个答案:

答案 0 :(得分:3)

您可以使用window.customElements.whenDefined("my-element")返回Promise,您可以使用window.customElements.whenDefined('my-element').then(() => { // do something after element is upgraded })来确定元素何时升级。



{




答案 1 :(得分:1)

由于html和javascript解析的sync顺序,您只需要等待插入元素。

第一个测试用例 - 插入HTML然后定义元素:

<el-one id="E1">
  <el-two id="E2">
  </el-two>
</el-one>
<script>
  // TEST CASE 1: Register elements AFTER instances are already in DOM but not upgraded:
  customElements.define('el-one', ElementOne)
  customElements.define('el-two', ElementTwo)
  // END TEST CASE 1
  console.assert( E1 instanceof ElementOne )
  console.assert( E2 instanceof ElementTwo )
</script>

第二个测试用例 - 定义的元素然后插入:

// TEST CASE 2: register elements THEN add new insances to DOM:
customElements.define('el-three', ElementThree)
customElements.define('el-four', ElementFour)
var four = document.createElement('el-four')
var three = document.createElement('el-three')
three.appendChild(four)
document.body.appendChild(three)
// END TEST CASE 2
console.assert( three instanceof ElementThree )
console.assert( four instanceof ElementFour )

&#13;
&#13;
class ElementZero extends HTMLElement {
    connectedCallback() {
        console.log( '%s connected', this.localName )
    }
}

class ElementOne extends ElementZero { }
class ElementTwo extends ElementZero { }

// TEST CASE 1: Register elements AFTER instances are already in DOM but not upgraded:
customElements.define('el-one', ElementOne)
customElements.define('el-two', ElementTwo)
// END TEST CASE 1
console.info( 'E1 and E2 upgraded:', E1 instanceof ElementOne && E2 instanceof ElementTwo )


class ElementThree extends ElementZero { }
class ElementFour extends ElementZero { }

// TEST CASE 2: register elements THEN add new insances to DOM:
customElements.define('el-three', ElementThree)
customElements.define('el-four', ElementFour)
const E4 = document.createElement('el-four')
const E3 = document.createElement('el-three')
E3.appendChild(E4)
document.body.appendChild(E3)
// END TEST CASE 2
console.info( 'E3 and E4 upgraded:', E3 instanceof ElementThree && E4 instanceof ElementFour )
&#13;
<el-one id="E1">
  <el-two id="E2">
  </el-two>
</el-one>
&#13;
&#13;
&#13;

第三个​​测试用例 - 未知元素名称

如果您不知道内部元素的名称,可以解析外部元素的内容,并在每个发现的自定义元素上使用whenDefined()

&#13;
&#13;
// TEST CASE 3
class ElementOne extends HTMLElement {
  connectedCallback() {
    var customs = []
    for (var element of this.children) {
      if (!customs.find(name => name == element.localName) &&
        element.localName.indexOf('-') > -1)
        customs.push(element.localName)
    }
    customs.forEach(name => customElements.whenDefined(name).then(() => 
      console.log(name + ' expected to be true:', this.children[0] instanceof customElements.get(name))
    ))
  }
}

class ElementTwo extends HTMLElement {}
customElements.define('el-one', ElementOne)
customElements.define('el-two', ElementTwo)
&#13;
<el-one>
  <el-two>
  </el-two>
</el-one>
&#13;
&#13;
&#13;

注意如果您必须等待升级不同的自定义元素,那么您将获得Promise.all()分辨率。您可能还希望执行更详细的(递归)解析。

答案 2 :(得分:0)

@trusktr如果我理解你的问题,这绝对有可能通过创造性地使用:defined伪选择器,MutationObserver&amp;您已经提到的自定义元素方法

const o = new MutationObserver(mutationRecords => {
  const shouldCheck = mutationRecords.some(mutationRecord => mutationRecord.type === 'childList' && mutationRecord.addedNodes.length)

  const addedNodes = mutationRecords.reduce((aN, mutationRecord) => aN.concat(...mutationRecord.addedNodes), [])

  const undefinedNodes = document.querySelectorAll(':not(:defined)')

  if (shouldCheck) { 
    console.info(undefinedNodes, addedNodes);

    [...undefinedNodes].forEach(n => customElements.whenDefined(n.localName).then(() => console.info(`${n.localName} defined`)))
  }
})

o.observe(document.body, { attributes: true, childList: true })

class FooDoozzz extends HTMLElement { connectedCallback () { this.textContent = 'FUUUUUCK' }  }

// Will tell you that a "foo-doozzz" element that's undefined has been added
document.body.appendChild(document.createElement('foo-doozzz'))

// Will define "foo-doozzz", and you event fires telling you it was defined an there are no longer any undefined elements
customElements.define('foo-doozzz', FooDoozzz)

// You'll see an event fired telling you an element was added, but there are no undefined elements still
document.body.appendChild(document.createElement('foo-doozzz'))

答案 3 :(得分:0)

再次提出该问题,因为最初的问题尚未得到解决,并且这里可能存在规格增强的情况。

基本上,我的问题与OP相同:在处理DOM元素的框架代码中,我需要检测一个未定义的对象并将其处理推迟到定义后。

james_womack(及其他地方)提出的

element.matches(':defined')是一个很好的开始,因为它无需确定给定元素是否是自定义/自定义的,这本身并不有趣。

此方法解决了自定义元素的问题:customElements.whenDefined(element.localName)

这对于自定义内置元素来说是不够的,因为本地名称将只是标准节点名称。

在元素上保留is属性的值可以解决此问题,但今天的规范并不需要。

因此,在执行以下代码时: let element = document.createElement('input', {is: 'custom-input'}); is属性丢失。

顺便说一句,当有效执行与以下操作相同的操作时,is属性将保留: document.body.innerHTML += '<input is="custom-input"/>'

恕我直言,is属性也应该在程序设计创建流程中保留 ,从而提供一致的API行为并提供等待定义自定义内置元素的功能。 / p>

附录: