文档片段内自定义元素的初始化

7
考虑这个包含两个平级的x-element和一个嵌套的x-element的HTML模板
<template id="fooTemplate">
  <x-element>Enter your text node here.</x-element>
  <x-element>
    <x-element>Hello, World?</x-element>
  </x-element>
</template>

如何初始化(启动构造函数)从fooTemplate克隆的所有自定义元素文档片段,而不将其附加到DOM中,也不通过使用is="x-element"扩展内置元素;或者整个片段。
class XElement extends HTMLElement {
  constructor() { super(); }
  foo() { console.log( this ); }
} customElements.define( 'x-element', XElement );

const uselessf = function( temp ) {
  const frag = window[ temp ].content.cloneNode( true );

  /* Your magic code goes here:
  */ do_black_magic( frag );

  for (const e of  frag.querySelectorAll('x-element') )
    e.foo(); // This should work.

  return frag;
};

window['someNode'].appendChild( uselessf('fooTemplate') );

请注意,脚本带有defer属性执行。

也许我应该以某种方式将“HTMLElement”向上转换为“XElement”? - Monsieur Pierre Doune
3个回答

3
我们可以使用这个箭头函数来初始化模板:
const initTemplate = temp =>
  document.createRange().createContextualFragment( temp.innerHTML );

const frag = initTemplate( window['someTemplate'] );

或者使用在template原型上定义的这种方法(我更喜欢这种方式):

Object.defineProperty(HTMLTemplateElement.prototype, 'initialise', {
  enumerable: false,
  value() {
    return document.createRange().createContextualFragment( this.innerHTML );
  }
});

const frag = window['someTemplate'].initialise();

无论如何,此代码将正常工作:

for (const elem of  frag.querySelectorAll('x-element') )
  elem.foo();

window['someNode'].appendChild( frag );

我不确定这些方法是否是在模板中初始化自定义元素的最有效方式。

同时请注意,无需克隆模板。


在第一个链接中,Eric使用了已弃用的document.registerElement函数,在最后一个链接中没有关于模板中自定义元素的内容。 - Monsieur Pierre Doune
我想要的是:第一个链接第二个链接 - Monsieur Pierre Doune
1
document.createRange().createContextualFragment(temp.innerHTML); 实际上会创建模板内容/HTML的副本。这样做的原因是,该副本使用全局文档作为上下文/ownerDocument。 - Robbendebiene

3
TLDR:
使用 document.importNode(template.content, true); 替代 template.content.cloneNode(true); 在此处阅读有关 document.importNode() 的更多信息。 说明:
由于自定义元素是在不同的文档/上下文中创建的(即模板的 DocumentFragment),因此它不知道根/全局文档中自定义元素的定义。您可以通过读取 Node.ownerDocument 属性(MDN)来获取元素所属的文档,在这种情况下,它将与 window.document 元素不同。
说了这么多,为了“应用”自定义元素,您需要在全局文档的上下文中创建自定义元素。可以通过调用 document.importNode(node, [true]) (MDN) 来实现,它与 node.cloneNode([true]) 类似,但会在全局文档上下文中创建该元素的副本。
或者,您也可以使用 document.adoptNode(node) (MDN) 引入 DocumentFragment 到全局文档,然后通过 node.cloneNode([true]) 创建其副本。请注意,如果您将 adoptNode() 用于 HTML 元素,则该元素将从其原始文档中移除。
以下是说明性示例代码:

class XElement extends HTMLElement {
  constructor() { super(); console.log("Custom Element Constructed") }
}
customElements.define( 'x-element', XElement );

const externalFragment = fooTemplate.content;

console.log(
  "Is ownerDocument equal?",
  externalFragment.ownerDocument === document
);

console.log("import start");

const importedFragment = document.importNode(externalFragment, true);

console.log("import end");

console.log(
  "Is ownerDocument equal?",
  importedFragment.ownerDocument === document
);
<template id="fooTemplate">
  <x-element>Hello, World?</x-element>
</template>

注意:将一个文档中的元素附加到另一个文档中会强制进行节点的隐式采用。这就是为什么在这种情况下将元素附加到全局DOM中可以正常工作的原因。

1
这是正确的答案!!!有些情况下,您不希望在操作或查询之前将文档片段添加到DOM中。
  • 您希望能够在不冒泡事件的情况下进行操作。
  • 您希望在孤立的文档片段中查询,而不是在整个文档的上下文中查询。
document.importNode(MY_COMPONENT_TEMPLATE,true)允许使用轻量级的DocumentFragment模板,并在克隆时“激活”其内容。两全其美!非常感谢!
- Jeremy Chone
非常印象深刻,谢谢!你是怎么学到这些的?我从阅读 MDN 文章中并没有获得这么多的理解。 - Jose V

0

您可以通过将模板克隆添加到处理它之前的document中,来避免先前答案中的“createContextualFragment”hack。

假设我们已经定义了这两个变量...

const containerEl = document.querySelector('div.my-container')
const templateEl = document.querySelector('#fooTemplate')

...而不是这样做(其中frag包含未初始化的自定义元素)...

const frag = templateEl.content.cloneNode(true)
manipulateTemplateContent(frag)
containerEl.appendChild(frag)

...将模板克隆附加到文档首先,然后进行操作。用户不会注意到任何区别-所有同步代码都在同一帧中执行。

const frag = templateEl.content.cloneNode(true)
containerEl.appendChild(frag)
manipulateTemplateContent(containerEl)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接