Search code examples
javascripthtmlweb-component

Not able to render vanilla webcomponent imported as template


I am trying to build a webcomponent and import it in main HTML file.

my index.html:

<!DOCTYPE html>
<html>
<head>
  <link rel="import" href="./tile.html">
</head>
<body>
  <a href="javascript:;" onclick="buildTile()">Build</a>

  <ns-tile /> <!-- Works -->

  <div id="ns-search-results"></div>
<script> 
  async function buildTile() { 
    const results = document.getElementById('ns-search-results'); 
    for(let i = 0; i < 10; i++) { 
      var nsTile = document.createElement('ns-tile'); 
      results.appendChild(nsTile); 
    } 
  }
</script>
</body>
</html>

tile.html

<template id="ns-item-tile"> 
  <style> </style> 
  <div class="tile"> Tile content </div>
</template>
<script> 
class NSTile extends HTMLElement { 
  constructor() {
    super();
  } 

  connectedCallback() { 
    var importedDoc = document.currentScript.ownerDocument; 
    let shadowRoot = this.attachShadow({ mode: 'open' }); 
    const t = importedDoc.querySelector('#ns-item-tile'); 
    const instance = t.content.cloneNode(true); 
    shadowRoot.appendChild(instance); 

    function init() { 
      const head = shadowRoot.querySelector('.tile'); 
      head.addEventListener("click", function () { 
        console.log('click'); 
      }); 
    } 
    init(); 
  } 
} // init the element

customElements.define("ns-tile", NSTile);
</script>

When I use <ns-tile /> directly in index.html, content renders properly. But when I try to render it inside a for loop in buildTile method, I am getting an error Uncaught TypeError: Cannot read property 'ownerDocument' of null at HTMLElement.connectedCallback (tile.html:16)

How can I access the html template inside tile.html so I can build using for loop?


Solution

  • A few things:

    1. Custom elements are never self closing. <ns-tile /> should be replaced with <ns-tile></ns-tile>
    2. <link rel="import" href=""> is dead. Just load the script file normally or use ES6 Import instead.
    3. If you do continue to use link rel="import" then you must move var importedDoc = document.currentScript.ownerDocument; outside of your class.
    4. I don't see anywhere that you are "defining" your component: customElements.define('tag-name', className);

    Here are some changes to the component file. If you change it to a JS file then it can look like this:

    const template = `<style></style> 
    <div class="tile"> Tile content </div>
    `;
    
    class NSTile extends HTMLElement { 
      constructor() {
        super();
        let shadowRoot = this.attachShadow({ mode: 'open' }); 
        shadowRoot.innerHTML = template; 
    
        const head = shadowRoot.querySelector('.tile'); 
        head.addEventListener("click", function () { 
          console.log('click'); 
        }); 
      } 
    }
    
    customElements.define('ns-tile', NSTile);
    <ns-tile></ns-tile>