Saving files locally using Web Storage

This topic starts where Reading local files left off, and demonstrates how to save non-large files locally using Web Storage.

  • Canvas drawing application
  • Saving files using Web Storage
  • Exercises
  • Related topics

One of the easiest ways to save moderately sized files (locally) is through the use of Web Storage. Web Storage is similar to traditional cookies but provides typically larger storage allocation. For example, Internet Explorer 10 allows 10 MG per domain to locally store various items such as strings, files, JavaScript objects, and so on. The following code examples demonstrate how to save files locally using this technique.

Note  The following code examples require a browser that supports File API and Web Storage, such as Internet Explorer 10 or later.

 

Canvas drawing application

In order to make the subsequent code examples more realistic, the first example introduces a simple HTML5 canvas based drawing application. Such applications provide a legitimate need to save files locally. This example will be subsequently extended in order to save one’s artist achievements locally.

Example 1

<!DOCTYPE html>
<html>
  <head>
    <title>Simple Drawing App</title>
    <meta http-equiv="X-UA-Compatible" content="IE=10">
    <style>
      html { 
       -ms-touch-action: none;
       text-align: center; /* Center all contents of the page. */
      }
    </style>
  </head>
  <body>
    <h1>Simple Drawing App</h1>
    <h3>Example 1</h3>
    <canvas id="drawSurface" width="500" height="500" style="border:1px solid black;"></canvas> <!-- The canvas element can only be manipulated via JavaScript -->
    <div>
      <button id="erase">Erase</button>
    </div>
    <script>
      if (!document.createElement('canvas').getContext) {
        document.querySelector('body').innerHTML = "<h1>Canvas is not supported by this browser.</h1><p>To use this application, upgrade your browser to the latest version.</p>";
      }
      else {
        window.addEventListener('load', init, false); // Safety first.
  
        function init() {
          var canvas = document.getElementById('drawSurface'); // A static variable, due to the fact that one or more local functions access it.
          var context = canvas.getContext('2d'); // A static variable, due to the fact that one or more local functions access it.
  
          context.fillStyle = "purple";
  
          if (window.navigator.msPointerEnabled) {
            canvas.addEventListener('MSPointerMove', paintCanvas, false);
          }
          else {
            canvas.addEventListener('mousemove', paintCanvas, false);
          }
  
          document.getElementById('erase').addEventListener('click', eraseCanvas, false);
  
          function paintCanvas(event) { // The "event" object contains the position of the pointer/mouse.
            context.fillRect(event.offsetX, event.offsetY, 4, 4); // Draw a 4x4 rectangle at the given coordinates (relative to the canvas box). As of this writing, not all browsers support offsetX and offsetY.
          } // paintCanvas
  
          function eraseCanvas() {
            context.clearRect(0, 0, context.canvas.width, context.canvas.height);
          } // eraseCanvas
        } // init      
      } // else
    </script>
  </body>
</html>

To use this simplistic application, move your mouse over the square canvas area. To erase a drawing, simply click the Erase button.

Perhaps the most complex aspect of this code are the two "static" variables, canvas and context, declared as locals within init. Because local functions (paintCanvas and eraseCanvas) access them, they are accessible after init has terminated. This has the benefit of not cluttering up the global namespace. Note that this application also works with touch-enable devices, such as a tablet.

Saving files using Web Storage

As mentioned above, modern browsers (including Windows Internet Explorer 9 and later) support Web Storage. There are two types of Web Storage, local storage and session storage. As the name suggests, session storage only persists for the given browser session. Local storage, on the other hand, persists indefinitely.

We will use local storage to save a user’s drawing. This turns out to be relatively trivial, as the following example shows:

Example 2

<!DOCTYPE html>
<html>
  <head>
    <title>Simple Drawing App</title>
    <meta http-equiv="X-UA-Compatible" content="IE=10">
    <style>
      html { 
       -ms-touch-action: none;
       text-align: center; /* Center all contents of the page. */
      }
    </style>
  </head>
  <body id="bodyElement"> <!-- This ID is used in the following script block for feature detection. -->
    <h1>Simple Drawing App</h1>
    <h3>Example 2</h3>
    <canvas id="drawSurface" width="500" height="500" style="border:1px solid black;"></canvas> <!-- The canvas element can only be manipulated via JavaScript -->
    <div>
      <button id="erase">Erase</button>
      <button id="save">Save</button>
      <button id="load">Load</button>
    </div>
    <script>
      function requiredFeaturesAvailable() {
        return (
                 !!window.addEventListener && // Use the double negative "!!" to force the object to a Boolean value.
                 !!document.createElement('canvas').getContext &&
                 !!window.localStorage
               );
      } // requiredFeaturesAvailable
      
      if ( !requiredFeaturesAvailable() ) {
        document.getElementById('bodyElement').innerHTML = "<h2>Required features are not supported by this browser.</h2><p>To use this application, upgrade your browser to the latest version.</p>";
      }
      else {
        window.addEventListener('load', init, false); // Safety first.
  
        function init() {
          var canvas = document.getElementById('drawSurface'); // A static variable, due to the fact that one or more local functions access it.
          var context = canvas.getContext('2d'); // A static variable, due to the fact that one or more local functions access it.
  
          context.fillStyle = "purple";
  
          if (window.navigator.msPointerEnabled) {
            canvas.addEventListener('MSPointerMove', paintCanvas, false);
          }
          else {
            canvas.addEventListener('mousemove', paintCanvas, false);
          }
  
          document.getElementById('erase').addEventListener('click', eraseCanvas, false);
          document.getElementById('save').addEventListener('click', saveCanvas, false);
          document.getElementById('load').addEventListener('click', loadCanvas, false);
  
          function paintCanvas(event) { // The "event" object contains the position of the pointer/mouse.
            context.fillRect(event.offsetX, event.offsetY, 4, 4); // Draw a 4x4 rectangle at the given coordinates (relative to the canvas box). As of this writing, not all browsers support offsetX and offsetY.
          } // paintCanvas
  
          function saveCanvas() {
            window.localStorage.canvasImage = canvas.toDataURL(); // Save the user's drawing to persistent local storage.
          } // saveCanvas
  
          function eraseCanvas() {
            context.clearRect(0, 0, context.canvas.width, context.canvas.height);
          } // eraseCanvas
  
          function loadCanvas() {
            var img = new Image(); // The canvas drawImage() method expects an image object.
            
            img.src = window.localStorage.canvasImage; // Retrieve the last saved artistic achievement from persistent local storage.
            img.onload = function() { // Only render the saved drawing when the image object has fully loaded the drawing into memory.
              context.drawImage(img, 0, 0); // Draw the image starting at canvas coordinate (0, 0) - the upper left-hand corner of the canvas.
            } // onload
          } // loadCanvas
        } // init
      } // else
    </script>
  </body>
</html>

Example 2 is essentially example 1 with the following extensions:

  • We first detect for all required features we are using with the requiredFeaturesAvailable function. As a safety precaution, we double-negate the objects of interest to "cast" the object to a Boolean value.

  • Next we add two new event handlers, saveCanvas and eraseCanvas, that are respectively invoked when the Save and Load buttons are clicked. The saveCanvas function consists of one line:

    window.localStorage.canvasImage = canvas.toDataURL();
    

    This converts the user’s drawing into a form that can be displayed on a webpage and then saves it to local storage under the custom canvasImage property name (this can be any valid name you like).

  • When the Load button is clicked, we retrieve the saved image and render it on the canvas as follows: Because the canvas.drawImage method expects an image, we first create a new generic image object, set its src attribute to the saved drawing (this is usually set to an HTTP path pointing to a graphics file), and then when the image objects tells us that it’s src property is fully loaded, we transfer it to the canvas element by invoking canvas.drawImage.

To convince yourself that the drawing really is persistently saved, create a drawing, click Save, restart your computer, start example 2 again, and then (being careful not to accidentally draw on the blank canvas), click Load – the saved drawing "miraculously" appears.

As an aside, be aware that Web Storage only allows you to store string-based key/value pairs. Despite this, it is possible to store raw JavaScript objects using JSON.stringify(). The following example demonstrates how this can be done:

Example 3

<!DOCTYPE html>
<html>
  <head>
    <title>Storing/Retrieving JavaScript Objects Using HTML5 Local Storage</title>
    <meta http-equiv="X-UA-Compatible" content="IE=10">    
  </head>
  <body>
    <h1>Storing Objects using Local Storage</h1>
    <h3>Example 3</h3>
    <p></p>
    <script>
      if (!!window.localStorage) {
        var person = { firstName: 'John', lastName: 'Anderson' }; // Create a JavaScript object literal.

        window.localStorage.person = JSON.stringify(person); // Convert the object to a string.
        person = JSON.parse(window.localStorage.person); // Convert the object string back to a JavaScript object.

        document.querySelector('p').innerHTML = "<strong>First Name:</strong> " + person.firstName + "<br /><strong>Last Name:</strong> " + person.lastName;
      }
      else {
        document.querySelector('body').innerHTML = "<h2>Local storage is not supported by this browser.</h2><p>To use this application, upgrade your browser to the latest version.</p>";
      }
    </script>
  </body>
</html>

Exercises

Clearly, the above drawing application is rudimentary at best. It is suggested that, at a minimum, you improve this application such that the user must first click on the canvas before drawing starts, and that the "blocky" rectangles (sensitive to mouse/pointer speed) be replaced with contiguously flowing lines.

A brief overview of JSON

How to manage local files

Internet Explorer 10 Samples and Tutorials

Introduction to Web Storage

Reading local files