Search code examples
polymerpolymer-3.xlit-html

How to implement lit-html instead of juicy-html in a polymer 3 component?


I am converting an app from polymer 1 to polymer 3. I used juicy-html, but they have not updated to Polymer 3 and I see that there is lit-html. I am wondering how I can change this snippet to using lit-html. It is used for expanding a string like: 'Hello <span class="highlight">world</span>!'

Here is the snippet of code from my polymer 1 component.

<template is="dom-repeat" items="[[item.snippets]]">
  <template is="dom-repeat" items="[[item.matches]]">
    <div><template is="juicy-html" content$="[[item.text]]"></template></div>
  </template>
</template>

Do I need to implement a new component for the inner div? Is there an example that I can look at?

Here is the resulting Polymer 3 element to display a highlighted text within a string:

import {html, LitElement} from '@polymer/lit-element/lit-element.js';

/**
 * `search-snippet-highlight`
 * 
 *
 * @customElement
 * @polymer
 * @demo demo/index.html
 */
class SearchSnippetHighlight extends LitElement {

  static get properties() {
    return {
      snippet: { type: String }
    };
  }

  render() {
    return html`
      <style>.highlight { background-color: yellow; }</style>
      <div .innerHTML="${this.sanitizeHtml(this.snippet)}"></div>`;
  }

  sanitizeHtml(input) {
    return input; // TODO: actually sanitize input with sanitize-html library
  }

}

window.customElements.define('search-snippet-highlight', SearchSnippetHighlight);

Solution

  • The equivalent of that <template> with juicy-html in Polymer's LitElement (the recommended base element that uses lit-html) is:

    render() {
      let content = '';
      for (const s of this.item.snippets) {
        for (const m of s.matches) {
          content += `<div>${m.text}</div>`;
        }
      }
      return html`<div .innerHTML="${this.sanitizeHtml(content)}"></div>`;
    }
    

    The render function above does the following:

    1. builds an HTML string from inputs
    2. sanitizes the HTML
    3. puts the result into the container div's innerHTML, using LitElement syntax (.PROPERTY="VALUE")

    <html>
    <head>
      <!-- Polyfills only needed for Firefox and Edge. -->
      <script src="https://unpkg.com/@webcomponents/webcomponentsjs@latest/webcomponents-loader.js"></script>
    </head>
    <body>
      <!-- Works only on browsers that support Javascript modules like
           Chrome, Safari, Firefox 60, Edge 17 -->
      <script type="module">
        import {LitElement, html} from 'https://unpkg.com/@polymer/lit-element/lit-element.js?module';
    
        class MyElement extends LitElement {
    
          static get properties() {
            return {
              item: { type: Object }
            }
          }
    
          constructor() {
            super();
            this.item = {
              snippets: [
                {
                  matches: [
                    {text: 'hello <span class="highlight">world</span>'},
                    {text: 'we are the <span class="highlight">world</span>'},
                    {text: 'war of the <span class="highlight">world</span>s'},
                  ]
                },
                {
                  matches: [
                    {text: 'the <span class="highlight">cat</span> in the hat'},
                    {text: '<span class="highlight">cat</span>fish are in the water'},
                    {text: '<span class="highlight">cat</span>erpillars become butterflies'},
                  ]
                },
              ]
            };
          }
    
          render() {
            let content = '';
            for (const s of this.item.snippets) {
              for (const m of s.matches) {
                content += `<div>${m.text}</div>`;
              }
            }
            return html`
              <style>.highlight { background: rgb(255, 251, 222); }</style>
              <div .innerHTML="${this.sanitizeHtml(content)}"></div>`;
          }
    
          sanitizeHtml(input) {
            return input; // TODO: actually sanitize input with sanitize-html library
          }
        }
    
        customElements.define('my-element', MyElement);
      </script>
      
      <my-element mood="great"></my-element>
      
    </body>
    </html>