www.damionmullins.com Damion Mullins, MS, MBA

How Tos, Explanations, & Other Stuff

A log of examples on how to do certain programming related activities



Read

Server-Sent Events (SSE)

Server-Sent Events (SSE) is an HTML5/JavaScript (native) API that allows an application to send data from the server without having to continaully request it. SSE is an alternative to polling and to the use of a WebSocket. SSE has low latency by using a single connection through an open stream. It is an efficient way to send data/messages, one way, from a server, and to handle the data/messages from the browser using JavaScript.

Below is an example of SSE in use. Press the "Start Events" button to start receiving messages from the server. Ten messages will be sent, and then SSE will close automatically. As you can see, there are no need for Ajax requests.

Start Events

JavaScript: In this example, I instantiate a new EventSource; add an event listener on the getMessage id of the messages sent from the server; and close SSE once the message says, "end". You'll also notice that the message content has to be parsed in JSON.

        
   document.addEventListener("DOMContentLoaded", function(){
          document.querySelector("#sseStartBtn").onclick = function(event) {
            serverSentEvent.start();
          };
    });

    var serverSentEvent = {
        start: function () {
          var sse = new EventSource("/serverSentEvents.php");
          sse.addEventListener("getMessages", function(event) {
              var msg = JSON.parse(event.data).message;
              if ("end" === msg) {
                sse.close();
              } else {
                  var testConst = document.querySelector('#msgCont');
                  testConst.innerHTML = testConst.innerHTML + " " + msg;
              }
          });
          
          sse.onerror = function(err) {
              console.error("SSE failure:", err);
              sse.close();
          };
        }
    };
        
    

PHP: In this example, I instantiate a class to process and send the messages. The most important part of SSE is in the message structure. Here I use a named event, "getMessages". This MUST be followed by a newline character. I then have my data. Each line must always be followed with a newline character.

        
   
    date_default_timezone_set("America/New_York");
    header("Cache-Control: no-cache");
    header("Content-Type: text/event-stream");

    /**
    * Send messages process
    */
    class ServerSentEvents
    {
        /** @var $data */
        protected $data;
        
        /**
        * ServerSentEvents constructor
        */
        public function __construct() {
            $this->data = [
                'Message one',
                'Message two',
                'Message three',
                'Message four',
                'Message five',
                'Message six',
                'Message seven',
                'Message eight',
                'Message nine',
                'Message ten',
                'end'
            ];
        }
        
        /**
        * Start the event process
        */
        final public function startEvent() 
        {
            $this->sendMessages(0);
        }
        
        /**
        * Create message
        * @param int $count
        */
        private function createMessage(int $count): string
        {
            return "event: getMessages\n" .
                   "data: {\"message\": \"" . $this->data[$count] . "\"}" .
                   "\n\n"; 
        }
        
        /**
        * Send the message
        * @param int $count
        */
        private function sendMessages(int $count)
        {
            while (true) {
                echo $this->createMessage($count);
                $count++;
                
                if ($count > 10 || connection_aborted()) {
                    break;
                }
                
                @ob_flush(); 
                @flush();
                sleep(1);
            }
        }
    }

    /**
    * Instantiate the class and start the process
    */
    (new ServerSentEvents())->startEvent();
        
    

Read

Promises in JavaScript (ES6)

ECMAScript 6 (ES6) was a major revision to JavaScript (JS). This revision introduced a feature called promises. Promises allow asynchronous methods to return values like synchronous methods. A good example of this is when calling an Ajax function and wanting to call another function right after. An Ajax call makes a server reguest asynchronously, (in the background or independent of other called functions) which allows the webpage to function as normal while the Ajax call is in progress. A JS promise simplifies the process and syntax of calling a function after an Ajax call gets completed successfully.

For reference and clarity, this is a very basic example of a JS promise

        
            // First, you have a function call another function using the "then" keyword, as well as handle any failure
            myFirstFunction().then(
                (data) => {
                    mySecondFunction(data);
                },                
                (failed) => {
                    handleFailure(failed);
                }
            );
            
            // Second, you have your function that requires a promise
            function myFirstFunction() {
                return new Promise((resolve, reject) => {
                    try {
                        // Resolve makes data available as the result or output.  In this example, I return the data.
                        resolve(data);
                    } catch (e) {
                        // If there is a failure, you would handle it here.  In this example, I return the failure.
                        reject(e);
                    }
                })
            }
            
            // Third, you have your second function which is the promise
            function mySecondFunction(data) {
                // do something with the data
            }
            
            // Fourth, this function handles any failures that may occur
            function handleFailure(failed) {
                // do something with the failure
            }
        
    

In this working example below, I instantiate a class called Ajax. This class contains all methods to make an Ajax call, get a file's content, and render the content on the screen. The first Ajax instantiation performs the task without a promise, while the second Ajax instantiation performs the same task with a JS promise. Without the promise, the output of makeAjaxCallWithoutPromise() is undefined, because it outputs the results before the server request is complete.

        
        document.addEventListener("DOMContentLoaded", function(){
            (new Ajax('/files/promise.txt', '#withoutPromise')).makeAjaxCallWithoutPromise(); //output is "undefined"
            (new Ajax('/files/promise.txt', '#withPromise')).makeAjaxCallWithPromise(); // output is "welcome to damionmullins.com"
        });
        
    

Below is the output of each function, one without a promise and one with a promise.

        
            
File content without ES6 promise:
File content with ES6 promise:

This is the Ajax class. I won't go into details on the OOP aspect of this example, but the process is as follows:
      Class instantiated
      Action called
      Ajax request made to get the contents of a file
      Results rendered in an HTML element
The difference between the two actions, "makeAjaxCallWithoutPromise()" and "makeAjaxCallWithPromise()", are that the method with a promise essentially waits for the Ajax request to finish. Once it's complete, the results are then sent to "renderText()" in order for the file's text to be rendered on screen. I've included comments within the class for a thorough step-by-step explanation of what is happening.

        
        class Ajax {
            // Object variables are set
            constructor(url, outputId) {
                this.url      = url; // url to the file
                this.outputId = outputId; // id of the element that will render the output
            }
            
            // Action to make an Ajax request without a JS promise and render the output
            makeAjaxCallWithoutPromise() {
                this.renderText(
                    this.getFileContents()
                );
            }
            
            /** Action to make an Ajax request with a JS promise and render the output.
            * The "then" keyword is used to call the next function and
            * the parameter is anything you want to pass to that function (resolve).
            * If there is a failure, then the second parameter (reject) is called.
            */
            makeAjaxCallWithPromise() { 
                // Make the Ajax call
                this.getFileContentsPromise().then(
                    (text) => {
                        // If successful, render the text
                        this.renderText(text);
                    }, 
                    (failedStatus) => { 
                        // If failed, output failed status in the console
                        console.log("Error: " + failedStatus);
                    }
                );
            }
            
            // Render the file content in an HTML element
            renderText(text) {
                let label = document.querySelector(this.outputId).innerHTML;
                document.querySelector(this.outputId).innerHTML = `${label} ${text}`;
            }
            
            // Ajax request with no promise
            getFileContents() {
                let Http = new XMLHttpRequest();
                Http.open("GET", this.url);
                Http.send();
                Http.onreadystatechange = (e) => {
                    return Http.responseText;
                    if (Http.readyState === 4){
                        if (Http.status === 200){               
                            return Http.responseText;
                        } else {
                            console.log("request failed");
                        }
                    }
                }
            } 
            
            /** Ajax request with a JS promise
            * A new promise is instantiated
            * The promise takes two arguements, which are resolve and reject
            * Resolve and reject are callable functions. Either one will run, but not
            * both.  Resolve is called for a successful Ajax request or function call.
            * This is where you would pass the results of your Ajax request to the function you want to call
            * immediately afterwards.  Reject is called upon a failure.
            * The difference between "getFileContents()" and "getFileContentsPromise()" is that getFileContentsPromise
            * wraps the promise around the Ajax call, and includes the resolve and reject functions.
            */
            getFileContentsPromise() {
                return new Promise((resolve, reject) => {
                    let Http = new XMLHttpRequest();
                    Http.open("GET", this.url);
                    Http.send();
                    Http.onreadystatechange = (e) => {
                        if (Http.readyState === 4){
                             if (Http.status === 200){               
                                resolve(Http.responseText);
                             } else {
                                reject(Http.status);
                             }
                          }
                    }
                });
            } 
        }
        
    

Read

Writing JavaScript Using OOP Concepts in ES6

ECMAScript 6 (ES6) was a relatively recent and major revision of JavaScript (JS). ES6 helped make JS more Object Oriented. Below is a simple working example of OOP concepts in JS.

The below list of constants will be the data used for this example. Since constants cannot be changed, they are usually written in snake-case with all capitals.

        
        /**
        * Data & storage for this example
        */
        const EXAMPLE_USER_ID_1 = 1234;
        
        const NEW_DATA          = {
            firstName: "Bob",
            lastName: "Smith",
            email: "bob.smith@email.com",
            phone: "18004365555"
        };
        
        const BAD_NEW_DATA      = {
            firstName: "Bill",
            lastName: "",
            email: "bill@.com",
            phone: "18004365555"
        };
        
        const EDITED_DATA       = {
            id: 9876,
            firstName: "Mary",
            lastName: "Thomas",
            email: "mary.thomas@email.com",
            phone: "18004345555"
        };
        
        const VIEW_DATA        = {
            id: 9999,
            firstName: "Lisa",
            lastName: "Stevens",
            email: "lisa.Stevens@email.com",
            phone: "18002325555"
        };
        
    

In this example, the Users class contains methods to perform CRUD (i.e. create, read, update, and delete) actions, similar to a database-driven web application. Each CRUD action in the class is called once the class has been instantiated. The general worklflow for each action is as follows:
  1. Users instantiated
  2. Action called
  3. Component called
  4. Data validated
  5. Data processed
  6. Response returned
The action then returns a payload that includes a boolean value for successful or unsuccessful, a message, and output, if applicable. Each action takes a parameter which is either an id and/or a constant. In a real-world example, this would be equivalent to a server request passing in parameters.

If you are using a modern browser (i.e. no more IE shenanigans), you can also see the below console.log outputs in your own browser's inspect > console, since this is a working and tested example.

        
        /**
        * Class Instantiation
        */
        let users = new Users();
        
        console.log(users.add(NEW_DATA)); //output [true, "Saved", ""]
        
        console.log(users.add(BAD_NEW_DATA)); //output [false, "Email format is incorrect", ""]
        
        console.log(users.edit(9876, EDITED_DATA)); //output [true, "Edited", ""]
        
        console.log(users.edit('badId', EDITED_DATA)); //output [false, "System error", ""]
        
        console.log(users.delete(9876)); //output [true, "Deleted", ""]
        
        console.log(users.delete('badId')); //output [false, "System error", ""]
        
        console.log(users.view(9999)); //output [true, "Success", {email: "lisa.Stevens@email.com", firstName: "Lisa", id: 9999, lastName: "Stevens", phone: "18002325555"}]

        console.log((new Users()).view('badId')); //output [false, "System error", ""]
        
    

The Values class holds the successful and failed boolean variables that are used in conditionals throughout this example. Typically, you would want to make these constants, since they will never change; however, this is an example of static variables. Static variables in a class do not require class instantiation and they can be accessed from any other class.

        
        /**
        * Static values
        */
        class Values {
            static failed     = false;
            static successful = true;
        }
        
    

The Base class is a parent class, because it should always be called when the Users class is instantiated. In this example, the Base class is used to get the user's ID. In a real-world example, the Base class would be used to get and make readily-available internal data, such as the user's information, permissions, etc...

        
        /**
        * This class should get internal data, permissions, etc...
        * For example, Base class gets the user's id
        * This class is extended because it should always be called 
        */
        class Base {
            /**
            * All constructors do not need to be explicitly called, 
            * instead they automatically initialize.
            */
            constructor() {
                // Here the class that gets the user's id from storage
                this.userId = EXAMPLE_USER_ID_1;
            }
        }
        
    

The Users class is a child class of the Base class. You can see this relationship by the "extends" keyword. Users acts as a controller class that directs the process workflow to the correct component. If there are any errors within the process workflow of each action, the error will get caught and thrown in the action.

        
        /**
        * Users contains all methods to manage users
        */
        class Users extends Base {
            /**
            * Public method
            */
            add(data) {
                try {
                    return (new AddComponent(this.userId)).addAction(data);
                } catch (error) {
                    throw new Error(error);
                }
            }
            
            /**
            * Public method
            */
            edit(id, data) {
                try {
                    return (new EditComponent(this.userId)).editAction(id, data);
                } catch (error) {
                    throw new Error(error);
                }
            }
            
            /**
            * Public method
            */
            delete(id) {
                try {
                    return (new DeleteComponent(this.userId)).deleteAction(id);
                } catch (error) {
                    throw new Error(error);
                }
            }
            
            /**
            * Public method
            */
            view(id) {
                try {
                    return (new ViewComponent(this.userId)).viewAction(id);
                } catch (error) {
                    throw new Error(error);
                }
            }
        }
        
    

The DataComponent class performs the actual data queries and computations. It is also a parent class to each of the component classes.

        
        class DataComponent {
            /**
            * Protected method
            */
            saveData(data) {
                let success = Values.failed;
                
                /* Some type of save into storage
                * would happen here.  For this example,
                * I'll just use a true statement.
                */
                if (1 === 1) {
                    success = Values.successful;
                }
       
                return success;
            }
            
            /**
            * Protected method
            */
            editData(id, data) {
                let success = Values.failed;
                
                /* Some type of update record in storage
                * would happen here.  For this example,
                * I'll just use a true statement.
                */
                if (1 === 1) {
                    success = Values.successful;
                }
                
                return success;
            }
            
            /**
            * Protected method
            */
            deleteData(id) {
                let success = Values.failed;
                
                /* Some type of delete record in storage
                * would happen here.  For this example,
                * I'll just use a true statement.
                */
                if (1 === 1 && !isNaN(id)) {
                    success = Values.successful;
                }
                
                return success;
            }
            
            /**
            * Protected method
            */
            viewData(id) {
                let success = Values.failed;
                let data    = {};
                
                /* Some type of record to view in storage
                * would happen here.  For this example,
                * I'll just use a true statement.
                */
                if (1 === 1 && !isNaN(id)) {
                    success = Values.successful;
                    data    = VIEW_DATA;
                }
                
                return [
                    success,
                    data
                ];
            }
        }
        
    

The AddComponent class holds and calls all methods related to adding a record. This class is a child class to the DataComponent. The "super" keyword in the constructor allows the AddComponent access to the Base class object. This allows the AddComponent class to gain access to the user id. In a real-world example, methods that manipulate or create records should know who (the user) is making the queries.

        
        class AddComponent extends DataComponent {
            constructor(userId) {
                // Call the parent constructor to get the object
                super();
                this.userId = userId;
            }
            
            /**
            * Public method
            */
            addAction(data) {
                let success          = Values.failed;
                let [valid, message] = (new ValidationComponent()).validateAdd(data);
                
                if (Values.successful === valid) {
                    success = this.saveData(data);
                    if (Values.successful === success) {
                        message = "Saved";
                    }
                } 
                
                return [
                    success,
                    message,
                    ""
                ];
            }
        }
        
    

The EditComponent class holds and calls all methods related to editing a record. This class is a child class to the DataComponent. The "super" keyword in the constructor allows the EditComponent access to the Base class object. This allows the EditComponent class to gain access to the user id. In a real-world example, methods that manipulate or create records should know who (the user) is making the queries.

        
        class EditComponent extends DataComponent {
            constructor(userId) {
                // Call the parent constructor to get the object
                super();
                this.userId = userId;
            }
            
            /**
            * Public method
            */
            editAction(id, data) {
                let success          = Values.failed;
                let [valid, message] = (new ValidationComponent()).validateEdit(id, data);
                
                if (Values.successful === valid) {
                    success = this.editData(id, data);
                    if (Values.successful === success) {
                        message = "Edited";
                    }
                } 
                
                return [
                    success,
                    message,
                    ""
                ];
            }
        }
        
    

The DeleteComponent class holds and calls all methods related to deleting a record. This class is a child class to the DataComponent. The "super" keyword in the constructor allows the DeleteComponent access to the Base class object. This allows the DeleteComponent class to gain access to the user id. In a real-world example, methods that manipulate or create records should know who (the user) is making the queries.

        
        class DeleteComponent extends DataComponent {
            constructor(userId) {
                // Call the parent constructor to get the object
                super();
                this.userId = userId;
            }
            
            /**
            * Public method
            */
            deleteAction(id) {
                let message = "System error"; 
                let success = this.deleteData(id);
                
                if (Values.successful === success) {
                    message = "Deleted";
                }
                
                return [
                    success,
                    message,
                    ""
                ];
            }
        }
        
    

The ViewComponent class holds and calls all methods related to viewing a record. This class is a child class to the DataComponent. The "super" keyword in the constructor allows the ViewComponent access to the Base class object. This allows the ViewComponent class to gain access to the user id. In a real-world example, methods that access records should know who (the user) is making the queries.

        
        class ViewComponent extends DataComponent {
            constructor(userId) {
                // Call the parent constructor to get the object
                super();
                this.userId = userId;
            }
            
            /**
            * Public method
            */
            viewAction(id) {
                let message         = "System error"; 
                let [success, data] = this.viewData(id);
                
                if (Values.successful === success) {
                    message = "Success";
                }
                
                return [
                    success,
                    message,
                    data
                ];
            }
        }
        
    

The ValidationComponent class validates all data before it gets saved, deleted, edited, etc... In a real-world example, valdiation should be done on both the client and server. Client-side validation helps to assure data is ready to be sent to the server, without inundating the server with many requests. Server-side validation assures that the data is valid and protects the server from fraudulent or dirty data, since client-side validations can easily be circumvented.

        
        class ValidationComponent {
            constructor() {
                this.valid   = Values.successful;
                this.message = "";
            }
            
            /**
            * Public method
            */
            validateAdd(data) {
                this.firstName(data.firstName);
                this.lastName(data.lastName);
                this.email(data.email);
                this.phone(data.phone);
                
                return [
                    this.valid,
                    this.message
                ];
            }
            
            /**
            * Public method
            */
            validateEdit(id, data) {
                this.id(id, data.id);
                this.firstName(data.firstName);
                this.lastName(data.lastName);
                this.email(data.email);
                this.phone(data.phone);
                
                return [
                    this.valid,
                    this.message
                ];
            }
            
            /**
            * Protected method
            */
            id(id, value) {
                this.validateId("System error", value, id);
            }
            
            /**
            * Protected method
            */
            firstName(value) {
                this.validateEmpty("First name", value);
            }
            
            /**
            * Protected method
            */
            lastName(value) {
                this.validateEmpty("Last name", value);
            }
            
            /**
            * Protected method
            */
            email(value) {
                this.validateEmpty("Email", value);
                this.validateEmailStructure(value);
            }
            
            /**
            * Protected method
            */
            phone(value) {
                this.validatePhoneStructure(value);
            }
            
            /**
            * Private method
            */
            validateId(type, value, id) {
                if (Number(id) !== Number(value)) {
                    this.valid   = Values.failed;
                    this.message = type;
                }
            }
            
            /**
            * Private method
            */
            validateEmpty(type, value) {
                if ("" === value) {
                    this.valid   = Values.failed;
                    this.message = type + ' is empty';
                }
            }
            
            /**
            * Private method
            */
            validateEmailStructure(value) {
                if (Values.failed === /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(value)) {
                    this.valid   = Values.failed;
                    this.message = 'Email format is incorrect';
                }
            }
            
            /**
            * Private method
            */
            validatePhoneStructure(value) {
                if (Values.failed === /^\d{10}$/) {
                    this.valid   = Values.failed;
                    this.message = 'Phone number format is incorrect';
                }
            }
        }
        
    

Read

Using JavaScript Array Map (and all of its parameters)

There are many ways to iterate over arrays and objects in JavaScript. Loops are typically fast and syntactically clear. However there are more robust options. Below is an example of using array map to iterate over an array and even an objects.

The array map method allows you to map an array. You can create a new array, as well as change the mapped array. Most examples on the web do not really go into details on every parameter that array map can take and how they can be used. Here is example of array map with every parameter.

        
            /**
            * basic structure for clarity
            */
            let newArray = myArray.map(function (value, index, myArray)  {
                // do stuff here
                // and/or return stuff inside another array
                return ...
            }, elem);
        
    
        
            // Array to map
            let myArray = [
                'Sunday',
                'Monday',
                'Tuesday',
                'Wednesday',
                'Thursday',
                'Friday',
                'Saturday'
            ];
            // Container element for output #1
            let elem = document.querySelector('#outputElement');
            /** 
            * Return array after mapping.
            * value is each value (day of the week) in myArray
            * index is the key (0 thru nth) for each value in the array
            * myArray is the array that you are mapping
            * elem is any element you would like to have "this" access to
            */
            let newArray       = myArray.map(function (value, index, myArray)  {
                let str        = `${index} ${value}`; // create a string with index and value
                let output     = this.innerHTML; // get the content in the "elem" element
                this.innerHTML = `${output} ${str}`; // update elem with new content
                myArray[index] = `${value} is index ${index}`; // update the value in the mapped array with a new value
                
                return value; // return each value in the new array
            }, elem);
            
            // Populate each container output
            document.querySelector('#newArrayOutput').innerHTML = `2) ${newArray}`;
            // Populate each container output
            document.querySelector('#myArrayOutput').innerHTML  = `3) ${myArray}`; 
        
    // Each index and value in myArray (i.e. mapped array)
    
1) The days of the week are
// Each value in newArray (i.e. the new array)
// Each changed value now in myArray (i.e. mapped array)

In combination with JavaScript's (ES6) Object class methods, the array map method can also be used to iterate over objects. However, you won't be able to modify the mapped object, instead you create a new object. There are better ways to iterate over an object, but in the below example I wanted to show how you would create a new object with the keys and values from the mapped object and detail how to use all of the parameters array map can take. Additionally, for clarity, I wanted to show how the Object class methods and array map can be written in regular syntax as opposed to using the => syntax.

        
            /**
            * basic structure for clarity
            */
            let myNewObject = Object.fromEntries(
              Object.entries(myObject)
              .map(function ([key, value], index, myObject) { 
                // do stuff here
                // and/or return stuff inside another object
                return ...
              }, this)
            );
        
    
        
            class Mapping {
                objectMap() {
                    // Object to map
                    let myObject = {
                        'Day 1': 'Monday',
                        'Day 2': 'Tuesday',
                        'Day 3': 'Wednesday',
                        'Day 4': 'Thursday',
                        'Day 5': 'Friday',
                        'Day 6': 'Saturday',
                        'Day 7': 'Sunday'      
                    };
                    
                    /** 
                    * Return object after mapping.
                    * [key, value] is the key value pair in the object (i.e. key: value)
                    * index is a key for each key-value pair starting with 0
                    * myObject is the object that you are mapping
                    * "this" is the instantiated class object (i.e. to have access to all methods in the class)
                    */
                    let myNewObject = Object.fromEntries( // fromEntries changes array into object
                      Object.entries(myObject) // entries returns an array of object's key and value pair
                      .map(function ([key, value], index, myObject) { 
                        return this.doSomethingToMyObject(key, value, index, myObject) 
                      }, this)
                    );
                    // Render new object on screen
                    document.querySelector('#newObjectOutputB').innerHTML = `2) ${JSON.stringify(myNewObject)}`;
                }
                
                /**
                * This method creates the new key and values for the new object
                * and renders the key and values of the mapped object
                */
                doSomethingToMyObject(key, value, index, myObject) {
                    let nextDay          = (typeof myObject[index + 1] !== "undefined") ? myObject[index + 1][1] : myObject[0][1];
                    let str              = `
${key} is ${value} and the next day is ${nextDay}`; let outputCont = document.querySelector('#outputElementB'); let output = outputCont.innerHTML; outputCont.innerHTML = `${output} ${str}`; return [index + 1, value]; } } // Each index and value of the mapped object (myObject) rendered in a div element
1) The days of the week are as follows:
// Each modified key and value in the new object (myNewObject)

Read

JavaScript Service Workers

The below code is an example of using a JavaScript service worker to make a website work offline. There are many resources on the web that explain how service workers work. However, here are some specific notes to remember when using a service worker:
  • Requires HTTPS, unless you're testing in a local environment
  • Requires a browser that supports ES6 (modern browser)

The basic setup for a service worker is as follows:
  1. Place your service worker JavaScript (JS) file in your Public folder or equivalent in your website directory (i.e. public/sw.js
  2. Initialize your service worker from any JS page on your website. The following worklfow will occur:
    1. Check if supported
    2. Register your service worker (i.e. sw.js file)
    3. Install the pages you want cached
    4. Fetch the cache
    5. Update the service worker
    6. Delete old cache, and activate new cache

        
// Initialize service worker from any JS page in your website
function initialize() {
    if ('serviceWorker' in navigator) { 
        navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(function(registration) {
            registration.update();
        }, function(error) {
            console.log('Service worker registration failed:', error);
        });
    } else {
        console.log('Service workers are not supported.');
    }
};
        
    
The below code is the content of the sw.js file
        
             
        
    
More Projects!