WDD 131: Dynamic Web Fundamentals

Web Storage API - localStorage

Overview

There are times when non-sensitive data storage would provide a better user experience and offer some site performance improvements. The Web Storage API mechanisms provide the ability to store information in a key-value format based upon a particular user agent or origin. In this activity, we discover the purpose and scope of the localStorage mechanism available through the Storage interface. We will use this knowledge to drive and display some traffic data from individual clients on a page.

The web storage data is actually saved in a SQLite file in a subfolder or file in the user's profile folder.

Prepare

Here are some comparison data between cookies, localStorage, and sessionStorage.

Table 1: Local Storage Methods Comparison Chart
Cookies localStorage sessionStorage
Capacity ~4KB ~5MB ~5MB
Expires Manually set Never On tab/windows close
Accessible from Any window Any Window Same tab

Each of these methods stores data with the actual user agent (browser) client and not on the server.

localStorage Advantages

You can store non-string data in localStorage. One common approach is to use the JSON.stringify() method to convert the data to a string before storing it, and then use the JSON.parse() method to convert it back to its original form when you retrieve it from localStorage. This allows you to store and retrieve more complex data structures, such as objects and arrays, in localStorage.

localStorage Demonstration

What are some of the differences between session and local storage?

One main difference is that localStorage data is persistent (remains) even when the browser session expires. Session variables do not.

Using the Storage interface, what are some methods built into this interface object and what do they do?

Out of the entire method list [key(), getItem(), setItem(), removeItem(), clear()] the most common are getItem() and setItem() which you will be using in the assignments.
Storage Interface of the Web Storage API

Activity Overview

This activity revisits the favorite Book of Mormon chapters exercise. The chapters entered by the user do not persist between visits to the application. In other words, they are not stored anywhere. In this activity, we will use localStorage to store the BOM chapter list for the user.

The high level directions for this problem would be "Enhance the favorite Book of Mormon chapters application so that the users entries persist between visits to the application using the localStorage API". Take on the challenge by first thinking about a solution to this problem and map it out on paper.

The directed plan is to create an array of valid book and chapter entries made by the user. Then that array could be stored in localStorage as one large string using JSON string using JSON.stringify(). This means we need to load that array upon application load with the parsed data from localStorage, IF the named localStorage component exists. Once loaded, we need to populate the list with the stored values.

So instead of having two functions that do the same thing for the most part, we will create a single function that appends the favorite chapter list with the corresponding delete button, once on load, and also when a new entry is made.

Activity Instructions

Note that the given example code is just an example. You may have used different variable names, etc. You should never just copy code.
  1. Make a copy of your BOM application, by copying the HTML, CSS, and JavaScript from the previous learning activity into a week05 folder.
  2. Open the JavaScript file. Declare an array named chaptersArray and assign it to the results of a defined function named getChapterList (This function does not exist, yet).
  3. In that same array variable declaration and assignment, add a compound OR condition to assign it an empty array in case this is the user's first visit or if the localStorage item is missing.
    This works because the function might not return anything, so it is falsy which means it will revert to assigning the empty array to chaptersArray.
    Example
            const input = document.querySelector('#favchap');
    const button = document.querySelector('button');
    const list = document.querySelector('#list');
    
    let chaptersArray = getChapterList() || [];
    Code Explanations

    The first three lines establish references to the DOM elements that we will be using in the program. Note that they only reference the HTML element objects, not any properties.

    The array declaration initializes the chaptersArray variable with the list of chapters returned by the getChapterList() function or an empty array if the function call returns null or undefined.

  4. Now let's populate the displayed list of chapters. Use a forEach on the chaptersArray to process each entry named chapter. Use an arrow function within the loop to call a new defined function named displayList and pass it the argument of chapter. That way each entry will be processed, i.e., appended to the list.
    Example
                  chaptersArray.forEach(chapter => {
      displayList(chapter);
    });
  5. Change the button click event listener to only do the following tasks (the other tasks in that original function will be used in a separate function named displayList):
    1. check if the input is empty, if not, then
    2. call displayList with the input.value argument,
    3. push the input.value into the chaptersArray,
    4. update the localStorage with the new array by calling a function named setChapterList,
    5. set the input.value to nothing, and
    6. set the focus back to the input.
    Example with // code comments
            button.addEventListener('click', () => {
      if (input.value != '') {  // make sure the input is not empty
        displayList(input.value); // call the function that outputs the submitted chapter
        chaptersArray.push(input.value);  // add the chapter to the array
        setChapterList(); // update the localStorage with the new array
        input.value = ''; // clear the input
        input.focus(); // set the focus back to the input
      }
    });
  6. Create the displayList defined function that receives one parameter named item.
  7. Put all the code that builds a list item from the previous button click event listener into this new displayList function and use the item parameter as the input. A deleteChapter function will need to be called within the delete button click event to remove the chapter from the array and localStorage.
    Example: displayList()
            function displayList(item) {
      let li = document.createElement('li');
      let deletebutton = document.createElement('button');
      li.textContent = item; // note the use of the displayList parameter 'item'
      deletebutton.textContent = '❌';
      deletebutton.classList.add('delete'); // this references the CSS rule .delete{width:fit-content;} to size the delete button
      li.append(deletebutton);
      list.append(li);
      deletebutton.addEventListener('click', function () {
        list.removeChild(li);
        deleteChapter(li.textContent); // note this new function that is needed to remove the chapter from the array and localStorage.
        input.focus(); // set the focus back to the input
      });
      console.log('I like to copy code instead of typing it out myself and trying to understand it.');
    }
  8. Define the setChapterList function to set the localStorage item that you have already named. Use JSON.stringify() to stringify the array.
    Example: setChapterList()
                  function setChapterList() {
      localStorage.setItem('myFavBOMList', JSON.stringify(chaptersArray));
    }
  9. Define the getChapterList function to get the localStorage item. No parameter is needed. Since this function returns to an awaiting array, we will need to use JSON.parse on the string.
    Example: getChapterList()
    function getChapterList() {
      return JSON.parse(localStorage.getItem('myFavBOMList'));
    }
  10. Finally, define the deleteChapter function with a parameter named chapter that does three things.
    1. reformat the chapter parameter to get rid of the ❌ that is passed on the end of the chapter string when we called the deleteChapter function. Use string.slice() method to extract the last character.
      chapter = chapter.slice(0, chapter.length - 1); // this slices off the last character
    2. redefine the chaptersArray array using the array.filter method to return everything except the chapter to be removed.
      chaptersArray = chaptersArray.filter((item) => item !== chapter);
    3. Call the setChapterList function to update the localStorage item.
    Example: deleteChapter()
    function deleteChapter(chapter) {
      chapter = chapter.slice(0, chapter.length - 1);
      chaptersArray = chaptersArray.filter(item => item !== chapter);
      setChapterList();
    }

Testing

Test the application by adding and removing chapters. Hard refresh and empty the cache the page and see if the chapters persist. You can also erase all application localStorage content under the Applications tab in DevTools.

Optional Resources

  1. Video: JavaScript Cookies vs localStorage vs Session Storage - Web Dev Simplified
  2. A dynamic structured approach to this activity would be to use Set object in JavaScript. Set allows us to add and delete items from the Set object as needed.
  3. Reference: Web Storage API - MDN
  4. The IndexedDB API is a more robust method of storing data on the client side and is more like a database.
  5. The Cache API is a method of storing data that is specifically used for caching data for offline use.