Passing Nodes to template literal & rendering it?

107 Views Asked by At

I have a function returning a template literal:

function generateStuff(nodes) {
 const output = `<ul>${nodes}</ul>`;

 return document.body.innerHTML = output;
}

nodes is an array of <li> elements made with createElement and then added to nodes via appendChild.

Is there a way of render a list with generateStuff(nodes)? Right now all it returns is <ul>[object NodeList]</ul> and I just want it to return proper HTML with working links etc.

I suppose I need to parse it somehow but as this is JS and NodeList is native to it maybe there's a method in place for it already?

2

There are 2 best solutions below

4
T.J. Crowder On BEST ANSWER

Is there a way of render a list with generateStuff(nodes)?

Yes, but I'd consider changing how you're doing this instead. But the way you'd do it is with a round-trip through HTML:

function generateStuff(nodes) {
    const output = `<ul>${Array.from(nodes, (node) => node.outerHTML)}</ul>`;

    return (document.body.innerHTML = output);
}

But that's inefficient and lossy (loses event handlers on the nodes, for instance). Instead of using a template literal, consider appending the nodes directly:

function generateStuff(nodes) {
    const ul = document.createElement("ul");
    for (const node of nodes) {
        ul.appendChild(node);
    }
    // Or replace the loop with: `ul.append(...nodes)`

    document.body.innerHTML = "";
    document.body.appendChild(ul);
    return ul.outerHTML; // **If** you really need to return HTML, but hopefully not
}

In a comment you've said:

I think I might have overly simplified my case, normally I wouldn't have use template literal here but instead of I have like 20 nested containers there. Your second approach seems really cool but what if ul is inside of a x different containers? How do I append to then? Do I still have to manually create and append every single one of them? That's what I'm trying to avoid here.

You could create the structure by assigning to innerHTML with a means of identifying the ul, then once the structure exists, do the append:

function generateStuff(nodes) {
    // Create a new replacement `body` element
    const body = document.createElement("body");
    // Create the structure
    body.innerHTML = "<ul class='target'></ul>";
    // Get the `ul` and append to it
    const ul = body.querySelector(".target");
    ul.append(...nodes);

    // Replace the element
    document.body.replaceWith(body);

    // **If** you really need to return HTML
    return document.body.innerHTML;
}

Live Example:

document.querySelector("input[type=button]").addEventListener("click", () => {
    const nodes = Array.from({length: 5}, (_, i) => {
        const li = document.createElement("li");
        li.textContent = "li #" + i;
        return li;
    });
    generateStuff(nodes);
});

function generateStuff(nodes) {
    // Create a new replacement `body` element
    const body = document.createElement("body");
    // Create the structure
    body.innerHTML = "<ul class='target'></ul>";
    // Get the `ul` and append to it
    const ul = body.querySelector(".target");
    ul.append(...nodes);

    // Replace the element
    document.body.replaceWith(body);

    // **If** you really need to return HTML
    return document.body.innerHTML;
}
<input type="button" value="Go!">

1
Unmitigated On

You could use document.createElement and Element#append.

function generateStuff(nodes) {
  const el = document.createElement('ul');
  el.append(...nodes);
  document.body.append(el);
  return el.outerHTML;
}
console.log(generateStuff([...Array(3)].map((_, i) => Object.assign(document.createElement('li'), {textContent: i}))));