在Web组件内动态添加元素

时间:2019-12-15 20:49:34

标签: javascript web-component native-web-component

我想创建一个Web组件,其中包含可以添加到其中的元素列表。 例如,如果我有一个初始模板,例如:

const template = document.createElement("template");
template.innerHTML = `<input type="text"></input><button>add div</button>`;

class MyElement extends HTMLElement {
  constructor() {
    super();
    this._shadowRoot = this.attachShadow({ mode: "open" });
    this._shadowRoot.appendChild(template.content.cloneNode(true));
    const button = this._shadowRoot.querySelector("button");
    button.addEventListener("click", this.addDiv);
  }
  addDiv(e) {
    // ...
  }
}
customElements.define("my-element", MyElement);

每单击一次按钮,就会添加一个<div>,其中包含来自输​​入字段的文本,从而创建如下内容:

<input type="text"></input><button>add div</button>
<div>first text from input added</div>
<div>second text from input added</div>
...

3 个答案:

答案 0 :(得分:1)

在您的情况下,您不能在Shadow DOM insertAjacentHTML()属性上使用shadowRoot,因为Shadow Root无法实现Element接口。

使用bind(this)

更好的解决方案是在appendChild(属性上使用shadowRoot)。但是,您需要在click事件回调中添加特殊的bind()操作。

在事件回调中,它确实引用触发事件的元素,而不是在其中定义回调的对象。

为了获得对自定义元素的引用(为了访问Shadow DOM shadowRoot中的输入元素,请在bind(this)内调用addEventListener()

button.addEventListener( "click", this.addDiv.bind( this ) )

请参阅下面的完整示例:

const template = document.createElement("template");
template.innerHTML = `<input type="text"></input><button>add div</button>`;

class MyElement extends HTMLElement {
  constructor() {
    super();
    this._shadowRoot = this.attachShadow({ mode: "open" });
    this._shadowRoot.appendChild(template.content.cloneNode(true));
    const button = this._shadowRoot.querySelector("button");
    button.addEventListener("click", this.addDiv.bind( this ) );
  }
  addDiv(e) {
    var div = document.createElement( 'div' )
    div.textContent = this.shadowRoot.querySelector( 'input' ).value
    this.shadowRoot.appendChild( div )
  }
}
customElements.define("my-element", MyElement);
<my-element></my-element>


使用箭头功能

另一个解决方案是使用arrow function。使用箭头功能,此功能不会重新定义,因此您无需使用bind()

class MyElement extends HTMLElement {
  constructor() {
    super()
    const sh = this.attachShadow( { mode: "open" } )
    sh.innerHTML = `<input type="text"></input><button>add div</button>`
    const button = sh.querySelector( "button" )
    button.onclick = ev => {
      let div = document.createElement( "div" )
      div.textContent = sh.querySelector( "input" ).value
      sh.appendChild( div )
    }
  }
}
customElements.define( "my-element", MyElement )
<my-element></my-element>

答案 1 :(得分:1)

扩展到Supersharps答案:

    默认情况下
  • attachShadow() 设置 this.shadowRoot
  • attachShadow() 返回 shadowRoot,因此您可以在其上链接.innerHTML
  • appendChild()返回附加的DIV,因此您可以对其进行链接

  class MyElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: "open"})
        .innerHTML = '<input type="text"></input><button>add div</button>';
    this.shadowRoot.querySelector('button').onclick = evt =>
      this.shadowRoot
          .appendChild(document.createElement("div"))
          .innerHTML = this.shadowRoot.querySelector("input").value
  }
}
customElements.define("my-element", MyElement)
<my-element></my-element>

或将其重写为一个辅助函数$ append
这使得其余代码可读性

class MyElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: "open"});

    let $append = ( tag, html = '' ) => (
          tag = this.shadowRoot.appendChild(document.createElement(tag)),
          tag.innerHTML = html,
          tag // return tag, so onclick can be chained
        );

    let input = $append('input');
    $append('button', 'add div').onclick = evt => $append("div", input.value);
  }
}
customElements.define("my-element", MyElement)
<my-element></my-element>

答案 2 :(得分:0)

由于Danny的回答,我意识到我不需要创建this._shadowRoot,因此我的问题和Supersharp的回答可以简化为以下内容。我保留模板是因为从模板创建Web组件是一种好习惯,因为与使用shadowRoot.innerHTML相比,性能要好。

const template = document.createElement("template");
template.innerHTML = `<input type="text"></input><button>add div</button>`;

class MyElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.shadowRoot.appendChild(template.content.cloneNode(true));
    const button = this.shadowRoot.querySelector("button");
    button.addEventListener("click", this.addDiv.bind(this));
  }
  addDiv(e) {
    const div = document.createElement("div");
    div.textContent = this.shadowRoot.querySelector("input").value;
    this.shadowRoot.appendChild(div);
  }
}
customElements.define("my-element", MyElement);