Search code examples
javascriptlit-elementlit-html

lit-element: appendChild of ul element in constructor() renders in wrong dom position


  • Trying to create a lit-element encapsulated todo list. As a first step, trying to create a ul element, and then append a li element with every button click.
  • Although I can get this to work by starting with pre-creating the anchor ul element in the html before the web component, I don't want to do that. I want the entire functionality to be contained within the web component.
  • Below is a consolidated html file (including the lit-element script), showing a couple of ways that I've attempted this.
    • If I create the ul element in the constructor, then I'm successfully able to append li elements. But, they are in the wrong position (after other elements that come after the component in the html portion).
    • If I create the ul element in the html template literal of render(), then the button clicks don't work (resulting in a error of "uncaught typeerror, cannot read property 'appendChild' of null").

Why are these implementations not working? I've even tried to break it up into 2 different web components, where the first component is only responsible for creating the ul, but I get the same "appendChild of null" error.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <p>(1) from html - before litelement component</p>
  <raf-demo></raf-demo>
  <p>(2) from html - after litelement component</p>
</body>

<script type="module">
  import { LitElement, html, css } from 'https://unpkg.com/lit-element/lit-element.js?module';

  class rafDemo extends LitElement {
    constructor() {
      super();
      var el = document.createElement("ul");
      el.id = "button1";
      el.innerHTML = "Why after (2)? From the component constructor(), I need something that fires only once and renders before render(), outputting between (1) and (2).";
      document.body.appendChild(el); 
    }
    render() {
      return html`
      <ul id="button2">from component in render() - correctly renders inbetween</ul>
      <button @click="${this.button1}">Add li: works, but after (2)</button>
      <button @click="${this.button2}">Add li: should be between (1) and (2), but error</button>
      `;
    }
    button1(event) {
      const li = document.createElement('li');
      li.textContent = "text";
      document.getElementById('button1').appendChild(li);
    }
    button2(event) {
      const li = document.createElement('li');
      li.textContent = "text";
      document.getElementById('button2').appendChild(li);
    }
  }
  customElements.define('raf-demo', rafDemo);
</script>
</html>

Your help is appreciated!


Solution

  • <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
    </head>
    <body>
      <p>(1) from html - before litelement component</p>
      <raf-demo></raf-demo>
      <p>(2) from html - after litelement component</p>
    </body>
    
    <script type="module">
      import { LitElement, html, css } from 'https://unpkg.com/lit-element/lit-element.js?module';
    
      class rafDemo extends LitElement {
        constructor() {
          super();
          var el = document.createElement("ul");
          el.id = "button1";
          el.innerHTML = "Why after (2)? From the component constructor(), I need something that fires only once and renders before render(), outputting between (1) and (2).";
          document.body.appendChild(el); 
        }
        render() {
          return html`
          <ul id="button2">from component in render() - correctly renders inbetween</ul>
          <button @click="${this.button1}">Add li: works, but after (2)</button>
          <button @click="${this.button2}">Add li: should be between (1) and (2), but error</button>
          `;
        }
        button1(event) {
          const li = document.createElement('li');
          li.textContent = "text";
          var elem = this.shadowRoot.getElementById('button2').appendChild(li);
    	  this.shadowRoot.appendChild(elem);
        }
        button2(event) {
          const li = document.createElement('li');
          li.textContent = "text";
          document.getElementById('button2').appendChild(li);
        }
      }
      customElements.define('raf-demo', rafDemo);
    </script>
    </html>