12-Dec

JavaScript

Vanilla SPA

Let us look at what it takes to create our own SPA

3 min read

·

By Dag Frode Solberg

·

December 12, 2019

Back when I started studying IT, I wanted to create a Single Page Application(SPA). I knew some HTML, CSS, and JavaScript, and that was enough to get something up and running.

It is easy to add React, Vue, or Angular to a project that might not need it. Writing everything from scratch is a way to remind us of what these frameworks solve for us and how they limit us.

Let’s look at how we can create a simple SPA, just using vanilla JavaScript.

Templates

Let's start by creating some way of showing content. We will do this by creating a page class that will be hidden by default. In the next section, we will look at code that will add a class to the active page to show the correct content. The main page gets to be the default page shown until the JavaScript has loaded.

The templates we create for the SPA let us add static content and placeholders for the different pages. Usually, only parts of the page are dynamic.

<html>
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" type="text/css" href="main.css" />
  </head>
  <body>
    <div id="main" class="page active">
      <h1>Forside</h1>
      Alle ...
    </div>
    <div id="toll" class="page">
      <a href="#main">&lt; Tilbake til forsiden</a>
      <h1>Tollstations</h1>
      <div id="tollstations"></div>
    </div>
    <div id="page404" class="page">
      <h1>404</h1>
      <h2 id="page404-error"></h2>
    </div>
    <script src="main.js"></script>
  </body>
</html>

To access the templates, you can define an object structure to make it easier to reference the correct elements.

// Define html element references
PAGES = {};

// Main page
PAGES.main = {};
PAGES.main.page = document.querySelector("#main");

// Some other page
PAGES.toll = {};
PAGES.toll.page = document.querySelector("#toll");
PAGES.toll.content = document.querySelector("#tollstations");

// 404
PAGES.page404 = {};
PAGES.page404.page = document.querySelector("#page404");
PAGES.page404.error = document.querySelector("#page404-error");

Navigation

We now have multiple pages in our SPA, most of them hidden with CSS. To make this a SPA we need some way of navigating between the pages.

By using the function window.onhashchange, we can detect when the hash in the URL changes. Combining this with anchors, and we have a way detect when we should changing the content of the SPA.

The URL hash is easily accessible through location.hash.

var path;

// Navigation
function navigate() {
  // Get the url path in a easy
  path = location.hash.substr(1).toLowerCase().split("/");

  // Find what page to show
  var currentPage = path[0];
  if (!PAGES.hasOwnProperty(currentPage)) {
    if (path[0] === "") {
      currentPage = "main";
    } else {
      currentPage = "page404";
    }
  }

  // Hide the previous active page
  for (var page in PAGES) {
    if (PAGES.hasOwnProperty(page)) {
      PAGES[page].page.classList.remove("active");
    }
  }

  // Show the active page and run its custom script
  PAGES[currentPage].page.classList.add("active");
}

// First time loading the page
navigate();

window.onhashchange = navigate;

To only show the active page, we need to add some styling to hide all pages that is not active.

.page {
  display: none;
}

.page.active {
  display: block;
}

Custom code for each page

Some of the pages need to load some additional information based on parameters in the URL. To achieve this, we can create a map of the pages with the code to run for each page.

// Code to run for each page
pageFunctions = {};

// Custom code to run when showing the 404 page
pageFunctions.page404 = function () {
  PAGES.page404.error.innerHTML = `Page ${location.hash.substr(1)} not found!`;
};

To run the custom code, we need to hook the custom code to the navigate function by adding some code to the end of our navigate function.

//Run custom page code if it exists
if (pageFunctions.hasOwnProperty(currentPage)) {
  pageFunctions[currentPage]();
}

Adding dynamic content

To add some dynamic content to the page, we create a new custom function for the page "toll" and add a REST call.

We use fetch to retrieve the data and reduce to generate what we will show.

pageFunctions.toll = function () {
  fetch("https://hotell.difi.no/api/json/vegvesen/bomstasjoner?")
    .then((response) => response.json())
    .then((json) => {
      PAGES.toll.content.innerHTML = json.entries.reduce((acc, toll) => {
        return (acc += tollInfo(toll));
      }, "");
    });
};

function tollInfo(toll) {
  return `
  <div class="toll">
  <h2>${toll.navn}</h2>
  <h3>Takst stor bil: ${toll.takst_stor_bil}</h3>
  <h3>Takst liten bil: ${toll.takst_liten_bil}</h3>
  </div>
  `;
}

Demo

Bringing it all together and we have a working SPA.

Final thoughts

Not bad, considering it is only a few lines of HTML, CSS, and Javascript? I might not use frameworks for all of my pet projects in the time to come. But I will probably continue using frameworks for more significant projects with other developers. I would prefer not to create my version of BobX.