The Web Storage API (localStorage, sessionStorage)

The Web Storage API (localStorage, sessionStorage)

INTRODUCTION

The Web storage API (see the related W3C specification) introduces “two related mechanisms, similar to HTTP session cookies, for storing structured data on the client side”.

Indeed, Web Storage provides two interfaces – sessionStorage and localStorage – whose main difference is data longevity. This specification defines an API for persistent data storage of key-value pair data in Web clients.

With localStorage the data will remain until it is deleted, whereas with sessionStoragethe data is erased when the tab/browser is closed

SIMPLE KEY-VALUE STORES, ONE PER DOMAIN (FOLLOWING THE SAME ORIGIN POLICY)!

localStorage is a simple key-value store, in which the keys and values are strings. There is only one store per domain. This functionality is exposed through the globally available localStorage object. The same applies to sessionStorage.

Example:

  1. // Using localStorage
  2. // store data
  3. localStorage.lastName = “Bunny”;
  4. localStorage.firstName = “Bugs”;
  5. localStorage.location = “Earth”;
  6. // retrieve data
  7. var lastName = localStorage.lastName;
  8. var firstName = localStorage.firstName;
  9. var location = localStorage.location;

This data is located in a store attached to the origin of the page. We created a JS Bin example in which we included the above code.

Once opened in your browser, the JavaScript code is executed. With the browser dev. tools, we can check what has been stored in the localStorage for this domain:

DIFFERENCES WITH COOKIES?

Cookies are also a popular way to store key-value pairs. Web Storage, however, is a more powerful technique than cookies. The main difference is in size limits: cookies are limited to a few KBytes whereas Web Storage may extend to several MBytes. Also cookies generate additional HTTP request traffic (whether to request a Web page, an image, a stylesheet, a JavaScript file, etc.).

Objects managed by Web Storage are no longer carried on the network and HTTP, and are easily accessible (read, change and delete) from JavaScript, using the Web Storage API.

EXTERNAL RESOURCES

Example 1: a form that never loses what you entered, even if you reload the page, or press “backspace” by mistake

You can start filling this form and come back another day and complete it. It doesn’t matter if you closed your browser before coming back.

In this example, we use the most simple way to use localStorage:

    • Save with the localStorage.key = value syntax. For example, localStorage.firstName = ‘Michel’ will save the value “Michel” with the access key being ‘firstName’
    • Restore with the var value = localStorage.key syntax. For example, var fn = localStorage.firstName; will set fn with the value ‘Michel’ if this value has been previously saved as in the example from the line above.

SAVING THE FORM CONTENT ON THE FLY

Open this online example at JS Bin, and use F12 or cmd-alt-i (Mac OS) to look at the dev. tools. As you type in the different input fields, their content is updated in the localStorage.

We just added input event listeners to each input field. For example, in order to save the first name input field’s content, we just added:

  1. oninput=“localStorage.firstName=this.value;”

Where firstName in red is the key and this.value the current value of the input field.

In the same way, we added an input listener to all the input fields in this example’s form.

RESTORING THE FORM CONTENT ON PAGE LOAD/RELOAD

This time, we want the form content to be restored on page load/reload. We will add a restoreFormContent() function in the JavaScript code that will be called each time the page is loaded. In this function, we will read the saved data and set the input fields’ values.

Complete example on JS Bin: enter data and press reload at any time. The form content is restored!

Source code extract (only addition to the previous example):

  1. // Called when the page is loaded
  2. window.onload = restoreFormContent;
  3.  
  4. function restoreFormContent() {
  5.    console.log(“restoring form content from localStorage”);
  6.    if(localStorage.firstName !== undefined)
  7.      document.getElementById(“firstName”).value = localStorage.firstName;
  8.    if(localStorage.lastName !== undefined)
  9.      document.getElementById(“lastName”).value = localStorage.lastName;
  10.    if(localStorage.email !== undefined)
  11.      document.getElementById(“email”).value = localStorage.email;
  12.  
  13.    if(localStorage.age !== undefined)
  14.      document.getElementById(“age”).value = localStorage.age;
  15.    if(localStorage.date !== undefined)
  16.      document.getElementById(“date”).value = localStorage.date;
  17. }

The tests at lines 7, 10, 13, etc., verify that data has been saved, before trying to restore it. Without these tests, it would put the “undefined” string as the value of input fields with no corresponding data to restore.

 

More methods from localStorage/sessionStorage

This time we will look at another example that uses new methods from the API:

    • localStorage.setItem(…),
    • localStorage.getItem(…),
    • localStorage.removeItem(…),
    • localStorage.clear().

GETTING/SETTING VALUES USING THE GETITEM(KEY) AND SETITEM(KEY, VALUE)METHODS

If you want to keep a simple counter of the number of times a given user has loaded your application, you can use the following code (just to show how to use setItem/removeItem methods):

  1. var counter = localStorage.getItem(“count”) || 0;
  2. counter++;
  3. localStorage.setItem(“count”, counter);

As you can easily guess from the above, we use var value = getItem(key) to retrieve a key’s value and setItem(key, value) to set it. This is similar to what we saw in the examples of the page above, except that this time:

    • The key can contain spaces, for example we can write: localStorage.setItem(“Instructor’s name”, “Michel”); and var name =  localStorage.getItem(“Instructor’s name”);, whilevar name = localStorage.Instructor’s name; will not work!
    • In a loop or in an iterator, sometimes we need to set/get localStorage values using this syntax, for example:
  1. var inputField = document.getElementById(“firstName”);
  2. saveInputFieldValue(inputField);
  3. function saveInputFieldValue(field) {
  4.     localStorage.setItem(field.id, field.value);
  5. }

DELETING A KEY WITH REMOVEITEM(KEY), OR ALL KEYS WITH CLEAR()

Deleting a key can be performed through removeItem(). And if you wish to reset the entire store, simply call localStorage.clear().

Note that it will probably only be the rare occasion that you will want the entire store to be cleared by the user in production software (since that effectively deletes their entire data). However, it is a rather a common operation needed during development, since bugs may store faulty data the persistence of which can break your application, since the way you store data may evolve over time, or simply because you also need to test the experience of the user when first using the application.

One way of handling this is to add a user interface button that calls clear() when clicked, but you must then remember to remove it when you ship! The recommended approach  to use (whenever possible) is to simply open the dev. tool’s console and type localStorage.clear() there — it’s safer and works just as well.

ITERATING LOCAL STORES

Local stores (localStorage or sessionStorage) can also be iterated through in order to list all the content that they contain. The order is not guaranteed, but this may be useful at times (if only for debugging purposes!). The following code lists everything in the current store:

  1. for (var i = 0, n = localStorage.length; i < n; i++) {
  2.     var k = localStorage.key(i);
  3.     console.log(k + “: “ + localStorage[k]); // get the ith value, the one with a key that is in the variable k.
  4. }

Observant students will note that something seems a bit off in the example above: instead of calling localStorage.getItem(k), we simply access localStorage[k]. Why? Because keys in the local store can also be accessed as if the store were a simple JavaScript object. So instead of localStorage.getItem(“foo”) and localStorage.setItem(“foo”, “bar”), one can write localStorage.foo and localStorage.foo = “bar”. Of course there are limitations to this mapping: any string can serve as a key, so that localStorage.getItem(“one two three”) works, whereas that string would not be a valid identifier after the dot (but it could still work as localStorage[“one two three”]).

EXAMPLE THAT SHOWS ALL THE METHODS OF THE LOCAL STORAGE API IN ACTION

Online example at JS Bin, run it, then click on the first button to show all key/values in the localStorage. Open the URL in another tab, and see that the data is shared between tabs, as local stores are attached to an origin.

Then click on the second button to add data to the store, click on the third to remove data. Finally, the last one clears the whole data store.

Source code:

  1. <!DOCTYPE html>
  2. <html lang=“en”>
  3. <head>
  4. <meta charset=utf-8 />
  5. <title>Example of localStorare API use</title>
  6.    // Using localStorage
  7.    var counter = localStorage.getItem(“count”) || 0;
  8.    counter++;
  9.    localStorage.setItem(“count”, counter);
  10.    function getCountValue() {
  11.       // retrieve data
  12.       document.querySelector(“#counter”).innerHTML = localStorage.count;
  13.    }
  14.    function seeAllKeyValuePairsStored() {
  15.       // clear list first
  16.       document.querySelector(‘#list’).innerHTML=“”;
  17.       for (var i = 0, n = localStorage.length; i n; i++) {
  18.          var key = localStorage.key(i);
  19.          var value = localStorage[key];
  20.          console.log(key + “: “ + value);
  21.          var li = document.createElement(‘li’);
  22.          li.innerHTML = key + “: “ + value;
  23.          document.querySelector(‘#list’).insertBefore(li, null);
  24.       }
  25.    }
  26.    function resetStore() {
  27.         // erase all key values from store
  28.         localStorage.clear();
  29.         // reset displayed list too
  30.        document.querySelector(‘#list’).innerHTML=“”;
  31.    }
  32.    function addSomeData() {
  33.       // store data
  34.       localStorage.lastName = “Buffa”;
  35.       localStorage.firstName = “Michel”;
  36.       // refresh display
  37.       seeAllKeyValuePairsStored();
  38.    }
  39.    function removeSomeData() {
  40.       // store data
  41.       localStorage.removeItem(“lastName”);
  42.       localStorage.removeItem(“firstName”);
  43.       // refresh display
  44.       seeAllKeyValuePairsStored();
  45.    }
  46. </head>
  47. <body onload=getCountValue()>
  48.    <h1>Number of times this page has been seen on this browser: <spanid=“counter”></span></h1>
  49.    <button onclick=seeAllKeyValuePairsStored()>Show all key value pairs stored in localStorage</button><br/>
  50.    <output id=“list”></output>
  51.  
  52.    <button onclick=addSomeData()>Add some data to the store</button><br/>
  53.    <button onclick=removeSomeData()>Remove some data</button><br/>
  54.    <button onclick=resetStore()>reset store (erase all key/value pairs)</button>
  55. </body>
  56. </html>

You can check in the Chrome dev. tools user interface that the content of the localStorage changes as you click on the buttons.

 

Practical example 2: save/restore user’s preferences

INTRODUCTION

Local stores are also useful for saving/restoring user preferences of Web Applications. For example, the JS Bin tool you have been using since the beginning of this course uses localStorage to store the list of tabs you open, and their width:

In this way, the next time you come back to JS Bin, “it will remember your last settings”.

Another example is a guitar FX processor / amp simulator your instructor is writing with some of his students. It uses localStorage to save/restore presets values:

 

PRACTICAL EXAMPLE: SAVE/RESTORE PREFERENCES OF AN EXAMPLE YOU HAVE ALREADY SEEN

Original example on JS Bin: we can change the color, size and speed of the animated rectangle. However, each time we come back to the page, default values are restored.

We would like to save the current values and find them back as they were when we come back to the page.

Here is a modified example that saves/restores its state, you can try it at JS Bin. In this modified version of the animated rectangle example, you can set the color, size, speed, etc. And if you reload the page, the state of the different input field is restored, but also the internal variables. Check the source code in the JS Bin example and read the following explanations.

We used the same generic code for saving/restoring input fields’ values we saw in the first example that used localStorage. The only difference is that we renamed the two generic functions so that they correspond better to their role here (instead of saveFormContent we called the function restorePreferences).

The function initPreferences is executed when the page is loaded.

Source code extract:

  1. function initPreferences() {
  2.    console.log(“Adding input listener to all input fields”);
  3.    // add an input listener to all input fields
  4.    var listOfInputsInForm = document.querySelectorAll(“input”);
  5.    for(var i= 0; i < listOfInputsInForm.length; i++) {
  6.       addInputListener(listOfInputsInForm[i]);
  7.    }
  8.    // restore preferences
  9.    restorePreferences();
  10.    applyGUIvalues(); // Use the input fields’ values we just restored to set internal 
  11.                      // size, incX, color, lineWidth variables
  12. }
  13.  
  14. function addInputListener(inputField) {
  15. // same as before
  16. }
  17.  
  18. function restorePreferences() {
  19. // same as old restoreFormContent
  20. }
  21.  
  22. function applyGUIvalues() {
  23.    // Check restored input field content to set the size of the rectangle
  24.    var sizeWidget = document.getElementById(“size”);
  25.    size = Math.sign(incX)*parseInt(sizeWidget.value);
  26.    // also update the outline element’s value
  27.    document.getElementById(“sizeValue”).innerHTML = size;
  28.    // Check restored input field content to set the color of the rectangle
  29.    var colorWidget = document.getElementById(“color”);
  30.    ctx.fillStyle = colorWidget.value;
  31.    // Check restored input field content to set the speed of the rectangle
  32.    var speedWidget = document.getElementById(“speed”);
  33.    incX = Math.sign(incX)*parseInt(speedWidget.value);
  34.    // also update the outline element’s value
  35.    document.getElementById(“speedValue”).innerHTML = Math.abs(incX);
  36.    // Check restored input field content to set the lineWidth of the rectangle
  37.    var lineWidthWidget = document.getElementById(“lineWidth”);
  38.    ctx.lineWidth = parseInt(lineWidthWidget.value);
  39. }

Practical example 3: example 1 on steroids

TRY THE EXAMPLE!

Online example at JS Bin

This time, using the setItem and getItem method we saw earlier in the course, we could write some generic functions for saving/restoring input fields’ content, without having advance knowledge about the number of fields in the form, their types, their ids, etc.

Furthermore, we removed all input listeners in the HTML, making it cleaner (no more oninput=”localStorage.firstName = this.value;’…)

DEFINE LISTENERS + RESTORE OLD VALUES AFTER THE PAGE IS LOADED, USE GENERIC FUNCTIONS

We start writing an init() function that is called when the page is loaded. This function will:

    1. Define input listeners for all input fields
    2. Restore the last saved value for each input field, if present.

Source code:

  1. // Called when the page is loaded
  2. window.onload = init;
  3.  
  4. function init() {
  5.    console.log(“Adding input listener to all input fields”);
  6.    // add an input listener to all input fields
  7.    var listOfInputsInForm = document.querySelectorAll(“input”);

  8.    for(var i= 0; i < listOfInputsInForm.length; i++) {
  9.       addInputListener(listOfInputsInForm[i]);
  10.    }
  11.    // restore form content with previously saved values
  12.    restoreFormContent();
  13. }

And here is the addInputListener(inputField) function. It takes an input field as parameter and attaches an oninput listener to it, that will save the field’s content each time a value is entered. The key will be the id of the input field (line 3):

  1. function addInputListener(inputField) {
  2.     inputField.addEventListener(‘input’, function(event) {
  3.         localStorage.setItem(inputField.id, inputField.value);
  4.      }, false);
  5. }

Note that at line 2 we use addEventListener (that is not using the oninput property here). addEventListener will not replace existing oninput definitions and keep all existing listeners unchanged.

RESTORE ALL INPUT FIELDS’ CONTENT USING A GENERIC FUNCTION

We have seen how to save all input fields’ content on the fly. Now, let’s see how we can restore saved values and update the form. This is done using the function restoreFormContent():

  1. function restoreFormContent() {
  2.    console.log(“restoring form content from localStorage”);
  3.    // get the list of all input elements in the form
  4.    var listOfInputsInForm = document.querySelectorAll(“input”);
  5.    // For each input element,
  6.    // – get its id (that is also the key for it’s saved content
  7.    // in the localStorage)
  8.    // – get the value associated with the id/key in the local
  9.    // storage
  10.    // – If the value is not undefined, restore the value
  11.    // of the input field
  12.    for(var i= 0; i < listOfInputsInForm.length; i++) {
  13.      var fieldToRestore = listOfInputsInForm[i];
  14.      var id = fieldToRestore.id;
  15.      var savedValue = localStorage.getItem(id);
  16.      if(savedValue !== undefined) {
  17.         fieldToRestore.value = savedValue;
  18.      }
  19.    }
  20. }

In this function, we first get the list of input fields (line 5), then iterate on it (line 14). For each input field, we get its id, which value is the key in localStorage for the previous data saved for this field (lines 15-16). Then if the value is not undefined, we restore it by setting the value of the input field (lines 19-20).

THESE GENERIC FUNCTIONS CAN BE USED IN MANY DIFFERENT PROJECTS

Indeed, if you look carefully, you will see that these functions are really useful. You may easily embed them in your own projects, or perhaps adapt them for a particular need (i.e. for saving input type=”checkboxes” that work a bit differently), etc. Later in the course, we will show how to reuse them with another example: the animated red rectangle.

 

Size limitation, security, localStorage or sessionStorage?

We have already talked about client-side storage limits when we studied the HTML5 cache. We saw that the limit varies depending on various parameters. However, it’s good to recall a few things from the specification:

    • User agents (browsers) should limit the total amount of space allowed for storage areas.
    • User agents may prompt the user when quotas are reached, allowing the user to grant more space to a site. This enables sites to store many user-created documents on the user’s computer, for instance.
    • User agents should allow users to see how much space each domain is using.
    • A mostly arbitrary limit of five megabytes per origin is recommended (translation: give at least 5Mb per origin).

In many cases, local storage is all that your application will need for saving/loading data on demand. More complex ways to do it exist, such as IndexedDB, a No SQL database, that proposes transactions and usually comes with far more available space than local storage. IndexedDB usage is for advanced users and will be covered in the HTML5 part-2 course.

Additionally, there will be a limit on the amount of data that you can store there. Browsers enforce quotas that will prevent you from cluttering your users’ drives excessively. These quotas can vary from platform to platform, but are usually reasonably generous for simple cases (around 5MB), so if you are careful not to store anything huge there, you should be fine.

Finally, keep in mind that this storage is not necessarily permanent. Browsers are inconsistent in how they allow for it to be wiped, but in several cases it gets deleted with cookies — which is logical when you think of how it can be used for tracking in a similar fashion.

For serious applications, you might want to synchronize existing data with the server on a regular basis, in order to avoid data loss (and in general, because users enjoy using the same service from multiple devices at once). This is a rather complex feat, and frameworks such as Firebase can help. Such techniques are beyond the scope of this course and will not be covered.

SESSIONSTORAGE KEY/VALUES INSTEAD OF COOKIES?

Note that if all you need is to store session-based data in a manner that is more powerful than cookies, you can use the sessionStorage object which works in exactly the same way as localStorage, but the lifetime is limited to a single browser session (lifetime of your tab/window).

Also note that in addition to being more convenient and capable of storing more data than cookies, it has the advantage of being scoped to a given browser tab (or similar execution context).

Cookies’ big danger: if a user has two tabs open to the same site, they will share the same cookies. Which is to say that if you are storing information about a given operation using cookies in one tab, that information will leak to the other side — this can be confusing if the user is performing different tasks in each.

By using sessionStorage, the data you store will be scoped and therefore not leak across tabs!

Storing more than strings? Use JSON!

INTRODUCTION

Storing strings is all well and good, but it quickly becomes limiting: you may want to store more complex data with at least a modicum of structure.

There are some simple approaches, such as creating your own minimal record format (e.g. a string with fields separated with a given character, using join() on store and split() upon retrieval) or using multiple keys (e.g.post_17_title, post_17_content, post_17_author, etc.). But these are really hacks. Thankfully, there’s a better way,  JSON.stringify() and JSON.parse() methods.

JSON provides a great way of encoding and decoding data that is a really good match for JavaScript. You have to be careful not to use circular data structures or non-serializable objects, but in the vast majority of cases, plugging JSON support into your local store is straightforward.

TYPICAL USAGE

  1. locaStorage.key = JSON.stringify(object); // or…
  2. localStorage.setItem(key, JSON.stringify(object));

Let’s try a simple toy example (online at JS Bin).  The example below saves a JavaScript object in JSON, then restores it and checks that the object properties are still there!

Source code:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset=utf-8 />
  5. <title>Storing JSON Objects with Local Storage</title>
  6.     var personObject= {‘firstName’: ‘Michel’, ‘lastName’: ‘Buffa’};
  7.     // Store the object as a JSON String
  8.     localStorage.setItem(‘testObject’, JSON.stringify(personObject));
  9.     // Retrieve the object from storage
  10.     var retrievedObject = JSON.parse(localStorage.getItem(‘testObject’));
  11.     console.log(retrievedObject.firstName + ” “ + retrievedObject.lastName);
  12.    // then you can use retrievedObject.firstName, retrievedObject.lastName…
  13. </head>
  14. <body>
  15. </body>
  16. </html>

Explanations:

    • Line 7: we built a JavaScript object that contains a person.
    • Line 10: we store it in localStorage as a JSON string object, with a key equal to testObject.
    • Line 13: we restore it from localStorage as a string, and the JSON.parse methods turns it back into a JavaScript object.
    • Line 15: we print the values of the object properties.

MORE COMPLETE EXAMPLE THAT SHOWS HOW WE CAN SAVE A FORM’S CONTENT IN JSON

Online example on JS Bin that saves in localStorage an array of contacts in JSON

MORE COMPLETE EXAMPLE: A FORM AND A TABLE THAT DISPLAYS THE CONTACTS STORED IN LOCALSTORAGE

Example on JS Bin (uses summary/details, so use a browser that supports it or add a polyfill, as seen in Week 1).

Add contacts using the form, see how the HTML table is updated. Try to reload the page: data are persisted in localStorage.

The source code for this example is a bit long, and we suggest that you examine it in the JS Bin tool. We extensively commented it. It uses:

    • Well structured page with the new elements seen during Week 1 (section, article, nav, aside, etc.)
    • HTML5 form elements with builtin and custom validation (the date cannot be in the past, the firstName and lastName fields do not accept &, #, ! or $ characters),
    • localStorage for saving / restoring an array of contacts in JSON
    • It shows how to use the DOM API for dynamically updating the page content (build the HTML table from the array of contacts, add a new line when a new contact is submitted, etc.)

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s