skip to Main Content

I want to instantiate a web component based on a <template> element in the DOM using JS to set 2 slot values title and text.

template HTML

<template id="tiddler-display">
  <div class="tiddler">
    <h1><slot name="title">TITLE</slot></h1>
    <p><slot name="text">TEXT</slot></p>
  </div>
</template>

How does one insert ‘foo’ and ‘bar’ into the text/tile slots?

  customElements.define(
    'tiddler-display',
    class extends HTMLElement {
      constructor() {
        super();
        let template = document.getElementById('tiddler-display');
        let templateContent = template.content;
        const shadowRoot = this.attachShadow({mode: 'open'});
        shadowRoot.appendChild(templateContent.cloneNode(true));
      }
    },
  );
  // Instantiate
  let newElement = document.createElement('tiddler-display');
  // How to insert 'foo' and 'bar' into the text/tile slots?
  newElement.shadowRoot.querySelectorAll('slot')[0].innerHTML = 'foo';
  // This cannot be the best way 👆

2

Answers


  1. TL;DR : https://jsfiddle.net/zL8gapn2/15/

    Hi,

    What you did works fine, however if you have a lot of slots and want to reuse the template, it can be a bit long…
    I use string templating for this :

    What you can do is create a js object that fits your template

    var jsobject = { text:'foo', title:'bar'}
    <template id="tiddler-display">
      <div class="tiddler">
        <h1><slot name="title">${this.title}</slot></h1>
        <p><slot name="text">${this.text}</slot></p>
      </div>
    </template>

    The you could get the html inside the template

    let template = document.getElementById('tiddler-display');
    let templateContent = template.innerHTML;

    Then you create a Function object, and call it providing your js object

    let fillTemplate = new Function("return `" + templateContent + "`;");
    let instanceContent = fillTemplate.call(jsobject);
    let instance = document.createElement("div");
    instance.innerHTML = instanceContent;
    document.body.appendChild(instance);

    I’ve worked a bit with templates lately, and that’s how I proceed.

    Login or Signup to reply.
  2. Your <template> defines what is in shadowDOM

    lightDOM needs to provide the content for the <slot> elements reflected in shadowDOM

    When you do document.createElement("tiddler-display")
    you create the <tiddler-display> HTML Node, but without lightDOM content inside

    See below code

    • It demos the full (inner)HTML for <tiddler-display> with content to be slotted (aka reflected) from lightDOM to shadowDOM
       <tiddler-display>
          <span slot="text">Are great!</span>
          <i slot="title">Web Components</id>
       </tiddler-display>
    

    • It has a instantiate( ) function that programmaticaly creates the Web Component,
      and sets its lightDOM content
    <template id="tiddler-display">
      <div class="tiddler">
        <h3><slot name="title">TITLE</slot></h3>
        <p><slot name="text">TEXT</slot></p>
      </div>
    </template>
    
    <tiddler-display>
      <span slot="text">Are great!</span>
      <i slot="title">Web Components</i>
    </tiddler-display>
    
    <script>
      const createElement = (tag, props = {}) => Object.assign(document.createElement(tag), props);
      const instantiate = (tag, title, text) => createElement(tag, {
        innerHTML: `<b slot="title">${title}</b><span slot="text">${text}</span>`
      });
      customElements.define('tiddler-display', class extends HTMLElement {
          constructor() {
            super().attachShadow({mode:'open'}).append(
                createElement("style", {
                  textContent:`:host{display:block;background:beige}`+ 
                              `::slotted( i ){background:gold}`
                }),
                document.getElementById(this.localName).content.cloneNode(true)
              );
          }
        },
      );
      let newElement = instantiate("tiddler-display", "FOO", "BAR");
      document.body.append(newElement);
    </script>

    DOM parts

    The other answer uses Template Literals to set content. Different solution, that works
    (but the shown solution has no Web Component code).
    You can apply this to Web Components as well. Make it fancy to extract the slot names from the template, whatever you need.

    If you have a lot of templates/content you might want to check out the
    Google Lit BaseClass for Web Components, it does all of the Template Literal heavylifting for you: https://lit.dev/

    Note that the Lit (Template Literal) solution will be obsolete within the next year (or two)
    The Web Components standards body is very active on bringing DOM Parts into the standard.

    See DOM Parts in https://github.com/WICG/webcomponents/tree/gh-pages/proposals

    Declarative shadowDOM

    Note you can create one Web Component without any JavaScript
    Great for SSR or rapid development (write HTML first, once it works create the optimized JS version)

    <tiddler-display>
        <template shadowrootmode="open">
        
            <style>
                :host{ display:block;background:beige }
                ::slotted( i ){ background:gold }
            </style>
            
            <h1><slot name="title">TITLE</slot></h1>
            <p><slot name="text">ARE COOL!</slot></p>
            <slot></slot>
            
        </template>
        
        <i slot="title">Web Components</i>
        <img src="https://i.imgur.com/bjvx7vf.png">
        
    </tiddler-display>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search