Web Apps

13.2 SPA and JAM Stack

# Multi-Page Scripts

When building websites that are made up of multiple HTML files, you must bear in mind that, every time the browser loads a webpage it is replacing an old one. When the new page loads, it will be parsed by the browser and it will load any attached JS files, CSS files, fonts, and images.

It is likely that these other files will be in the browser's cache (not to be confused with the Cache API which is controlled by the developer). However, to run, they still need to be loaded into memory.

Any variables and functions that you had in your old JS file are being recreated.

Any values that were in your old variables ARE GONE.

This is why we need to use that DOMContentLoaded event to call an init method inside our namespace object. We need to run through the setup and add event listeners on every page load.

So, how does our script know which page has been loaded?

# Page Specific Code

There are many ways that you can figure out which page just loaded our script. You can look at the location object to get the URL. But the simplest way to do it is to add a unique id attribute to your <body> element in each of your webpages.

<html>
  <head></head>
  <body id="home"></body>
</html>
1
2
3
4

Then you can use that in your code as the identifier for each page.

const APP = {
  init: () => {
    //any page has loaded
    APP.addListeners();
    APP.pageSpecific();
  },
  pageSpecific: () => {
    //run code that is specific to each page
    let id = document.body.id;
    if (!id) return; //page is missing an id
    switch (id) {
      case 'home':
        //on the home page
        break;
      case 'other':
        //some other page
        break;
      case 'contact':
        //on the contact page
        break;
      default:
      //page that we don't recognize the id
      //or page with no specific code needed
    }
  },
  addListeners: () => {
    //load any event listeners that are common to all pages
    //like hashchange, popstate, main nav clicks, etc
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# CSS and Style Implications

If you are adding unique ids to each page's <body> tag then you can leverage that in your CSS.

A common use is to style current links or hide and show page specific elements.

#home .sidebar {
  display: none;
  /* on the home page hide the sidebar */
}
#products .footer {
  position: fixed;
  width: 100vw;
  bottom: 0;
  height: 6rem;
  /* make the footer fixed on the products page */
}
1
2
3
4
5
6
7
8
9
10
11

# SPA

A Single Page Application (SPA) is what the name implies. A single HTML file that gets loaded to the browser and then the JavaScript is the Application logic responsible for showing and hiding and loading new content. We can use the History API to manage the location bar and history array. We use the Fetch API to get new content from the server. We can use the Cache API and the Web Storage API to save items, like images or JSON files, loaded from the server, which will improve performance in your app.

# JAMStack

The JAM in JAMStack refers to JavaScript, APIs, and Markup. Basically what we have been doing since week 5. A JAMStack application can be a SPA or it can be a multi-page web app. It uses all the same HTML5 APIs as a SPA site - history api, fetch api, cache api, web storage api and more.

The defining feature of a JAMStack site is that all the files used to make the site are static ones. There is no server-side programming required, with the exception of the APIs. Generally, the APIs are built independently from the website. They can be hosted elsewhere and use any kind of server-side programming language.

You can even use Github Pages to host and run a JAMStack website. The dynamic content will come from one or more APIs and be fed into your static website using client-side JavaScript.

# Server-Side Programming, Servers, Hosting & Databases

There are so many different programming languages in use today that it is virtually impossible to keep up with all of them. There are also many different kinds of web servers. Some webservers are able to support multiple programming languages, some support only one. On top of all that there are tons of options for different types of databases.

There are many kinds of hosting choices available to you and the one that you pick will be directly connected to which programming language and which kind of database you want to use.

# Languages

Here are a few of the server-side programming languages that you could use to build websites.

  • NodeJS
  • PHP
  • Ruby
  • ASP.Net C#
  • Python
  • Java J2EE

Most of those come with a variety of frameworks that can be used with the language to make development faster.

Next semester we will be leveraging your knowledge of JavaScript to create websites and APIs with NodeJS.

# Databases

There are different types of Databases, each with its own strengths and weaknesses. Here are the main categories with a few examples, in brackets, of their implementations.

  • Text Files (txt, csv, xlsx)
  • Relational Databases (MySQL, MariaDB, PostgreSQL, SQL Server, Oracle)
  • Document Databases (MongoDB, DynamoDB, DocumentDB)
  • Graph Databases (Neo4j)
  • Hierarchical Databases

Each type has its own learning curve and mental models that you need to figure out.

In our program we will be focused on Document Databases but will give you a brief introduction to Relational Databases too.

The database is where you save the data that you want to save with multiple users. It is where APIs get the information that they are sharing with webpages.

# Window.open()

If you ever need to open a new window or tab from JavaScript, you can do this with the window.open() method. The decision to open a new tab or window depends on the settings in the user's browser. It is not something we can control with JS. There is also a window.close() method.

When you use JavaScript to open a new window/tab you will be returned a reference to the newly created window object. This means that you can control what is happening in the page that you opened from the creating page script.

let url = 'https://www.mydomain.com';
let features =
  'menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=no';
let winRef = window.open(url, '', features);
1
2
3
4

The features parameter controls which elements will be visible if you are opening a new window.

If the domain for the page that you are opening is the same as the domain for the page that is running your script, then you will have full control over it. If the domain is different then there will be some security limitations to what you can control.

//to close the a window or tab that was opened by JS from within that page
window.close();

//to close the newly opened window from the script that opened it
winRef.close(); //winRef is the variable created in the script above
1
2
3
4
5

# Window Loading and Unloading

When you are loading and unloading pages there are a series of event that take place. They will always take place in the same order.

This concept applies to many of the events that you use in your code. A single user action results in a cascade of events. A user taps on theA key on their keyboard -> at the very least there will be a keydown, keyup, and keypress event sequence. However, depending on what element had focus there could also be an input event or a propertychange event. These events all have a predefined sequence that they have to follow.

When a page is loading these are the events that could be triggered in the order that they would occur.

  1. window.readystatechange
  2. window.DOMContentLoaded
  3. window.load
  4. window.pageshow
  5. window.popstate
  6. window.hashchange

When the user clicks on a link to navigate away from the page, these are the events that will be triggered, in the order that they will occur.

  1. element.mousedown
  2. element.focus
  3. element.mouseup
  4. element.click
  5. window.pagehide
  6. window.beforeunload
  7. window.unload

And, if a page loses focus by a user switching tabs or applications.

  • window.visibilitychange

If you switch away from a page by moving to another tab or, on a mobile device, opening another app, then you will trigger the window.visibilitychange event. Switching back to the tab will also trigger the visibilitychange event. The pagehide event will not be triggered by leaving the tab or opening another app. The beforeunload and unload events are not as reliable for running code. Usually, by the time those events happen the process of leaving is already underway. If you want to send a fetch() call to upload recent data then the pagehide or beforeunload can be used. The unload event cannot be used for fetch because the page will be gone before the message is sent. Even with beforeunload you can send data but you will not receive a result before the page is unloaded.

Something that might be of use to you at some point is the persisted property, of the pageshow and pagehide events, which has a boolean value indicating whether or not the page was loaded from the browser cache.

window.addEventListener('pageshow', (ev) => {
  if (ev.persisted) {
    //page was loaded from browser cache
  } else {
    //page was NOT loaded from browser cache
  }
});
1
2
3
4
5
6
7

This might be useful if you decide you want to check for new versions of files or use your own files from the Cache API.

# What to do this week

TODO

Things to do before next week.

  • Read all the content from Modules 13.1, 13.2, and 14.1.
Last Updated: 5/31/2023, 8:15:38 PM