最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Dynamically add elements inside web component - Stack Overflow

programmeradmin0浏览0评论

I would like to create a web ponent that contains a list of elements that can be added to. For example if I had an initial template like:

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);

and each time the button is clicked, a <div> is added which contains the text from the input field, thereby creating something like:

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

I would like to create a web ponent that contains a list of elements that can be added to. For example if I had an initial template like:

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);

and each time the button is clicked, a <div> is added which contains the text from the input field, thereby creating something like:

<input type="text"></input><button>add div</button>
<div>first text from input added</div>
<div>second text from input added</div>
...
Share Improve this question asked Dec 15, 2019 at 20:49 Max888Max888 3,8004 gold badges34 silver badges76 bronze badges 1
  • This can be isolated from webponents. You may have tried appendChild &cie, what was the problem – grodzi Commented Dec 15, 2019 at 21:14
Add a ment  | 

3 Answers 3

Reset to default 2

In your case, you cannot use insertAjacentHTML() on the Shadow DOM shadowRoot property because the Shadow Root doesn't implement the Element interface.

Use bind( this )

A better solution is to use appendChild() on the shadowRoot property. However, you'll need to add a special bind() operation on the click event callback.

In an event callback, this indeed references the element that triggered the event, not the object which the callback is defined in.

In order to get the reference to the custom element (in order to access the input element in the Shadow DOM shadowRoot, call bind(this) inside addEventListener().

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

See a full example below:

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>


Use arrow function

Another solution whould be to use an arrow function. With arrow function, this is not redefined, so you don't need to use 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>

Thanks to Danny's answer I realised I don't need to create this._shadowRoot, so my question and Supersharp's answer can be simplified to the below. I've kept the template because it is good practice to create web ponents from templates because performance is better than using 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);

Extending on Supersharps answer:

  • attachShadow() sets this.shadowRoot by default
  • attachShadow() returns the shadowRoot, so you can chain .innerHTML on it
  • appendChild() returns the appended DIV, so you can chain it

class MyElement extends HTMLElement {
  constructor() {
    super() // sets AND return 'this' scope
        .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>

Or rewrite it to one helper function $append
which makes the rest of the code readable

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>

发布评论

评论列表(0)

  1. 暂无评论