Search code examples
javascripthtmlcssrepeatcopying

How can I repeat complex element multiple times on the same page?


For example, I have social tab in my header which I want to also add to footer. Said tab consist of multiple buttons with SVG images on top, eventlisteners connected to buttons id's and CSS formatting.

Obviously I can rewrite links from js script into html file and just copypaste elements. Will work for links, will be problems with something more complex.

I can also add new IDs for second pair of elements and copypaste the script changing IDs to new ones. Or just use classes instead and collect elements in array with querySelector and adding script with forEach loop.

But I want to ask is there some better solution instead? To just repeat the element at certain place retaining everything from original? Having the same IDs and eventlisteners.


Solution

  • For cloning a node, use the Node.cloneNode() method. This allows you to clone a node and optionally all its child nodes as well.

    This is effectively as if you copied its representing HTML, so only inline listeners, attributes etc. are copied.

    Since the listeners are not copied, you would have to explicitely attach them to the clones 'again'.

    Note: Make sure your DOM tree won't have duplicate IDs after cloning. Assign different IDs to the clones, or consider using a class instead.


    By using event delegation, you could clone and add the elements without having to attach the listeners directly; they use the listener from their ancestor.

    Here is an example of how this could look like:

    const commonAncestor = document.getElementById("common-ancestor");
    const topTablist = document.getElementById("top-tablist");
    const bottomTablist = document.getElementById("bottom-tablist");
    
    // Clone from top to bottom tablist; will use same listener from common ancestor
    const clonedTablist = topTablist.cloneNode(true);
    bottomTablist.replaceChildren(...clonedTablist.children);
    
    // Event delegation: One listener on a common ancestor for multiple targets.
    commonAncestor.addEventListener("click", evt => {
      // Make sure a relevant element (here: `[role=tab]`) is the target.
      const tab = evt.target.closest("[role=tab]");
      if (tab === null) {
        return;
      }
      
      selectPanel(tab.getAttribute("aria-controls"));
    });
    
    function selectPanel(panelId) {
      const tabs = document.querySelectorAll("[role=tab]");
      tabs.forEach(tab => {
        const shouldSelect = tab.getAttribute("aria-controls") === panelId;
        tab.ariaSelected = String(shouldSelect);
      });
      
      const panels = document.querySelectorAll("[role=tabpanel]");
      panels.forEach(panel => {
        const shouldHide = panel.id !== panelId;
        panel.toggleAttribute("hidden", shouldHide);
      });
    }
    #tabpanels {
      border: 1px solid black;
      width: 240px;
      height: 160px;
      display: grid;
    }
    <section id="common-ancestor">
      <div id="top-tablist" role="tablist">
        <button role="tab" aria-selected="true" aria-controls="panel-1">Tab 1</button>
        <button role="tab" aria-selected="false" aria-controls="panel-2">Tab 2</button>
      </div>
      <div id="tabpanels">
        <div id="panel-1" role="tabpanel">Panel 1</div>
        <div id="panel-2" role="tabpanel" hidden>Panel 2</div>
      </div>
      <div id="bottom-tablist" role="tablist">
        <!--Empty, will be filled via JS-->
      </div>
    </section>