1

From the one of answer to Creating a new DOM element from an HTML string using built-in DOM methods or Prototype question:

Most current browsers support HTML elements, which provide a more reliable way of turning creating elements from strings.

Source

Well, but generally it's useless to retrieve the template element without interpolating some data into it. But how to do it?

Concrete example

Retrieve below template from DOM and fill h1 and p elements by the data. It's allowed to modify the template tag content to make the access to elements easier, but let me repeat: please, no frameworks.

<template id="CatCardTemplate">
  <div class="CatCard">
    <h1></h1>
    <p></p>
  </div>
</template>

DOM manipulation solution

In the MDN example, the interpolation is being provided by DOM manipulations:

var tbody = document.querySelector("tbody");
var template = document.querySelector('#productrow');

var clone = template.content.cloneNode(true);
var td = clone.querySelectorAll("td");
td[0].textContent = "1235646565";
td[1].textContent = "Stuff";

tbody.appendChild(clone);

var clone2 = template.content.cloneNode(true);
td = clone2.querySelectorAll("td");
td[0].textContent = "0384928528";
td[1].textContent = "Acme Kidney Beans 2";

tbody.appendChild(clone2);

I can't believe if it's best that we can in 2021 without frameworks.

My efforts: musings about Handlebars solution

Because Handlebars is not a framework, it's allowed alternative. If Handlebars code is a valid HTML, we can use it inside template elements:

<template id="CatCardTemplate">
  <div class="CatCard">
    <h1>{{name}}</h1>
    <p>Age: {{age}}</p>
  </div>
</template>

Now we can pick up the template and interpolate the data by Handlebars:

/*
 * ⇩ Works, but causes "ModuleNotFoundError: Module not found: Error: Can't resolve 'fs' in ...".
 *   It's knows issue but I did not fount the acceptable solution yet.
 *   @see https://github.com/handlebars-lang/handlebars.js/issues/1174
 */
import Handlebars from "handlebars";

type CatData = {
  name: string;
  age: number;
};

const catCardTemplate: HTMLTemplateElement | null = document.getElementById<HTMLTemplateElement>("CatCardTemplate");

// Handle null case...

const renderCatCard: Handlebars.TemplateDelegate<Cat> = Handlebars.compile(catCardTemplate.outerHTML);

console.log(renderTab({
  cat: "Luna",
  age: 2
}))
Takeshi Tokugawa YD
  • 670
  • 5
  • 40
  • 124

2 Answers2

2

Not exactly sure what you want. But you can definitely convert the template into string first, then use regular expression to analysis the code by updating the variable, {{__variable_name__}} to the variable you want.

Here is a stupid try, that do that handle any of the exception, but can give you a glimpse on how you can actually do the job.

var tbody = document.querySelector("tbody");
const arr = [
    {
        code: "1235646565",
        productName: "Stuff"
    },
    {
        code: "0384928528",
        productName: "Acme Kidney Beans 2"
    },
    {
        codeX: "17263871263", // typo, then will not display
        productName: "Coca Cola"
    }
];
arr.forEach(row => {
    // console.log(convertTemplate("#productrow", row));
    tbody.appendChild(convertTemplate("#productrow", row));
});

function convertTemplate(templateSelector, variablesObj) {
    const template = document.querySelector(templateSelector);
    const templateStr = template.innerHTML; // get the corresponding DOM as string as that we can manipulate on
    // find all the corresponding {{ code }} there, and extract the name inside
    const regex = /{{([^}]+)}}/g

    const result = [];
    let arr;
    while (arr = regex.exec(templateStr)) {
        if (arr.length > 1) {
            const variableName = arr[1];
            result.push(variableName);
        }
    }

    let finalTemplateStr = templateStr;
    result.forEach(variableName => {
        const value = variablesObj[variableName] || '';
        if (!value) {
            console.error('no value are available set as empty first');
        }
        finalTemplateStr = finalTemplateStr.replace(`{{${variableName}}}`, value)
    });

    // create element same as root element described
    const rootElemRegex = new RegExp("<([^/>]+)/?>", "i");
    arr = rootElemRegex.exec(finalTemplateStr);
    const rootElemName = arr[1];
    const div = document.createElement(rootElemName);
    div.innerHTML = finalTemplateStr;
    return div; 
}
<template id="productrow">
    <tr>
        <td class="record">{{code}}</td>
        <td>{{productName}}</td>
    </tr>
</template>
<table>
    <thead>
        <tr>
            <td>UPC_Code</td>
            <td>Product_Name</td>
        </tr>
    </thead>
    <tbody>
        <!-- existing data could optionally be included here -->
    </tbody>
</table>

P.S. I have no knowledge on HTML native template module, if it already includes some easy to use tool, just share with us. Thanks.

CHANist
  • 1,302
  • 11
  • 36
  • Thank you for the answer! The title of your solution is "Create the own template syntax and implement the template engine yourself". – Takeshi Tokugawa YD Feb 06 '21 at 05:32
  • Great solution! Some of my styling got messed up by adding an extra div so I changed the last 6 lines of `convertTemplate` to: `let finalTemplate = document.createElement('div');` `finalTemplate.innerHTML = finalTemplateStr;` `finalTemplate = finalTemplate.firstElementChild;` `return finalTemplate;` – Marek OCLC May 03 '22 at 20:55
0

You would think that you shouldn't need handlebars or any similar templating library-since JS now has built in string interpolation in the form of Template literals. You might suspect you could create a template like this:

<template id="testTemplate">
    <div>
        <h1>${name}</h1>
        <h2>${age}</h2>    
    </div>
</template>

But unfortunately, HTML Templates and JS Template literals truly are two great tastes that don't work great together.

JS String interpolation via Template literals only works on backtick enclosed string literals, not string variables. So once you grab the contents of the template(or the template's innerHTML) there's almost no way to do this(interpolate).

I say 'almost' because you can solve it by dipping into your book of Dark(evil) JavaScript.

let name = "Ryu";
let age = "40";

function cloneTemplate(id) {
    let template = document.getElementById(id);
    let clone = template.content.firstElementChild.cloneNode(true);
    let interpolatedString = new Function("name", "age", "return `" + clone.outerHTML + "`")(name, age);
    return interpolatedString;
}

let result = cloneTemplate("testTemplate");

Although this works, its creating dynamically evaluated JS, which comes with all the typical warnings regarding security, etc. You probably are better off using string replace methods.