Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Web Storage Made Easy with AmplifyJS

5.00/5 (4 votes)
2 Jan 2013CPOL15 min read 35.9K   363  
Local storage, session storage, and all of the other web storage technologies can be great, but they can also be complicated. Learn how to use them, and then how to make life easier with AmplifyJS.

Introduction

As the drive to make websites behave in a rich-client manner continues, the need for cached data has increased greatly. One of the offshoots of the HTML5 standard is Web Storage, a persistent data storage API. Web Storage actually comes in two flavors: Local Storage and Session Storage. In both of these storage mechanisms, data can be stored on the client’s machine for later use by the website.

In this article, I want to give a brief overview of Web Storage for those who are unfamiliar with it. Before I get into the details of the API, however, I want to address a few areas where Web Storage could be used. Once we have covered all of that foundational information, I want to jump right into AmplifyJS and how it makes the use of Web Storage simple. I will then tackle a couple of our Web Storage use cases to show how AmplifyJS solves those problems.

Why Should I care About Web Storage

Technology without a good use case is just a toy. There are a number of excellent use cases for Web Storage. Here are a couple basic use cases that represent real-world issues that I am facing:

Scenario One – Populating Dropdowns

You have a site with a number of dropdown menus that are used on multiple pages of your site and they do not change often. You could cache the data server-side for the session but this can cause issues on the server, depending on the size and number of drop-downs like this. Also, each time that user visits your site in a new session you have to hit the database in order to populate your models with the appropriate data. With Local Storage, you can cache the dropdown data on the client and pull from the cache instead of pulling from the server each time. This reduces the number of hits on the database server and it relieves pressure on the web server as well.

Scenario Two – Long Form Data

You have a page on your website that contains a large number of fields to be filled out. Sometimes, half way through filling out the form, the user realizes that they need more information. Since the form is incomplete, it cannot be posted as is. They have to re-fill it out once they have all of the data. Instead, with Local Storage, they could fill out what they know and save it locally. They could then come back to the form tomorrow or whenever they get the rest of the information and pick up where they left off.

There are other scenarios that we could look at including offline or semi-connected web applications, but these will give you an idea of what needs we can address with Web Storage.

Overview of Web Storage

Web Storage is the storage of data in key-value pairs in the browser storage. While the standard has not been finalized yet, the recommended storage space for Web Storage is 5 MB. That is a significant upgrade over cookies, which were limited to 4k! This storage limit is per site and it is sandboxed. A site is defined by the following criteria:

  • By domain name (ex. microsoft.com)
  • By sub-domain name (ex. contoso.microsoft.com)
  • By protocol (ex. http or https)
  • By browser (ex. Chrome or Safari)
  • By browsing session type (ex. normal or in-private)

What this all means is that if all of the above don’t match, you will get a new Web Storage pool to play with. This is a good thing for a couple reasons. First, you won’t have data crossing boundaries in an unsafe manner (data for Bob being read by Joe). Second, you can be fully in control of the data in the cache without worrying about affecting other sites.

The one thing that always comes up here is the idea of security. How secure is the data in the cache? The answer is that it isn’t secure at all. Only put data in Web Storage that you are ok with the user reading. All data is stored in plain text format.

Local Storage vs. Session Storage

The difference between Local and Session Storage is quite easy to understand. Local Storage can persist indefinitely while Session Storage only persists for the length of that session. There are, however, a couple tricky things to note here. First, while sessions are normally limited to when the browser is open, the W3C standard does permit sessions to be resumed after a crash. That means that session data might persist as well (this is browser-dependent). Second, Local Storage is still subject to being cleared by the user (clear browser cache), which means you cannot depend on the data always being there.

Browser Support

Browser support for web storage has been relatively good. All major browsers support both Local Storage and Web Storage. Internet Explorer 8, Firefox 3.5, Safari 4, Chrome, Opera 10.5, iPhone 2, and Android 2 was where support was entered for Local Storage and Session Storage, with the one exception being Firefox which supported just Session Storage starting at version 2. This raises an issue, however, of fallback for incompatible browser versions. That is why I am so thrilled to introduce the AmplifyJS library to this discussion.

Overview of AmplifyJS

AmplifyJS takes care of all of the plumbing of Web Storage with a very graceful fallback that is transparent to the developer. They support Global Storage for Firefox 2-3, UserData for Internet Explorer 5-7, and Memory storage for all other incompatible browsers. What this means is that you as the developer don’t need to handle how to fall back gracefully and you don’t need to put all those browser checks into your code (it was when I realized this that I started dancing).

To add AmplifyJS to your website, simply download the latest version of AmplifyJS (the full version or just the Store part) from the website at http://amplifyjs.com. You can also visit their Github page at https://github.com/appendto/amplify to gain access to the entire project. Once you have downloaded the files you need, add the specific JS file you need to your project like so:

HTML
<script src="./Scripts/amplify.min.js" type="text/javascript"></script>

To store data in AmplifyJS, once you bring the script into your web application, you need to write only one line of JavaScript:

JavaScript
amplify.store(dataId, data);

That is it. Now your data is stored in Web Storage (or the highest level of compatible storage available to you). The dataId is a unique identifier that you will use to access your data later. Be careful of this, since you can overwrite your data if you accidentally use the same dataId again. The data that you store in Web Storage is anything that can be serialized.

To get the data back out of Web Storage, it again is only one line of code:

JavaScript
var data = amplify.store(dataId);

Note that you still call the store method, but this time you just pass in the unique identifier for the data you want. The output of this method is your data.

For about 90% of the use cases out there for Web Storage, these two lines of code will be all you need. All of the plumbing normally associated with using a cutting-edge web technology has been eliminated and what is left is a clean, simple implementation that just works.

Advanced Options of AmplifyJS

There are a few extra options in the library that need to be discussed for those times when you will need them. First is the option to select your specific storage mechanism. While this takes away a lot of the goodness of AmplifyJS, it is possible and there are times when it is useful to do so. To specify the storage mechanism, use the following line of code:

JavaScript
amplify.store.sessionStorage(dataId, data);

Note the “sessionStorage” method used instead of the root “store” method. You can substitute whatever type you want here (sessionStorage, globalStorage, userData, or memory). Just be sure to note that you will need to check for the availability of that particular storage mechanism first or you will get an error in any browser that does not support that mechanism.

Next is the option to expire your data after a specified amount of time. To specify a timeout on your data, simple add a third parameter to the method when you are storing data like so:

JavaScript
amplify.store(dataId, data, { expires: 60000 });

Note that this number is in milliseconds and indicates the amount of time from now to expire the data. Thus, in my example the data will expire after one minute. This expiration, however, is specific to using the AmplifyJS library. When it looks up the data, it makes sure it is not expired. If you were to circumvent AmplifyJS and look for the data directly, it would still be there. This isn’t really a problem, since you wouldn’t be storing sensitive data in Web Storage anyway, but it is something to understand going in.

That brings up a minor point about how AmplifyJS stores data. In order to keep its data separate from “regular” data you store in Web Storage, all IDs have a prefix of “__amplify__”. That way if you use an ID of “FirstName” to store something in Web Storage directly and then store an item with an ID of “FirstName” using AmplifyJS, they won’t overwrite each other. This is a nice safety measure. The thinking is that if you are accessing Web Storage directly and you are doing it via AmplifyJS, you are probably not ensuring the keys are all unique. Most likely the different parts of the site have been developed by two different developers who aren’t communicating. If this wasn’t done, the phantom bugs it could cause would be a nightmare to debug.

The next advanced feature isn’t really that advanced. The occasion might arise where you want to remove items from storage. To do this, simply store a null value for each key you want to wipe out like so:

JavaScript
amplify.store(dataId, null);

That wipes out the data and readies the ID to be used again. If you want to wipe out all of the data, you can either loop through each ID or you can use one of the Storage-specific options to clear the data.

That leads to the last topic I want to cover, which is what to do when the storage fills up. Since you have a limited amount of space (typically 5 MB), there can be occasions where the storage becomes full. When this happens, AmplifyJS will throw a “amplify.store quota exceeded” error. Before it throws this error, however, it will clear out all of the expired data first and try the operation again.

Tackling the Use Cases

Let’s dig into our real-world examples with sample code that could solve each problem so we can see how easy it is to use AmplifyJS in our sites:

Scenario One – Populating Dropdowns

We have decided that we are hitting the database for dropdown lists too often, even if we cache the data on the server per session. To alleviate these issues, we are going to store our lists on the client side to improve site performance by reducing database and web server load. To accomplish this, we will populate our dropdown lists on the client via Web Storage first. If the data isn’t in Web Storage (or if it is expired), we will make an Ajax call to the server to load the data and populate Web Storage.

The first thing I’ll do is to check to see if the list is in Web Storage. If it is, I’ll use that data to populate my dropdown. If it isn’t in Web Storage, I’ll use AJAX to make the call to the database. I’ll then save that data in Web Storage and populate my dropdown. Here is the code that does just that:

JavaScript
//Populates the Vehicle List dropdown
if (amplify.store("vehiclelist")) {
   loadDropdownData(amplify.store("vehiclelist"));
   toastr.info("Used LocalStorage for Vehicle List");
}
else {
   $.ajax({
      type: "GET",
      url: "/Content/VehicleList.txt",
      data: ""
      dataType: "json",
      cache: false,
      success: function (data) {
         amplify.store("vehiclelist", data, { expires: 86400000 });
         toastr.info("Used Ajax call for Vehicle List");
         loadDropdownData(data);
      }
   });
}

There are a couple things to note in this code. First, I’m using the ToastrJS library to pop up a message letting you know where the data came from. That is just so you can see what is happening since the form will just work no matter where the data comes from. Second, I used a timeout on the data of one day. When I next ask for the data after the 24-hour period has expired, the data will be flushed and the new data will be loaded. Finally, note that I’m storing the entire JSON response in Web Storage. I can now access these results from Web Storage just like I would if I just got them directly from the server.

Now the dropdown list will be populated from the database once a day. We aren’t caching any dropdown data on the web server, and we aren’t hitting the database repeatedly for the same data. In certain scenarios, this can be a huge benefit to our resources. We also have the added bonus of reducing the traffic going over the wire.

Scenario Two – Long Form Data

Our users have complained that the employee survey is too long to do in one sitting. We broke up the site visually into multiple “pages” but it is still really one form and we don’t want to submit the data to the server until the entire form is filled out. To solve this problem, we will store the contents of each input as soon as the user leaves the input. That way, if the user decides to fill out one “page” of data and then come back to the survey tomorrow, the data from today will still be there.

Accomplishing this is very easy with AmplifyJS. We will just store any input when the user leaves the field (the blur event). However, to make things a bit more intentional, I decided that we should add a class called storedata to any input we wanted to store. That way we could easily ignore certain fields that we don’t want to store (such as password fields). I also decided that we should use the form id underscore the input id as the key name for storing the data. For instance, if we have an input with an id of FirstName in the form with the ID of EmployeeForm then the key for storing this particular data would be “EmployeeForm_FirstName”. That way we don’t have a conflict if we have multiple FirstName inputs on different pages in our site. It would only be a problem if both of our forms had the same id as well.

Here is my code to accomplish this task:

JavaScript
//Stores data for all input items that are marked with the storedata class
$(document).on("blur", "form input.storedata", function () {
   StoreChangedData($(this).closest('form').attr('id') + '_' + $(this).attr('id'), $(this).val());
});

Note that we are hooking into the blur event for any input that has a class of storedata inside of a form. The blur event fires when the user leaves the particular input. When they do, we will call the StoreChangedData method and we will pass in two values: the key and the value. This is where we build that key of form id underscore input id.

The StoreChangedData method is one that I created to reduce the number of times the storage event would fire. If we simply called the amplify.store method in the blur event, we would store the “new” data every time the user left the field, regardless of whether the data had changed. Instead, I wrote a method to compare the data first to see if we needed to store the new data. Here is that method:

JavaScript
function StoreChangedData(id, data) {
   var curr = amplify.store(id);

   //Only stores the data if it has changed (so we don't fire extra storage events)
   if (curr !== data) {
      amplify.store(id, data);
   }
}

I first load the data from Web Storage (using the var curr = amplify.store(id); line). Then I compare that data to the data that was passed into this method. If they are not the same, I store the data in Web Storage using AmplifyJS. I chose not to put a timeout here because the point of this exercise is to allow the user to revisit a form down the road and complete it. However, you could put a timeout on the data here if you wanted.

Now when a user starts filling out our giant form, the data will be saved until they are ready to submit it. Any field that we don’t want to save will be ignored, since our code will only save input fields that have a class of storedata. Also, when the user opens the form, the stored data will be loaded onto the form.

Example Project

I have attached an example project to this article that demos both of our scenarios. I built the example using just html pages and dummy JSON data but you could create the same thing using something more complex like ASP.NET MVC. In my testing, I was using ASP.NET MVC to set up and demo all of the capabilities of AmplifyJS and Web Storage.

The code in my attached code is heavily documented to help you understand what is going on. It follows right along with the examples I gave in this article so it should be easy enough to follow. Please note that the examples I have developed for this article are purely for demonstration purposes. This is not production code. I have stripped out all of my “normal” code in order to show you how to use AmplifyJS without confusing you.

Finally, I included a number of calls to ToastrJS. These messages are just to show you what is happening behind the scenes. These lines are not necessary to the success of the code. You can safely remove them once you feel comfortable that you know what is happening.

Conclusion

Web Storage is a wonderful technology that has a number of practical uses in the real world. AmplifyJS solves a number of problems for us in regards to Web Storage, including how to gracefully fall back to supported storage mechanisms in older browsers. With such an easy solution to the caching of data on the client side, now is the perfect time to start using Web Storage with AmplifyJS.

References:

W3C Web Storage Recommendation - http://www.w3.org/TR/webstorage

AmplifyJS Homepage - http://amplifyjs.com

AmplifyJS GitHub page - https://github.com/appendto/amplify

History

  • January 1, 2013 - Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)