【问题标题】:Is it possible to programmatically slot elements in web components?是否可以以编程方式在 Web 组件中插入元素?
【发布时间】:2020-12-23 10:53:55
【问题描述】:

是否可以自动或以编程方式插入嵌套的 Web 组件或特定类型的元素,而无需为其指定 slot 属性?

考虑这样的结构:

<parent-element>
  <child-element>Child 1</child-element>
  <child-element>Child 2</child-element>

  <p>Content</p>
</parent-element>

&lt;parent-element&gt; 具有这样的 Shadow DOM:

<div id="child-elements">
  <slot name="child-elements">
    <child-element>Default child</child-element>
  </slot>
</div>
<div id="content">
  <slot></slot>
</div>

预期结果是:

<parent-element>
  <#shadow-root>
    <div id="child-elements">
      <slot name="child-elements">
        <child-element>Child 1</child-element>
        <child-element>Child 2</child-element>
      </slot>
    </div>
    <div id="content">
      <slot>
        <p>Content</p>
      </slot>
    </div>
</parent-element>

换句话说,我想强制 &lt;child-element&gt;s 只允许在 &lt;parent-element&gt; 内使用,类似于 &lt;td&gt; 元素只允许在 &lt;tr&gt; 元素内使用。我希望它们被放置在&lt;slot name="child-elements"&gt; 元素中。必须为它们中的每一个指定 slot 属性以将它们放置在 &lt;parent-element&gt; 的特定插槽中似乎是多余的。 同时,&lt;parent-element&gt; 中的其余内容应自动插入到第二个 &lt;slot&gt; 元素中。

我在注册父元素时首先搜索了一种方法来定义它,尽管CustomElementRegistry.define() 目前只支持extends 作为选项。

然后我想,也许有一个功能允许手动插入元素,即类似childElement.slot('child-elements'),但似乎不存在。

然后我尝试在父元素的构造函数中以编程方式实现这一点,如下所示:

constructor() {
  super();

  this.attachShadow({mode: 'open'});
  this.shadowRoot.appendChild(template.content.cloneNode(true));

  const childElements = this.getElementsByTagName('child-element');
  const childElementSlot = this.shadowRoot.querySelector('[name="child-elements"]');
  for (let i = 0; i < childElements.length; i++) {
    childElementSlot.appendChild(childElements[i]);
  }
}

虽然这不会将子元素移动到&lt;slot name="child-elements"&gt;,但它们仍然会被插入到第二个&lt;slot&gt; 元素中。

【问题讨论】:

  • 我认为这可能是一个很好的问题,但就目前的形式而言,您想要实现的目标以及到目前为止您已经设法实现的目标有点令人困惑。为了清楚起见,请您编辑/重写您的问题 - 我喜欢 WebComponents,我想为您提供帮助。
  • @Rounin,想slotchange 活动为您解答...
  • @Rounin 我已经扩展了我的答案,以澄清预期结果是什么以及到目前为止我已经尝试过什么。
  • @Danny'365CSI'Engelman 据我所知,slotchange 事件仅在添加或删除元素后在 &lt;slot&gt; 元素上触发,但我没有看看这如何帮助我将它们放入正确的插槽中。
  • 我猜你的意思是childEements[i]而不是tabs[i]???在这种情况下,您不再使用 SLOT 技术,因为您现在将 移动 lightDOM 内容到 shadowDOM

标签: html web-component shadow-dom custom-element html-templates


【解决方案1】:

您的未命名默认&lt;slot&gt;&lt;/slot&gt;将捕获所有未分配给已命名槽的元素;
所以slotchange 事件可以捕获这些并强制child-element 进入正确的插槽:

customElements.define('parent-element', class extends HTMLElement {
    constructor() {
      super().attachShadow({mode:'open'})
             .append(document.getElementById(this.nodeName).content.cloneNode(true));
      this.shadowRoot.addEventListener("slotchange", (evt) => {
        if (evt.target.name == "") {// <slot></slot> captures
          [...evt.target.assignedElements()]
            .filter(el => el.nodeName == 'CHILD-ELEMENT') //process child-elements
            .map(el => el.slot = "child-elements"); // force them to their own slot
        } else console.log(`SLOT: ${evt.target.name} got:`,evt.target.assignedNodes())
      })}});
customElements.define('child-element', class extends HTMLElement {
    connectedCallback(parent = this.closest("parent-element")) {
      // or check and force slot name here
      if (this.parentNode != parent) {
        if (parent) parent.append(this); // Child 3 !!!
        else console.error(this.innerHTML, "wants a PARENT-ELEMENT!");
      }}});
child-element { color: red; display: block; } /* style lightDOM in global CSS! */
<template id=PARENT-ELEMENT>
  <style>
    :host { display: inline-block; border: 2px solid red; }
    ::slotted(child-element) { background: lightgreen }
    div { border:3px dashed rebeccapurple }
  </style>
  <div><slot name=child-elements></slot></div>
  <slot></slot>
</template>

<parent-element>
  <child-element>Child 1</child-element>
  <child-element>Child 2</child-element>
  <b>Content</b>
  <div><child-element>Child 3 !!!</child-element></div>
</parent-element>
<child-element>Child 4 !!!</child-element>

注意处理&lt;child-element&gt; 的逻辑不是&lt;parent-element&gt;直接子级,您可能想根据自己的需要重写它

【讨论】:

  • 效果很好。我只是想知道为什么您必须对 slotchange 事件做出反应,并且不能主动将它们移动到 &lt;slot name="child-elements"&gt;,即当它们被创建或升级时或在它们被插入之前。
  • 是的,你可以不用 slotchange 事件,并将逻辑放在子元素上:如果 slot 正确,设置它
  • 还有connectedCallback() doesn't take parameters,所以parent = this.closest("parent-element")应该在函数体内定义。
  • 不,你可以在那里声明变量.. 这是有效的 JavaScript,我们不受 TypeScript 语法的限制.. 如果组件应该很小,那是防止 LET 语句的创建位置.. 节省 4字节。请参阅我的IconMeister 中的 svg() 方法所有“变量”都是“参数”
  • 有效的 JavaScript,它在网络传输中节省了几个字节,是的,但我的意思是 DOM 规范说 connectedCallback() 是在没有参数的情况下调用的。将它放在参数列表中可能会让阅读您的答案的人认为他们可以以某种方式更改parent。因此,当您想最小化代码时这样做可能会很好,但为了清楚起见,最好在此处使用 const 语句。
猜你喜欢
  • 1970-01-01
  • 2014-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-09
  • 2011-11-04
  • 2011-03-30
  • 2013-06-29
相关资源
最近更新 更多