Creating a Progressive Web Application (PWA) using HTML and Vanilla JavaScript
Tags: pwa
Published: 2023-05-29 07:51:17

---
title: Creating a Progressive Web Application (PWA) using HTML and Vanilla JavaScript 
published: true
description: This article guides through the creation of a Progressive Web Application (PWA) featuring an MP3 player using HTML and Vanilla JavaScript, leveraging service workers for caching and explaining each segment of code in detail
tags: pwa
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-03-24 07:59 +0000
---

Creating a Progressive Web Application (PWA) using HTML and Vanilla JavaScript with an mp3 audio player can be amazing learning. 

We will look into a basic example of how service workers can be used for caching in a PWA. Our application is an MP3 Player, but the principles here are applicable to any PWA.


## Manifest.json

Let's start with `manifest.json`:

```json
{
  "short_name": "PWA MP3 Player",
  "name": "Progressive Web Application MP3 Player",
  "description": "An MP3 Player built as a Progressive Web Application",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#3f51b5",
  "icons": [
    {
      "src": "icon.png",
      "sizes": "192x192",
      "type": "image/png"
    }
  ]
}
```

Manifest.json provides information about an application (such as name, author, icon, and description) in a JSON text file. 

The purpose of the `manifest.json` file is to provide a **centralized place to put metadata associated with a web application**. 

Here's a breakdown of each property:

- `short_name`: The short_name is a shorter version of the app's name and is used on the user's home screen and wherever space is limited.
  
- `name`: The full name of the application.

- `description`: Description of the application.

- `start_url`: This is the URL that the PWA will start on when the user launches the app. In this case, it's set to open the application's home page.
  
- `display`: The display property defines the developer’s preferred display mode for the website. The "standalone" value means that the application will look and feel like a standalone application. This can include the application having a different window, its own icon in the application launcher, etc.

- `background_color`: This is the background color of the application, which is used during splash screen display when launching the app from a tile on the home screen.
  
- `theme_color`: Defines the default theme color for the application, which affects the color of the toolbar and the color in the task switcher.

- `icons`: This property represents an array of image files that can be used as the application's icon. Each object in the array is an image, and you can specify the path, the sizes, and the image type. The `src` key is the path to the image file, the `sizes` key is a space-separated list of image dimensions, and the `type` key is a MIME type for the image file.
  
In summary, the `manifest.json` file is a configuration file for your PWA that provides details about how your app should behave when installed on a device.

## Service Worker

Next, let's set up a basic service worker (`sw.js`):

```javascript
const cacheName = 'pwa-mp3-player-v1';
const assetsToCache = [
  './',
  'index.html',
  'main.js',
  'styles.css',
  'icon.png'
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(cacheName)
      .then((cache) => {
        return cache.addAll(assetsToCache);
      })
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then((response) => {
        return response || fetch(event.request);
      })
  );
});
```

When we talk about Progressive Web Apps (PWAs), the term "Service Workers" frequently appears. Service Workers, the true powerhouse behind PWAs, perform various essential functions, one of the most crucial being handling caching strategies. 

Let's break down this piece of JavaScript code:

```javascript
const cacheName = 'pwa-mp3-player-v1';
```

Here, we are declaring a constant `cacheName` to denote the version of our cache. It's common practice to version our caches because it makes managing them easier. If we update any files in our project, we can change the cache name, which will then trigger the service worker to cache the new files.

```javascript
const assetsToCache = [
  './',
  'index.html',
  'main.js',
  'styles.css',
  'icon.png'
];
```

In `assetsToCache`, we list the files that we want to cache. This usually includes all of the static files necessary for your app shell (the minimal HTML, CSS, and JavaScript required to power the user interface of a progressive web app).

The `self.addEventListener` part is where the magic happens. Service Workers can listen to several lifecycle events. The two most commonly used ones are 'install' and 'fetch'.

```javascript
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(cacheName)
      .then((cache) => {
        return cache.addAll(assetsToCache);
      })
  );
});
```

The 'install' event fires when the service worker is first installed. Here, we tell the service worker to open the cache using `caches.open()`, and then cache all necessary assets using `cache.addAll()`. `event.waitUntil()` is used to ensure that the service worker doesn’t stop installing until the code inside `waitUntil` has completed.

```javascript
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then((response) => {
        return response || fetch(event.request);
      })
  );
});
```

After the installation and caching process, the 'fetch' event fires whenever any resource (specified in `assetsToCache`) is requested. The `event.respondWith()` method allows us to intercept the request and provide a response.

Inside `respondWith`, we're asking the cache if the requested resource is already in there. If yes (`caches.match(event.request)` returns a response), we respond with the cached version, if not, we fetch it from the network using `fetch(event.request)`.

This strategy is often called Cache Falling Back to Network. The idea is to return cached content when available, otherwise fall back to a network request. 

This can ensure speed if the content is cached and availability if it's not.

### Great opportunities

This is just the tip of the iceberg when it comes to service workers and their capabilities. 

You can set up more complex caching strategies, handle post requests, background syncs, and much more. Service workers are a powerful tool in modern web development, enabling app-like features in the browser.

## Powered by JavaScript

Now, let's create a simple audio player (`main.js`):

```javascript
class AudioPlayer {
  constructor(audioElement) {
    this.audioElement = document.querySelector(audioElement);
  }

  play() {
    this.audioElement.play();
  }

  pause() {
    this.audioElement.pause();
  }

  togglePlay() {
    this.audioElement.paused ? this.play() : this.pause();
  }
}

document.addEventListener('DOMContentLoaded', () => {
  const player = new AudioPlayer('#audioElement');

  document.querySelector('#playButton').addEventListener('click', () => {
    player.togglePlay();
  });
});
```

Let's dissect it and understand how we can harness the power of object-oriented programming in JavaScript to create interactive web components.

```javascript
class AudioPlayer {
  constructor(audioElement) {
    this.audioElement = document.querySelector(audioElement);
  }
```

At the start, we define a class named `AudioPlayer`. Classes are a blueprint for creating objects with specific methods and properties in JavaScript. Our `AudioPlayer` class takes a parameter `audioElement` in its constructor. This parameter should be a selector for the HTML audio element you want the player to control. It uses `document.querySelector` to get the first HTML element that matches this selector and assigns it to `this.audioElement`.

```javascript
  play() {
    this.audioElement.play();
  }

  pause() {
    this.audioElement.pause();
  }
```

We then define two methods `play` and `pause` within the class. These methods, when called, use the built-in `play` and `pause` methods on the `HTMLAudioElement` to control playback.

```javascript
  togglePlay() {
    this.audioElement.paused ? this.play() : this.pause();
  }
}
```

Next, we have the `togglePlay` method. This checks whether the audio is currently paused (the `paused` property is `true` when audio is not playing). If it is paused, it calls the `play` method, and if it's playing, it calls the `pause` method. It's a simple way to toggle between play and pause with a single function.

```javascript
document.addEventListener('DOMContentLoaded', () => {
  const player = new AudioPlayer('#audioElement');

  document.querySelector('#playButton').addEventListener('click', () => {
    player.togglePlay();
  });
});
```

Finally, outside the class, we have some code that uses our `AudioPlayer`. First, we wait for the `DOMContentLoaded` event to ensure that our HTML has fully loaded before we try to interact with it. 

Then, we create a new instance of our `AudioPlayer` class, passing the selector for our audio element.

We also add an event listener to the play button (`#playButton`). When the button is clicked, the `togglePlay` method is invoked on our audio player instance. 

This enables us to start or pause the audio playback by clicking the button.

### Good practices

This example is a perfect illustration of how we can create reusable, organized code in JavaScript using classes. The `AudioPlayer` class abstracts away the details of how to play, pause, and toggle audio, providing an easy-to-use interface that we can leverage across our application. 

It allows us to make our code more modular, maintainable, and manageable.

**Important note**

Please note that service workers and the Cache API work only over HTTPS or localhost for security reasons. If you're developing locally, it should work, but once you're ready to deploy, you'll need to make sure that your server uses HTTPS.

## HTML loads all

Here's a basic `index.html` file that works with the JavaScript code you provided:

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PWA MP3 Player</title>
    <link rel="manifest" href="manifest.json">
    <style>
        /* Add your CSS styles here */
    </style>
</head>
<body>
    <div class="player">
        <audio id="audioElement" src="song.mp3" controls></audio>
        <button id="playButton">Play/Pause</button>
    </div>

    <script src="main.js"></script>
    <script>
        if ('serviceWorker' in navigator) {
            window.addEventListener('load', function() {
                navigator.serviceWorker.register('/sw.js').then(function(registration) {
                    console.log('ServiceWorker registration successful with scope: ', registration.scope);
                }, function(err) {
                    console.log('ServiceWorker registration failed: ', err);
                });
            });
        }
    </script>
</body>
</html>
```

In this HTML file:

- We have an `<audio>` tag with `id="audioElement"`. This is the audio player that the `AudioPlayer` class will control. The `src` attribute should be the path to your mp3 file.

- We have a `<button>` with `id="playButton"`. When this button is clicked, it will call the `togglePlay` method on the `AudioPlayer` instance, starting or pausing the audio.

- We're including the `main.js` file that contains the `AudioPlayer` class.

- We also have a script to register our service worker (`sw.js`) if the browser supports it. The service worker will control network requests to help provide a seamless offline experience.

Thanks to the [malandriner community](https://www.webreactiva.com/comunidad) for always contributing value, and to this article that gave me the courage to create this post: Web Reactiva 27: [Convierte tu web en PWA (Progressive Web App)](https://www.webreactiva.com/podcast/web-en-pwa)

Happy coding!