Search code examples
javascripthtmlcssweb-componentshadow-dom

Insert multiple instances of a same widget into a page


I have a widget which renders a bar chart(HTML/CSS/JavaScript), after point to a particular datasource/API. There is a configuration parameter to change the datasource/API, So I can have multiple instances of the same widget.

Is it possible to do above with the use of shadow-dom? I tried following but noticed widget is unable to select the correct div-element to do the rendering part.

        var host = document.querySelector("#" + gadgetContainer.getAttribute("data-grid-id"));
        var root = host.createShadowRoot();

        var template = document.createElement('template');
        template.innerHTML = gadgetContainer.outerHTML; 
        root.appendChild(document.importNode(template.content, true));

Following is the JavaScript logic which do the widget rendering, I have removed dataset and the config for clarity.

(function () {
    "use strict";
    //dataset used to plot charts
    var dataTable = {}, //REMOVED
        width = document.querySelector('*::shadow #bar').offsetWidth,
        height = 270,   //canvas height
        config = {}; //REMOVED
    widget.renderer.setWidgetName("bar-chart", "BAR CHART");

    chartLib.plot("*::shadow #bar",config, dataTable);
}());

Following is the simple HTML div, all required stylesheets and scripts are there in this page.

<div id="bar" class="bar"></div>

Solution

  • Here is a minimal example of a reuse of a template to create 2 instances of the same widget with Shadow DOM:

    var template = document.querySelector( 'template' )
    
    target1.attachShadow( { mode: 'open' } )
           .appendChild( template.content.cloneNode( true ) )
           
    target2.attachShadow( { mode: 'open' } )
           .appendChild( template.content.cloneNode( true ) )
    <template>
      <style>
        :host { display: inline-block; width:100px; height:50px ; border: 1px solid gray; }
        header { color: blue; font-size:15px; font-weight: bold; }
        section { color: gray; font-size:12px ; }
      </style>
      <header>
         Widget <slot name="number"></slot>
      </header>
      <section>
         Content
      </section>
    </template>
    
    <div id="target1"><span slot="number">1</span></div>
    
    <div id="target2"><span slot="number">2</span></div>

    Note that you should use Shadow DOM "v1", with attachShadow() instead of createShadowRoot() as it's the standard specification that will be implemented on browsers other that Chrome. The old version will be deprecated in the future.

    Use <slot> to get the content of the light DOM and insert it in the Shadow DOM.