Local Storage with HTML5? Here's How
PLUG-IN FREE SERIES - Tutorial 4
When HTML was originally created, the inventors couldn't possibly have anticipated a day like today, when web servers aren't just dishing out static pages and images, but sending down entire applications that run in browsers. Even simpler sites, such as those hosting news articles, are still sophisticated web applications, often allowing people to log in, post comments, and even interact with the site through Facebook. Today's web developers are software applications developers, with the browser as platform.
But one huge aspect of software applications has been missing from web applications: The ability to save and load data on the user's computer. Yes, the user can do File->Save As in the browser, but that just saves the web page. The browser, for security reasons, isn't allowed to write files to the user's computer. That prevents a lot of problems with users, but it also creates some.
HTML5 has a solution: Local storage (officially called "Web Storage"). While you still can't save files just anywhere on the user's hard drive, you can save data onto the user's computer in an area specifically reserved for your web application. By saving data locally, you can persist data across page refreshes without having to send data back to the server. When users return to your website, they can load the data back up, picking up right where they left off. No need to save the data on your server. Of course, this can be either an advantage or a disadvantage.
For example, if the user logs on from a different computer, he or she won't be able to access data that was stored locally from their previous computer. But that's not a flaw. It's an architectural decision on your part. Do you keep the data on the server or do you store it locally? I won't say one is right or wrong. Like most technologies, choice depends on the requirements of your application and the end user. So for now, I'm just going to show you how to use the local storage.
Saving States and Mobile
Let's explore two aspects of web storage. First, we'll just save the state of some controls (a checkbox, and so on) so when the user returns to the page, those controls will go back to the state they were when the user left. That's a pretty simple use case, but has a lot of practical applications. For example, if the user is filling out a long form that goes through multiple pages (such as a job application), they might want to save their work and come back later. Using web storage, your app can save the data and easily retrieve it, letting them pick up where they left off. And second, we'll actually store some files edited through a simple text editor.
And finally, to top it off, it's important to factor in mobile. For that, we're going to add some gestures. For this we'll strive towards a modern, flat, block appearance, allowing the user to swipe the screen left and right to move between files. This should give you some ideas on how you can incorporate such features in your own app. And as usual, it will all prove to be incredibly easy, using JavaScript, HTML5, and no plugins, including no Flash. And even better? These techniques will work across all modern HTML5-compatible browsers, which includes virtually all the newest phones-Windows 8 phone, Android, and iPhone.
Saving Control States for a Single Session
Starting simple, we'll just save a couple control states. Here's the scenario: Suppose you have a long form the user needs to fill out. Because it's long, you've decided to divide it up into multiple pages, as a wizard. At the end of all but the final page is a "Next" button; the final page has a "Save" or "Submit" button.
Using JavaScript, you could put the entire wizard on a single page, with the different sections showing and hiding when the Next button is clicked. This is the approach that has been used for the last several years; however, there's a fundamental flaw: If the user clicks Refresh, all is lost, and the wizard will go back to Page 1. There are ways around it, but it's a bit of a headache.
(Incidentally, if you've been doing web development for awhile, consider this: In the past, such as with ASP.NET, you would send your session state back to the server with each page. Using session storage, that's no longer needed.)
Instead, you can use session storage, which works with both cases, whether your wizard spans multiple pages or if the page is combined and shows and hides sections dynamically. In the former case, you can keep all the combined data saved as a session, and accumulate it until the final page. If the user refreshes a page, that page just reloads the session data. In the latter case, with the form all on a single page, when the page loads you can load the session and restore the wizard back to the state it was in before.
For this first example, let's just save the control states. If the page is refreshed, the controls will re-load with the state they were last in. Then we'll have a second page that continues to read the same data before finally submitting it all. And similarly, if the second page gets refreshed, the data will still be there.
Here's the full code:
<!DOCTYPE html>
<html>
<head>
<script src="jquery-2.0.2.min.js"></script>
</head>
<body>
<h1>Job Application: Page 1</h1>
<b>Name:</b> <input type="text" id="name" /><br />
<b>Address:</b> <input type="text" id="address" /><br />
<input type="checkbox" id="presentlyEmployed" /> Presently Employed<br />
<b>Position Applying For:</b><br />
<input type="radio" name="position" value="Acrobat">Acrobat<br />
<input type="radio" name="position" value="Cannon">Human Cannon<br />
<input type="radio" name="position" value="HighWire">High Wire<br />
<input type="radio" name="position" value="LionTamer">Lion Tamer<br />
<input type="radio" name="position" value="Concession">Concession<br />
<button id="NextButton">Next</button>
<script>
$('#name').val(window.sessionStorage.name ? window.sessionStorage.name : '');
$('#address').val(window.sessionStorage.address ? window.sessionStorage.address : '');
$('#presentlyEmployed').prop('checked',window.sessionStorage.presentlyEmployed == 'true');
$('input[name=' + window.sessionStorage.position + ']').prop('checked', true);
$('#NextButton').on('click', function() {
// Save the state
window.sessionStorage.name = $('#name').val();
window.sessionStorage.address = $('#address').val();
window.sessionStorage.presentlyEmployed = $('#presentlyEmployed').is(':checked');
window.sessionStorage.position = $('input[type=radio]:checked').val();
document.location="controlstate2.html";
});
</script>
</body>
</html>
The code is pretty straightfoward. We added code to handle the button's click event; we gather up the data, and save it in the window.sessionStorage object. This object is where we put the data for the session; however, it's important to remember that you can only store strings in this object. If you try to store a JavaScript object, you'll end up with the string "[object Object]", which isn't what you want at all. So what I'm doing here is just gathering up all the data, and one by one adding members to the sessionStorage object, one member of each piece of data. (There is a trick, however, using JSON.stringify that I'll show you in a moment.) Also, because all the data in sessionStorage are strings, booleans and numbers get converted to strings. (For example, true gets saved as the string literal "true".)
Now you might notice something interesting: We're not even using forms, and we're not using a form submit action. Instead, we're just attaching code to a button's click event, and simply loading the next page, with no query parameters in the URL and nothing posted back to the server! We just go directly to the next page.
In this code, we're making use of the window object's sessionStorage member. Because window is an object, you can access the member either as window.sessionStorage or as window['sessionStorage']. The JavaScript runtime, however, maintains a special object known as the global object. If you create variables without using the var keyword, those variables are actually members of the global object. When JavaScript runs in the browser, the global object is the object called window. All your global variables get stored as members in the window object. That means, technically, you don't even need to include the name "window". Instead, you can just access the sessionStorage object directly:
sessionStorage.name = $('#name').val();
Now here's the next page:
<!DOCTYPE html>
<html>
<head>
<script src="jquery-2.0.2.min.js"></script>
</head>
<body>
<h1>Job Application: Page 2</h1>
<b>Employment History</b><br /><br />
<b>Current Employer:</b> <input type="text" id="CurrentEmployer" /><br />
<b>Previous Employer:</b> <input type="text" id="PreviousEmployer" /><br />
<button id="SubmitButton">Submit</button>
<script>
$('#name').val(window.sessionStorage.CurrentEmployer ? window.sessionStorage.CurrentEmployer : '');
$('#address').val(window.sessionStorage.PreviousEmployer ? window.sessionStorage.PreviousEmployer : '');
$('#NextButton').on('click', function() {
window.sessionStorage.CurrentEmployer = $('#CurrentEmployer').val();
window.sessionStorage.PreviousEmployer = $('#PreviousEmployer').val();
});
</script>
</body>
</html>
Now for that JSON.stringify trick I mentioned. If you have a complex JavaScript object that you need to store, instead of creating a new member with sessionStorage and trying to piece the object back together on the next page, you can just stringify it. If you're familiar with JSON.stringify, here's some sample code you can run with:
var data = {
name: $('#name').val(),
address: $('#address').val(),
position: $('input[type=radio]:checked').val()
};
window.sessionStorage.data = JSON.stringify(data);
And here's what you do with it on the second page:
if (window.sessionStorage.data) {
var data = JSON.parse(window.sessionStorage.data);
}
The final thing we need to add is the page refresh for both pages. This is simply a matter of checking if the data is present, and if so, store it into the controls. Here's the code to add at the top of the only <script> section:
$('#name').val(window.sessionStorage.name ? window.sessionStorage.name : '');
$('#address').val(window.sessionStorage.address ? window.sessionStorage.address : '');
$('#presentlyEmployed').prop('checked',window.sessionStorage.presentlyEmployed == 'true');
$('input[name=' + window.sessionStorage.position + ']').prop('checked', true);
Pay close attention to how I'm testing for the existence of the member in the sessionStorage object before saving it-yes, even for the final case, with the radio buttons. (If the member isn't present, the jQuery selector will fail, and we won't get a runtime error.) Also, with the presentlyEmployed checkbox, if the member isn't present, the test against true will still work without an error, and return false.
Now here it is for the second page; again, we add this to the top of the only <script> section:
$('#name').val(window.sessionStorage.CurrentEmployer ? window.sessionStorage.CurrentEmployer : '');
$('#address').val(window.sessionStorage.PreviousEmployer ? window.sessionStorage.PreviousEmployer : '');
Finally, here are a couple things you might want to consider. First, the data doesn't get saved into the session object until the button is clicked. Instead, you might want to add handlers to all the controls and save the data when the control changes. One good place to do this is when the control loses focus. (As long as you know that the final control will lose focus-sometimes if you add a keyboard handler so that pressing Enter causes the next page to load, the final control the user was on won't lose focus.) Then you could add a data attribute (such as data-name="presentlyEmployed") to each member, and just grab that to obtain the name to use in the storageSession object. Then you'll have fully-generic code, with only a few lines. Also, if you're ambitious, I highly encourage you to look at the JavaScript library called Knockout.js. This library lets you bind controls on the page directly to JavaScript objects. I've used this library extensively, although I haven't personally tried it with the sessionStorage object. If you're interested in experimenting, I encourage you to do so.
Before we continue to our next example, let's review the ground rules. What happens if the pages are on different sites? It won't work. The domain needs to be the same. Session is saved per domain. Second, what happens if the user closes the browser? The data will be lost. That's the key with session storage. If you need the storage to last longer, then you can use local storage, which we'll look at next.
Full Local Storage: A Simple Editor, Gestures
While session storage only lasts until the browser window is closed, local storage persists between sessions. This is ideal for storing documents your users might create. But remember: You're storing data locally on the user's computer. If the user connects to your site from another computer, the data won't be there. Your job, as the software developer, is to determine which is best for your situation: storing locally or storing the data on the server. Local storage is nice because you don't need to worry about transferring the data up to the server. Further, if the web browser opens a cached version of your site while the user is offline (or if the internet connection goes down), your page can continue to operate, working on local data. Those are the benefits to local storage.
Storing on the server, on the other hand, while requiring a live connection, subsequently allows the user to access the data from any computer. Of course, you could combine the two; store the data on the server and locally. That way if your user needs to work offline, the data is still there and can be used and edited; then when the connection returns (such as when the plane lands), the data can be pushed back up to the server.
How do you use local storage? Let's build a simple editor. And while at it, let's make use of some modern gesture events for the full HTML5 and mobile experience. The app we're about to build is a simple notepad editor that lets the user store multiple documents in the local storage. The user can swipe left and right to move between the documents. The user can swipe down to create a new, empty document. The user can double-tap to edit a document.
A single document will take up the entire browser window. When the document is edited, a text area will open, taking up the entire browser window. Here's what the screen looks like with a sample document loaded:
To manage space, we won't load all the documents into the browser. Instead, we'll load the one that is in view, and the one that is to be displayed to the left of it, and the one that is displayed to the right of it. At any given moment, at most three documents will be loaded. That way when the user swipes left or right, the document on the right or left will slide into view, respectively. One a document slides into view, the next document on the side will load, and the one two-off will be removed.
When the app loads, it will check if there are any documents in the local storage to open. If so, read the first two in. If not, create four sample documents; store those samples in the local storage; and then open the first two. (Why two? Because the user will be looking at the first doc, meaning there won't be any to its left. The user can only swipe left to bring the next one into view.) We still create all the divs, however; we just keep the ones off view empty. Empty div tags are cheap, and it's fine keeping them all in their correct positions.
To make the swiping work, we will use two technologies: First, we'll use CSS3's support for transitions. The major browsers have supported this for quite some time using vendor-specific tags. Next, we'll use hammer.js to implement the swiping actions. Here's the CSS style:
.smoothswipe {
-ms-transition: left .4s ease, top .3s ease;
-webkit-transition: left .4s ease, top .3s ease;
-moz-transition: left .4s ease, top .3s ease;
-o-transition: left .4s ease, top .3s ease;
transition: left .4s ease, top .3s ease;
}
When the user swipes, we'll move the next document into view, using the CSS easing. If the user only drags a little, he or she can glance at the document on the left or right, as it moves just a bit into view. But without a swiping motion, the active document will bounce back into place. Only a full swipe will cause the next document to slide into view. This will allow for a natural-feeling "peek ahead" feature.
Here's what the screen looks like when the screen is slid only a bit; you can see the document on the right starting to slide into view:
The HTML itself only consists of three elements: A container div and an editor div, the latter of which has a textarea element inside it. The editor div will be invisible until needed. (We're not implementing full WYSIWYG here; but I encourage you to explore that feature.)
<div id="swipeholder" style="width:100%; height:100%; overflow:hidden"></div> <div id="editor" style="display:none; position:fixed; left:0px; top:0px; width:100%; height:100%; z-index:9999"> <textarea style="width:100%; height:100%;"></textarea> </div>
There isn't space here to list the entire JavaScript code; instead, I'll highlight a few parts of it, and you can download the entire code from the SourceForge project page.
To handle the gestures, we make heavy use of the hammer.js library. We capture the dragstart, dragleft, dragright, dragdown, and dragend events to handle the dragging. We also capture the swiperight and swipeleft events to move to the documents that are to the left or right. (Swiping right slides everything right, which in turn brings the document that's to the left into view.) We also handle the swipedown event to create a new document, and the double-tap event to open the editor and close the editor.
We accommodate two types of sliding; as the user slides a little, we manually move the documents. We move individual pixels, and as such during this time we turn off the easing transitions. We accomplish that by using jQuery to remove the smoothswipe class from all the elements:
var shiftSmall = function(offset) {
var width = $(window).width();
$('.slidingtext').removeClass('smoothswipe');
$('.slidingtext').each(function() {
var order = $(this).data('order');
$(this).css('width', width + 'px');
$(this).css('left', (order * width - _active * width + offset) + 'px');
});
};
In this code we loop through the elements holding the documents (which have a class of slidingtext), and move their position only the amount specified in the drag.
We keep careful track of when the dragging starts. The reason is that if the user drags just a little and lets go, we want to see a drag. But if the user is swiping, we also want to see a drag as the swiping is happening. When the user drags and lets go, we let hammer.js decide it if was just a slight drag or a full swipe. If it's only a slight drag, we slide the current document back to its starting point. But if it's a full swipe, then we slide the next document into view. The hammer.js code handles this perfectly; if a swipe action occurs, the swipe event takes place before the dragend event. That's perfect, because it allows us to note that the swipe took place and make a note not to do a restore in dragend. Otherwise, if there's no swipe, we restore the active document back to its current location. In both cases, we bring back the smoothswipe class so the easing transitions occur nicely:
$('.slidingtext').addClass('smoothswipe');
Then we loop through all the div elements for the documents, and move them to their new position. If we swipe to the left, we physically move them to the left by one screen width, and similary for swiping to the right.
This is then when we load the documents from local storage. When a document comes into view, we check if the document to its left is loaded; if not, we load it; and similarly for the document to its right. The document that scrolled into view will already be loaded, but in all three cases, we nevertheless check if the document is already loaded; if not, then we load it. We also delete any other documents off the screen (but only delete them from the screen; the docs are still in local storage.)
Loading local storage is easy; just as with the session storage, we access the storage through a key-value mechanism. In this case we use the window object's localStorage object:
localStorage['name'] = 'my text';
In order to determine which document to load from storage, each DIV has a data attribute that we store the document names in. They each start with the string "doc_" followed by a unique number. So we grab that attribute, load the text from storage, and put it right into the DIV element's HTML:
Here, then, is the full function that scrolls the element into view, loads the appropriate documents, and deletes any from the screen that aren't needed:
var scrollToActive = function() {
var width = $(window).width();
$('.slidingtext').addClass('smoothswipe');
$('.slidingtext').each(function() {
var order = $(this).data('order');
$(this).css('width', width + 'px');
$(this).css('left', (order * width - _active * width) + 'px');
$(this).css('top', '0px');
if (order >= _active - 1 && order <= _active + 1) {
if ($(this).html() == '') {
// Only load if it isn't loaded already
var text = localStorage[$(this).data('storagename')];
$(this).html(text);
}
}
else {
$(this).html('');
}
});
};
Editing is simple; we capture the double-tap event and open a full-screen DIV containing a text area, allowing you to type in the text as raw HTML. Then double-tap again, and we save it back to local storage. And finally, creating a new document is even easier; I capture the swipe down motion, and insert a new document into the localStorage. Then I call the above function to slide it into view. The result is actually kind of a cool animation as the existing document, which was slid down, slides at an angle up to the right and out of view, while a new document appears.
Conclusion
The code for the sample is a bit longer, but that's really the meat of it. If you want, I encourage you to take it to the next step. For example, I'm not handling a window resize event. You'll probably want to add that in, as I'm using a fixed size for each document. (Sliding the documents, however, does factor in the new screen size.) You might also want to add in a nicer WYSIWYG editor. All in all, session storage and local storage are incredibly easy to use. When you add in a gesture-driven GUI, you can then make an entire application for reading and writing simple notes-or not so simple; with a full editor, this could become a full-featured word processor. And the beautiful part is it's completely standards-compliant, and completely plugin free. No plugins required!
About the Author
Jeff Cogswell has nearly 25 years of experience as both a software engineer and a professional writer. He has worked on development teams, doing everything from low-level C programming on Unix up to high-level web development. In the past decade he has focused his work primarily on web development, including mastering such technologies as server-side programming with C#, and client-side programming such as HTML, XML, JavaScript, and Adobe tools, and now HTML5. He has authored numerous books, including C++ All-in-One for Dummies. He writes regular columns for SourceForge and SlashDot, including columns on parallel programming and development.











A Lap Around HTML5: HTML5 Graphics