String Replacement, Another Use for <template>

Bill Miller
6 min readJun 26, 2022

In one of my previous articles, I created a vanilla JavaScript app to create a To-Do list. In that article I made use of the <template> element. It’s a great new addition to HTML because it allows you to easily template any markup that isn’t going to be used right away but inserted into the DOM later. It helps you observe the separation of concerns by keeping your markup where it belongs…in your HTML file and out of the JavaScript.

I Created the Exact Same App with Simple Web Standards | by Bill Miller | Medium

Typically, <template> is used in conjunction with the .cloneNode method to copy the elements within it. There is, however, an alternative method for which <template> can be used to render HTML. That method is string replacement. String replacement is a great method to use when you have static text which needs to be rendered, and the markup for the static text is a repeated pattern. A table of data is a good example of this. I developed an app which demonstrates the method.

The “Top 10 App”

We start with a <form> which asks the user to select from two different top 10 lists. One is a list of the top 10 songs according to the Billboard Top 100 chart, and the other is a list of the Top 10 books according to the New York Times Bestseller list. A table with the information is displayed once the user makes a choice. They are also able to toggle back and forth between the lists.

I prettied it a little bit to make the “app” presentable. However, I didn’t go overboard with the CSS since styling is not the focus of this article.

The Markup

Markup-wise the HTML is very simple. Enclosed within the <main> we have a<header> with a level 1 heading introducing the page. This is followed by the<form> containing a <fieldset> and two radio buttons. Below that we have the<output> which will catch the <table> element when the user selects one of the radio buttons.

Below the <main> are two <template> elements. This is where the magic happens. The first is”#top10ListHeader” which sets up the table and inserts the <caption> and table headings. Within the encapsulated text of the<caption> element you may notice a bit of text, %name%. I use the percent symbols to indicate the text which is going to be replaced, and the text in the middle is a memory jog as to what needs to go there. In essence, I’m going to replace%name% with either “Music” or “Book” depending on the list the user selects.

The second <template>, “#top10Item”, is a simple table row with two elements to hold the title of the song or book and the name of the author. There are also percented strings within them. %Title% and %Author% respectively. I’m guessing you can probably figure out what data is going to go there.

<main>
<header>
<h1>Top 10 Lists - June 2022</h1>
</header>
<form>
<fieldset>
<legend>Choose the list you would like to review</legend>
<label><input type="radio" name="listOption" value="Music"> Music Top 10 List</label>
<label><input type="radio" name="listOption" value="Book"> Book Top 10 List</label>
</fieldset>
</form>
<output></output>
</main>
<template id="top10Header">
<table>
<thead>
<caption>
<h2>The Top 10 %name% List</h2>
</caption>
<tr>
<th scope="col">Title</th>
<th scope="col">By</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</template>
<template id="top10Item">
<tr>
<td>%title%</td>
<td>%author%</td>
</tr>
</template>

The Script

First, we add event listeners to the radio button inputs to trigger the displayTable() function. Each passes their value to the function if checked. The input values also correspond to the property value of the object we’ll see in a moment.

//Link the event listener
for(let input of document.querySelectorAll("input[type='radio']"))
{
input.addEventListener("change", setAndDisplayTable); }
//If the radio button is checked then execute displayTable
// using the radio button's value function
setAndDisplayTable() {
if (this.checked){
displayTable(this.value);
}
return;}

The table data is stored in an object called top10Lists. It looks like this…

const top10Lists =
{ "Music": [
{ "Title": "First Class", "Author": "Jack Harlow" },
{ "Title": "As It Was", "Author": "Harry Styles" },
{ "Title": "N95", "Author": "Kendrick Lamar" },
{ "Title": "Wait for U", "Author": "Future Featuring Drake, Tems" },
{ "Title": "Die Hard", "Author": "Kendrick Lamar, Blixst, Amanda Reifer" },
{ "Title": "You Proof", "Author": "Morgan Wallen" },
{ "Title": "Silent Hill", "Author": "Kendrick Lamar, Kodak Black" },
{ "Title": "United in Grief", "Author": "Kendrick Lamar" },
{ "Title": "About Damned Time", "Author": "Lizzo" },
{ "Title": "Big Energy", "Author": "Latto" }
],
"Book": [
{ "Title": "In the Blood", "Author": "Jack Carr" },
{ "Title": "Book Lovers", "Author": "Emily Henry" },
{ "Title": "It Ends with Us", "Author": "Colleen Hoover" },
{ "Title": "Where the Crawdads Sing", "Author": "Delia Owens" },
{ "Title": "Verity", "Author": "Colleen Hoover" },
{ "Title": "The Seven Husbands of Evelyn Hugo", "Author": "Taylor Jenkins Reid" },
{ "Title": "Ugly Love", "Author": "Colleen Hoover" },
{ "Title": "22 Seconds", "Author": "James Patterson, Maxine Paetro" },
{ "Title": "This Time Tomorrow", "Author": "Emma Straub" },
{ "Title": "People We Meet On Vacation", "Author": "Emily Henry" } ] };

We finish the whole thing off with the displayTable function. When called, it grabs the template with the ID of “#top10ListHeader”. We then clear the output element just in case we’re swapping out the tables as opposed to displaying for the first time. To change the title of the table within its caption we use the .replaceAll method, swapping %name% for the ID of the radio button which called the function. We then publish that markup to the output. This also builds the<tbody> element we’ll be using to display the lists

function displayTable(tableName) {
let top10Header =
document.getElementById("top10Header").innerHTML,
output = document.querySelector("output"),
tableBody = document.querySelector("tbody");
//Replace the %name% string with the name of the table
top10Header = top10Header.replaceAll("%name%", tableName);
//Clear Output then insert the table header
output.innerHTML = "";
output.insertAdjacentHTML("afterbegin", top10Header);

Next, we need to display the items which will go in the top 10 list. We crawl the array within the object property corresponding to the value of the calling radio button. We snag a copy of the innerHTML from top10Item template, and .replaceAll for the %Title% and %Author% respectively. It then gets inserted “beforeend” to the<tbody> of the table.

//The table body must be queried after the insertion of the header // until this point the table doesn't exist in the DOM
let tableBody = document.querySelector("tbody");
for (let item of top10Lists[tableName]) {

//Fetch the inner HTML of the template
let top10Item = document.getElementById("top10Item").innerHTML;
//Perform the string replacement
top10Item = top10Item.replaceAll("%title%", item.Title);
top10Item = top10Item.replaceAll("%author%", item.Author)

//Insert the item into the DOM, then rinse/repeat
tableBody.insertAdjacentHTML("beforeend", top10Item);
}
return;
}

The Whole Onion

Here is the whole project in a pen…

Wrap-Up

This method works well if you have static text to display, and don’t really have a need to attach event listeners to the internal elements. One could probably do that if necessary, but it’s a little bit more cumbersome. You can’t attach event listeners to string text, so you would have to insert the elements into the DOM prior to attachment. In these situations, copying <template> contents is better using the .cloneNode method. That way the markup is already defined as an element within a document fragment. You can query and attach with ease prior to DOM insertion.

Technically, this method is probably a little slower in execution than the .cloneNode followed by .appendChild methods. Using .insertAdjacentHTML requires the browser to parse the string markup prior to insertion and convert it to an element or elements. While this is technically true, the slower speed is not perceivable by the end user. The end user doesn’t care what methods and code you used to display the data.

I wanted to offer this method up as yet another way that we can use the <template> element to prototype HTML. It keeps the markup out of the JavaScript code, observes the separation of concerns, and makes coding simple.

Originally published at https://www.tripple6.design.

--

--