deCarta API Tutorials

deCarta mapping tutorials for developers of location enabled mobile and web applications. These HTML5 tutorials are a first in a continuous series provided by deCarta.

HTML5 Local Storage and deCarta Mobile JavaScript API

Introduction

An important aspect of mobile web apps is to maintain application preferences across sessions. An easy way to implement this would be to save user preferences in a cookie. However this presents some problems, since cookie data will be transmitted back and forth with each http request, adding overhead, latency and possibly costs for the user. In this article, we will look at the HTML5 LocalStorage implementation as a method we can use to persist user preferences without these undesirable side effects.

HTML5 Local Storage

HTML5 LocalStorage is a simple key / value storage API which allows us to persist application data cross sessions. It is widely supported in both desktop and mobile browsers. since it's supported by webkit, Android, iOS and Blackberry stock browsers support it fully. It is not supported in Opera Mobile (v 10.0). (You can easily write a cookie fallback method for this).

Using localStorage is very simple. There are very few components to the API:

getItem(key)		
setItem(key, value)
removeItem(key)
clear()
		

We can detect the availability of localStorage by checking for the presence of the window.localStorage property. We will the simply store a a key / value pair using a JSON representation of our preference set as our value.

The Code

Let's start by creating a UserPrefs object, and implementing simple Save and Load methods

UserPrefs = {
	
	/* These are our preferences */
	prefs: {
		zoom: 3,
		center: new deCarta.Mobile.Position(0,0)
	},

	/* Load the preferences */
	load: function(){		
		if (!window.localStorage){
			return;
		}
		this.prefs = JSON.parse(localStorage.getItem('Prefs'));					
			
	},

	/* Save the prefs. */
	save: function(){                        
		if (!window.localStorage){
			return;
		}                        
		localStorage.setItem('Prefs', JSON.stringify(this.prefs));						
	}
}
		

This works as a basic implementation. We can update our preferences by calling, for example

UserPrefs.prefs.zoom = 5;
UserPrefs.save();		
		

However, we can improve our implementation drastically by making use of Getters and Setters. Originally implemented by Firefox, this JS extension is currently available on all webkit-based mobile browsers, and Opera Mobile.

Getters and setters allow us to dynamically define getter and setter functions on an object. In this case, we'll use them to dynamically generate getter and setter functions for each of our "prefs" properties.

We can use something like this to iterate over the preferences and create custom getters and setters for each one:

//create the getters and setters. 
for (var pref in this.prefs){							
	if (this.prefs.hasOwnProperty(pref)){															
		with ({p : pref }){
			this.__defineGetter__(p, function(){
				return this.prefs[p];
			});
			this.__defineSetter__(p, function(v){
				this.prefs[p] = v;	
				this.save();
			});
		}
	}
}
		

Note that we are also saving the new values to local storage every time we set them. This might not be the most efficient method, but trying to detect when a user closes our app is very unreliable, and in this case the limited inefficiency is a tradeoff we can afford thanks to the improved user experience.

Let's take a look at the complete code for our UserPrefs object:

/**
* Creating a User Preferences storage with LocalStorage
*/
UserPrefs = {
	
	/* These are our preferences */
	prefs: {
		zoom: 3,
		center: new deCarta.Mobile.Position(0,0)
	},

	/* Load the preferences, and initialize*/
	load: function(){
		
		if (!window.localStorage){
			return;
		}
		
		var storedPrefs = JSON.parse(localStorage.getItem('SeismsPrefs'));      					    
		this.prefs = deCarta.Mobile.Utilities.extendObject( this.prefs, storedPrefs || {}); 						
		
		//create the getters and setters. 
		for (var pref in this.prefs){							
			if (this.prefs.hasOwnProperty(pref)){															
				with ({p : pref }){
					this.__defineGetter__(p, function(){
						return this.prefs[p];
					});
					this.__defineSetter__(p, function(v){
						this.prefs[p] = v;	
						this.save();
					});
				}
			}
		}
		
	},

	/* Save the prefs. */
	save: function(){                        
		if (!window.localStorage){
			return;
		}                        
		localStorage.setItem('SeismsPrefs', JSON.stringify(this.prefs));						
	}
}		

You will notice the use of the deCarta.Mobile.Utilities.extendObject function. What we achieve with it is to extend the default preferences set with any values we have stored in local storage. Basically, we override the default preferences with our stored ones (if we have them) ensuring that we always have a preference set available.

Now that we have a storage mechanism in place for our preferences, we need to integrate this in our application. We can load our preference set as the first step in our application, and apply the loaded center and zoom to our map constructor:

		
window.onload = function(){
	
	UserPrefs.load();

	/* Omitted resizing code */

	new deCarta.Mobile.Map({
		id: "mapContainer",
		zoom: UserPrefs.zoom,
		center: UserPrefs.center
	});  
}		

We also need to detect zoom and position changes, so that we can store the new values. The deCarta API provides an EventManager object which makes this extremely simple:

			
deCarta.Mobile.EventManager.listen('zoomend', function(e){							
	UserPrefs.center = e.map.center;
	UserPrefs.zoom = e.zoom;
});
deCarta.Mobile.EventManager.listen('moveend', function(e){
	UserPrefs.center = e.center;
});		

We attach listeners to both the zoomEnd and the moveEnd events. These will be invoked evey time the map zoom or center position change. Note that since save() is called implicitly, all we need to do is set the property.

We are now ready to try this out in a mobile web browser! You'll need to upload this to a web server to see it work, since localStorage does not allow local file sources for security reasons.

Complete code for this article

	
<!doctype html>
<html>
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width; height=device-height; initial-scale=1.0; 
        maximum-scale=1.0; minimum-scale=1.0; user-scalable=0;" >
		<!-- Eliminate url and button bars if added to home screen -->
		<meta name="apple-mobile-web-app-capable" content="yes" >
		<!-- Choose how to handle the phone status bar -->
		<meta names="apple-mobile-web-app-status-bar-style" content="black-translucent" >

		<title>Simple Mobile Map</title>

		<style>
		* {
			margin: 0; 
			padding: 0;
		}
		#mapContainer{
			overflow:hidden
			position: absolute; 
			top: 0; 
			left: 0;
		}
		</style>
	
	</head>
	<body>        
		<div id="mapContainer"></div>							
		<script type="text/javascript" src="deCartaMob.min.js"></script>
		<script type="text/javascript">
			deCarta.Mobile.Configuration.clientName = 'map-sample-app';
			deCarta.Mobile.Configuration.clientPassword = 'letmein';
			
			window.onload = function(){
				
				UserPrefs.load();
				
				deCarta.Mobile.EventManager.listen('zoomend', function(e){							
					UserPrefs.center = e.map.center;
					UserPrefs.zoom = e.zoom;
				});
				deCarta.Mobile.EventManager.listen('moveend', function(e){
					UserPrefs.center = e.center;
				});				
			
				var size = deCarta.Window.getViewport();				
			
				document.body.style.width = size.width + 'px';
				document.body.style.height =  size.height + 'px';

				document.getElementById('mapContainer').style.width = size.width + 'px';
				document.getElementById('mapContainer').style.height =  size.height + 'px';			
			
				new deCarta.Mobile.Map({
					id: "mapContainer",
					zoom: UserPrefs.zoom,
					center: UserPrefs.center
				});  
			}
			
			/**
			* Creating a User Preferences storage with LocalStorage
			*/
			UserPrefs = {
				
				/* These are our preferences */
				prefs: {
					zoom: 3,
					center: new deCarta.Mobile.Position(0,0)
				},

				/* Load the preferences, and initialize*/
				load: function(){
					
					if (!window.localStorage){
						return;
					}
					
					var storedPrefs = JSON.parse(localStorage.getItem('SeismsPrefs'));      					    
					this.prefs = deCarta.Mobile.Utilities.extendObject( this.prefs, storedPrefs || {}); 						
					
					//create the getters and setters. 
					for (var pref in this.prefs){							
						if (this.prefs.hasOwnProperty(pref)){															
							with ({p : pref }){
								this.__defineGetter__(p, function(){
									return this.prefs[p];
								});
								this.__defineSetter__(p, function(v){
									this.prefs[p] = v;	
									this.save();
								});
							}
						}
					}
					
				},

				/* Save the prefs. */
				save: function(){                        
					if (!window.localStorage){
						return;
					}                        
					localStorage.setItem('SeismsPrefs', JSON.stringify(this.prefs));						
				}
			}			
		</script>      
	</body>    
</html>