wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

HTML's template element in single page applications


"We need to use [insert-framework-of-the-day]!" is the typical answer when asking for a light web single page application (SPA).

It doesn't need to be that way, Paul and myself shared in a recent webinar.

Serving the long tail

Long Tail Apps tend to be outside of IT control, since they bypass the (usually heavy) standup of an IT development project.

That standup could be way lighter, when your application just consists of one html file, one css file, one js file, one manifest and (optionally) one or more image files as well as a definded point of deployment.

The typical objection goes: "But it is never one HTML file, I need a login, a list and a form in read and edit mode"

template to the rescue

The <template> element is part of the WebComponents specification and it is useful in simple SPA applications.

Let's look at a simple example. I replace the logic what to show with buttons and I leave proper CSS to your imagination.

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="index.css" />
    <title>Sample SPA</title>
  </head>
  <body>
    <header>
      <h1>Welcome to the Sample SPA</h1>
    </header>
    <main>
      <p>This is where the action goes</p>
    </main>
    <!-- Buttons instead of state logic -->
    <nav>
      <button id="loginBtn">Show login</button>
      <button id="listBtn">Show list</button>
      <button id="formBtn">Show form</button>
    </nav>
    <footer>&copy; 2025 - Apache 2.0</footer>
    <!-- templates go here -->
    <!-- Login template -->
    <template id="loginTemplate">
      <h2>Login</h2>
      <form>
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" required />
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required />
        <button type="submit">Login</button>
      </form>
    </template>
    <!-- More templtes here -->
    <script src="index.js"></script>
  </body>
</html>

All we need to do is to insert some <template> elements between </footer> and <script>. Template elements are not rendered, so the page doesn't show any of it.

I'm a big fan of seperation of concerns, so my HTML file is free from inline CSS and inline JavaScript. It allows me to run a tight Content Security Policy. The magic is contained in index.js

The structure follows a standard pattern:

  • state variables
  • functions, function bootstrap() last
  • loader
const bootstrap = () => {
  // Initialize the page with any necessary setup
  document.getElementById('loginBtn').addEventListener('click', (event) => {
    event.preventDefault();
    console.log('Login button clicked');
    mainContent('login');
  });
  document.getElementById('listBtn').addEventListener('click', (event) => {
    event.preventDefault();
    console.log('List button clicked');
    mainContent('list');
  });
  document.getElementById('formBtn').addEventListener('click', (event) => {
    event.preventDefault();
    console.log('Form button clicked');
    mainContent('form');
  });
  console.log('Page initialized');
  alert('Welcome to the 2025 SPA!');
};

/* Load the bootstrap script when the page is ready*/
if (document.readyState != 'loading') {
  bootstrap();
} else {
  document.addEventListener('DOMContentLoaded', bootstrap);
}

The code is deliberate repetitive and void of error handling to make it easy to follow. I leave refactoring to you dear reader. The magic is the mainContent() function. The function will:

  • get the template with the given name
  • clear the main area
  • insert a template clone
const mainContent = (templateId) => {
  const main = document.getElementsByTagName('main')[0];
  while (main.firstChild) {
    main.removeChild(main.firstChild);
  }
  const template = document.getElementById(`${templateId}Template`);
  if (template) {
    const clone = template.content.cloneNode(true);
    main.appendChild(clone);
    console.log(`Loaded template: ${templateId}`);
  } else {
    console.error(`Template not found: ${templateId}`);
    const errorMessage = document.createElement('p');
    errorMessage.textContent = `Error: Template not found for ${templateId}`;
    main.appendChild(errorMessage);
  }
};

I deliberatly omitted one template, so you can see the error when you try for yourself.

You might need to design your bootstrap funtion async if your business logic requires a server call, but that's another story for another time.

The result is dependency free, single file per type and doesn't come with a boat anchor of dependencies.

As usual: YMMV


Posted by on 23 June 2025 | Comments (0) | categories: WebComponents WebDevelopment

Comments

  1. No comments yet, be the first to comment