javascripttemplate-literalsnodelist

Passing Nodes to template literal & rendering it?


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?


Solution

  • 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!">