custom-elementlit-elementlit-htmltrumbowyg

Prevent DOM reuse within lit-html/lit-element


I am looking for a way to NOT reuse DOM elements within lit-html/lit-element (yes, I know, I'm turning off one of the prime features). The particular scenario is moving an existing system to lit-element/lit-html that at certain points embeds the trumbowyg WYSIWYG editor. This editor attaches itself to a <div> tag made within lit-element and modifies its own internal DOM, but of course lit-html does not know that this has happened, so it will often reuse the same <div> tag instead of creating a new one. I am looking for something similar to the vue.js key attribute (e.g., preventing Vue from aggresively reusing dom-elements)

I feel like the live() directive in lit-html should be useful for this, but that guards against reuse based on a given attribute, and I want to prevent reuse even if all attributes are identical. Thanks!


Solution

  • I have had similar issues with rich text editors and contenteditable - due to how templates update the DOM you don't want that to be part of a template.

    You do this by adding a new element with the non-Lit DOM and then adding that to the DOM that Lit does manage:

    class TrumbowygEditor
      extends HTMLElement {
    
      constructor() {
        super();
        const shadow = this.attachShadow({mode: 'open'});
        const div = document.createElement('div');
        shadow.appendChild(div);
        
        const style = document.createElement('style');
        // Add CSS required 
        shadow.appendChild(style);
    
        $(div).trumbowyg(); //init
      }
    }
    
    customElements.define('trumbowyg-editor', TrumbowygEditor);
    

    As this is running in a custom element's shadow DOM Lit won't touch it, you can do:

    html`
        <div>Lit managed DOM</div>
        <trumbowyg-editor></trumbowyg-editor>`;
    

    However, you will have to implement properties and events on TrumbowygEditor to add everything you want to pass to or get from the nested jQuery component.

    You can add the scripts with import if you can get module versions of jQuery/Trumbowyg (or your build tools support it) or you can add <script> tags to your component, add fallback loading DOM content in the constructor, and then on the load event of the <script> call the $(div).trumbowyg() to init the component.

    While messier and more work I'd recommend the latter as both components are large and (thanks to jQuery being built on assumptions that are now 15 years old) need to load synchronously (<script async or <script defer don't work). Especially on slower connections Lit will be ready long before jQuery/Trumbowyg have loaded in, so you want <trumbowyg-editor> to look good (show spinner, layout in the right amount of space etc) while that's happening.