Alternative Client-Side Storage using Sessvars.js

I was recently on a project where I needed a really flexible persistent data storage mechanism. Normally, this would be fairly easily handled via server-side data persistence code but in this case, each page of the app was basically a single-page XHR app in that to render the page required a JSONP call and lots of DOM insertion and manipulation. What I was trying to accomplish was back-button support for these dynamically rendered pages and as most know, hitting the back-button on dynamically rendered DOM elements won’t set them back to their original state. To further complicate the problem, the pages were being built based on user input into search forms which included input fields, select boxes and even fly-out accordion style controls. Basically, I was dealing with storing a large amount of data across page views and cookies definitely were not the solution. Also considering that the browser requirements started at IE6, none of the current localstorage mechanisms were viable.

Persistent Session Variables in JavaScript

After searching quite a bit, I finally found a solution that worked like a charm. Thomas Frank had created a library called Sessvars.js that leveraged the window.name attribute. The reason for using window.name is that values stored in it persist across page views making it ideal for my needs. The one caveat is that it can only store a string value which at first seemed to throw a wrench in my plans. I needed to store complex data, not just strings. Thankfully, Thomas had come up with a novel way of dealing with this by building a JSON stringifier which serializes and deserializes object data. This allowed me to create a complex data store that could then be converted to a string on the fly. Loved it!

What I did was grab the file name and used that as a key value to identify the specific information for that page. So my object would look something like this:

{ "page1.html" : { "form" : { "wrapper" : "#search-box", 
							  "hash" = "field1=foo&field2=21&field3=Mike" } },
	  		     "callback" : "resetWrapper" },
{ "page2.html" : { "form" : { "wrapper" : "#adv-search-box", 
							  "hash" = "field21=red&field22=blue&field56=February" } } 
	  		     "callback" : "resetWrapper" },							  
{...
}

The benefit of this is that it allowed me total flexibility in storing not only the affected form fields for that specific page as well as allowing for unique form field names on a page by page basis. I could also nest the data as deep as I wanted (i.e.: object within object).

Actually storing that data was extremely easy. I simply defined Sessvars.js in my document:

<script type="text/javascript" src="sessionvars.js"></script>

and then stored my data into a property that I created in the default sessvars object:

sessvars.myObj = { "page1.html" : { "form" : { "wrapper" : "#search-box", 
							  "hash" = "field1=foo&field2=21&field3=Mike" } },
	  		     "callback" : "resetWrapper" },
{ "page2.html" : { "form" : { "wrapper" : "#adv-search-box", 
							  "hash" = "field21=red&field22=blue&field56=February" } } 
	  		     "callback" : "resetWrapper" },							  
{...
};

The library attaches a method to the window’s unload event which automatically handles the stringification of the JSON data once you leave the page so basically assigning the value is all I needed to do to persist the data.

Restoring the Page

The great thing about Sessvars.js is that when a page is loaded, it immediately restores the persistent data making it immediately accessible to your scripts. We were using jQuery on this project so I created a method that would determine the current page, search through my sessvars persistent data and if it found a key, reload the form with the user’s values as well as re-run any previous JSONP calls to ensure the results were populated. I added the method in $(document).ready() to ensure it got called on every page load:

myApp.state = (function(){
	return {

		resetForm : function( key ) {
			if (sessvars.myApp.state[key]) {
			    var o = {};
			    var queryString = decodeURIComponent( sessvars.myApp.state[key].form.hash );
			    var wrapperEle = "", formEle= "", vals = "";
			    
			    queryString.replace(/([^=]+)\=([^&]+)&?/ig, function(item, a, b){
			    	a = a.replace( /\&/g, " ");
			    	b = b.replace( /\+/g, " ");
			    	if (o[a] && typeof o[a] !== "object") {
						o[a] = o[a] + "," + b;
					} else {
						o[a] = b;
					}
				});	 		    
	    
	    		wrapperEle = $( sessvars.myApp.state[key].form.wrapper );
	    
		    	for (var index in o) { 
			   		vals = o[index].split( "," );
			   		
			   		formEle = wrapperEle.find("input[name=" + index + "], select[name=" + index + "]");

			   		if (formEle.length > 0) {
			   		
				   		if (formEle[0].tagName == "INPUT" && (formEle[0].type == "checkbox" || formEle[0].type == "radio")){
				   			formEle.attr( "checked", "");
				   			for (var i=0; i < vals.length; i++) {
		   	   			
				   	   			if (index == "accordianSet") {
					   	   			wrapperEle.find("input[name=" + index + "][value=" + vals[i] + "]").trigger( "click" );	
				   	   			} else {
									wrapperEle.find("input[name=" + index + "][value=" + vals[i] + "]").attr( "checked", "true" );				   	   			
				   	   			}
				   	   			
				   			}
				   		} else {
				   			formEle.val( o[index] );
						}
						
					}
				}
    		}			
		},
		resetPage : function( key ) {

			if (sessvars.myObj.state[key]) {
				this.resetForm( key );
			}
			
			.... call various other methods to reset the page ....
			
		};

})();

$(document).ready( function(){
	myApp.state.resetPage( myApp.utils.getPageName() );
});

The result is that the search forms would get repopulated with the previously entered values and and the JSONP call re-run based on these new values. It worked exactly like we needed it.

Closing Thoughts

Using Sessvars.js definitely helped us out of a jam. After looking at a number of back-button solutions and even updating window.location, this turned out to be the best solution for our needs due to the dynamic nature of every page. The other takeaway is that using window.name safely affords you about 2mb of data storage. I say this because of the testing that Thomas did. In his research he found the following:

IE7 gave up at 32 Mb with a “out of memory” error. Firefox gave up at 32-64 Mb -sometimes crashed there about – otherwise threw an “out of memory” error.Safari gave up at 64 Mb and crashed. Opera has a limit built in (wise) and gave up after 2 Mb, throwing the error “Object to large (implementation limit.)”

I’ve not tested this out in newer versions of the browser so I can’t say if these limits are still set but for this project, 2mb was more than enough for our needs.

Update:

Since posting this I’ve been sent two other JS-based storage solutions which are definitely worth looking at:

Storage Pollyfiller by insanely talented developer Remy Sharp
LawnChair by uber-JS ninja Brian Leroux

Rey Bango

7 Comments

  1. Wow, that is pretty cool, but using the Window.name property for data storage seems like such a hack. Good thing it is abstracted by a library. Maybe there will be a better way some day.

    What advantage does this have over using cookies?

    • You’re definitely right in that it is a bit of a hack but the big thing that the lib had over cookies is the storage size. I can’t recall the cookie limitations off the top of my head but I remember that window.name’s 2mb baseline capacity was more.

  2. The problem with cookies is that they are sent to the server on every request. That’s wasted bandwidth if you only need the state client-side. Sessvars.js looks interesting.

  3. Why not use IE5.5+ user data binary behavior?? It works exaktly like local storage, but with a different syntax…

    #myStore
    {
    behavior:url(#default#userdata);
    }

    Reload

    • Hey Erik,

      Thanks for the suggestion. Didn’t know about that. Is that cross-browser? How much storage does it provide?

Comments are closed.