Search code examples
javascriptdomappendappendchild

Injecting into nested DOM elements created with template literals


I have a fairly simple question. I need to create a list of items that is quite deeply nested, something of this sort (in reality every div has like 20+ classes, data-attributes and so forth):

<div class="one" data-foo="a x y z">
  <div class="two" data-bar="x y z">
    <ul class="three" data-foobar="a b c">
    // contents
    </ul>
  </div>
</div>

My current code is:

    const div1 = document.createElement("div");
    div1.classList.add("one", all the other classes);
    div1.setAttribute("data-foo", "lots of data attributes");
    
    const div2 = document.createElement("div");
    div2.classList.add("two", all the other classes);
    div2.setAttribute("data-bar", "lots of data attributes");
    
    const div3 = document.createElement("div");
    div3.classList.add("three", all the other classes);
    div3.setAttribute("data-foobar", "lots of data attributes");

    div1.appendChild(div2);
    div2.appendChild(div3);

    // getting items for the list & other code

    div3.appendChild(someOutputData);

I came up with a great idea of using a template literal so I don't have to add all the extra properties, classes etc. I know it seems like I could've fixed it with simple foreach but there are bazillion of other things like aria tags etc. etc.

So my old code becomes:

 const wrapper = document.createElement("div");
 const layout = `<div class="one" data-foo="a x y z">
   <div class="two" data-bar="x y z">
     <ul class="three" data-foobar="a b c">
        
     </ul>
   </div>
 </div>`;

wrapper.innerHTML = layout;
wrapper.appendChild(someOutputData);

Million times clearer, right?

The problem is wrapper.appendChild(someOutputData); is not longer injecting data into .three but into <div> wrapper that is above .one.

Is there some way I can target nested DOM elements created via template literals? How can I push my someOutputData (list of nodes) into .three using the second snippet? Also can I omit wrapper somehow? I don't really need the "div" around my list.


Solution

  • So use variables in your template and select the element you created to append the elements

    function createComponent(data1, data2, data3, listData) {
      const wrapper = document.createElement("div");
      const layout = `<div class="one" data-foo="${data1}">
        <div class="two" data-bar="${data2}">
          <ul class="three" data-foobar="${data3}">
            
          </ul>
        </div>
      </div>`;
    
      wrapper.innerHTML = layout;
      wrapper.querySelector("ul").append(...listData);
    
      return wrapper;
    }
    
    
    const makeLi = (text) => {
      const li = document.createElement('li');
      li.textContent = text;
      return li;
    };
    
    const lis = ['foo', 'bar'].map(makeLi);
    const elem = createComponent(1,2,3, lis);
    document.body.append(elem);
    
    
    const lis2 = [1,2,3,4,5].map(makeLi);
    const elem2 = createComponent(1,2,3, lis2);
    document.body.append(elem2);