← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 10741: wip, added new storage system with 4 adapters: indexeddb, localstorage, sessionstorage, memorysto...

 

------------------------------------------------------------
revno: 10741
committer: Morten Olav Hansen <mortenoh@xxxxxxxxx>
branch nick: dhis2
timestamp: Thu 2013-05-02 20:55:39 +0700
message:
  wip, added new storage system with 4 adapters: indexeddb, localstorage, sessionstorage, memorystorage. Updated offline anonymous to use new storage system. IndexedDB adapter still needs work.
removed:
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.dom-ss.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.dom.js
added:
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.ls.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.memory.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.ss.js
modified:
  dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/cacheManifest.vm
  dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/javascript/anonymousRegistration.js
  dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/javascript/dhis2.storage.anonymous.js
  dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/javascript/entry.js
  dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/jsonProgramMetaData.vm
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/cacheManifest.vm
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.sharing.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.idb.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/main.vm


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/cacheManifest.vm'
--- dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/cacheManifest.vm	2013-04-22 04:16:31 +0000
+++ dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/cacheManifest.vm	2013-05-02 13:55:39 +0000
@@ -39,9 +39,11 @@
 ../dhis-web-commons/javascripts/dhis2/dhis2.select.js
 ../dhis-web-commons/javascripts/dhis2/dhis2.comparator.js
 ../dhis-web-commons/javascripts/dhis2/dhis2.availability.js
+../dhis-web-commons/javascripts/dhis2/dhis2.storage.ls.js
+../dhis-web-commons/javascripts/dhis2/dhis2.storage.ss.js
+../dhis-web-commons/javascripts/dhis2/dhis2.storage.idb.js
+../dhis-web-commons/javascripts/dhis2/dhis2.storage.memory.js
 ../dhis-web-commons/javascripts/dhis2/dhis2.storage.js
-../dhis-web-commons/javascripts/dhis2/dhis2.storage.dom.js
-../dhis-web-commons/javascripts/dhis2/dhis2.storage.dom-ss.js
 ../dhis-web-commons/javascripts/jQuery/jquery.cookie.js
 ../dhis-web-commons/javascripts/jQuery/jquery.validate.js
 ../dhis-web-commons/javascripts/jQuery/jquery.validate.ext.js

=== modified file 'dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/javascript/anonymousRegistration.js'
--- dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/javascript/anonymousRegistration.js	2013-04-26 06:47:09 +0000
+++ dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/javascript/anonymousRegistration.js	2013-05-02 13:55:39 +0000
@@ -1,74 +1,134 @@
 var DAO = DAO || {};
 
-var PROGRAMS_STORE = 'anonymousPrograms';
-var PROGRAM_STAGES_STORE = 'anonymousProgramStages';
-var OPTION_SET_STORE = 'optionSets';
-var USERNAME_STORE = 'usernames';
-var OFFLINE_DATA_STORE = 'anonymousOfflineData';
-
-function initalizeProgramStages() {
-    DAO.programStages = new dhis2.storage.Store( {name: PROGRAM_STAGES_STORE, adapter: 'dom-ss'}, function(store) {
-        $( document ).trigger('dhis2.anonymous.programStagesInitialized');
-    });
-}
-
-function initializePrograms() {
-    DAO.programs = new dhis2.storage.Store( {name: PROGRAMS_STORE, adapter: 'dom-ss'}, function ( store ) {
-        jQuery.getJSON( "getProgramMetaData.action", {},function ( data ) {
-            var keys = _.keys( data.metaData.programs );
-            var objs = _.values( data.metaData.programs );
-
-            DAO.programs.addAll( keys, objs, function ( store ) {
-                var deferred = $.Deferred();
-                var promise = deferred.promise();
-
-                _.each( _.values(data.metaData.programs), function(el, idx) {
-                    var psid = el.programStages[0].id;
-
-                    promise = promise.pipe(function () {
-                        return loadProgramStage(psid);
-                    });
-                });
-
-                promise = promise.pipe(function() {
-                    loadOptionSets( data.metaData.optionSets, data.metaData.usernames );
-                });
-
-                deferred.resolve();
-
-                selection.setListenerFunction( organisationUnitSelected );
-                $( document ).trigger('dhis2.anonymous.programsInitialized');
+DAO.metaData = new dhis2.storage.Store( {
+    name: 'dhis2',
+    adapters: [ dhis2.storage.DomSessionStorageAdapter, dhis2.storage.InMemoryAdapter ],
+    objectStores: [ 'programs', 'programStages', 'optionSets', 'usernames' ]
+} );
+
+DAO.offlineData = new dhis2.storage.Store( {
+    name: 'dhis2',
+    adapters: [ dhis2.storage.DomLocalStorageAdapter, dhis2.storage.InMemoryAdapter ],
+    objectStores: [ 'dataValues' ]
+} );
+
+DAO.metaData.open();
+DAO.offlineData.open();
+
+function loadPrograms() {
+    var def = $.Deferred();
+
+    $.ajax({
+        url: 'getProgramMetaData.action',
+        dataType: 'json'
+    } ).done(function(data) {
+        var programs = _.values( data.metaData.programs );
+        DAO.metaData.setAll('programs', programs ).then(function() {
+            def.resolve(data.metaData);
+        });
+    } ).always(function() {
+        def.resolve();
+    });
+
+    return def.promise();
+}
+
+function loadProgramStages( metaData ) {
+    if(!metaData) {
+        return;
+    }
+
+    var deferred1 = $.Deferred();
+    var deferred2 = $.Deferred();
+    var promise = deferred2.promise();
+
+    _.each( _.values(metaData.programs), function(el, idx) {
+        var psid = el.programStages[0].id;
+        var data = createProgramStage(psid);
+
+        promise = promise.then(function() {
+            return $.ajax( {
+                url: 'dataentryform.action',
+                data: data,
+                dataType: 'html'
+            } ).done(function(data) {
+                var obj = {};
+                obj.id = psid;
+                obj.form = data;
+                DAO.metaData.set('programStages', obj);
+            });
+        });
+    });
+
+    promise = promise.then(function() {
+        deferred1.resolve( metaData );
+    });
+
+    deferred2.resolve();
+
+    return deferred1.promise();
+}
+
+function loadOptionSets(metaData) {
+    if(!metaData) {
+        return;
+    }
+
+    var deferred1 = $.Deferred();
+    var deferred2 = $.Deferred();
+    var promise = deferred2.promise();
+
+    _.each( metaData.optionSets, function ( item, idx ) {
+        promise = promise.then( function () {
+            return $.ajax( {
+                url: 'getOptionSet.action?dataElementUid=' + item,
+                dataType: 'json'
+            } ).done( function ( data ) {
+                var obj = {};
+                obj.id = item;
+                obj.optionSet = data.optionSet;
+                DAO.metaData.set('optionSets', obj);
             } );
-        } ).fail( function () {
-            DAO.optionSets = new dhis2.storage.Store( {name: OPTION_SET_STORE, adapter: 'dom-ss'}, function() {} );
-            DAO.usernames = new dhis2.storage.Store( {name: USERNAME_STORE, adapter: 'dom-ss'}, function() {} );
-
-            selection.setListenerFunction( organisationUnitSelected );
-            $( document ).trigger('dhis2.anonymous.programsInitialized');
         } );
     } );
-}
-
-function initializeOfflineData() {
-    DAO.offlineData = new dhis2.storage.Store( {name: OFFLINE_DATA_STORE, adapter: 'dom'}, function(store) {
-        $( document ).trigger('dhis2.anonymous.offlineData');
+
+    if ( metaData.usernames ) {
+        promise = promise.then( function() {
+            return $.ajax( {
+                url: 'getUsernames.action',
+                dataType: 'json'
+            } ).done( function (data) {
+                var obj = {};
+                obj.id = 'usernames';
+                obj.usernames = data.usernames;
+                DAO.metaData.set('usernames', obj);
+            } )
+        } );
+    }
+
+    promise = promise.then(function() {
+        deferred1.resolve( metaData );
     });
+
+    deferred2.resolve();
+
+    return deferred1.promise();
 }
 
 function updateOfflineEvents() {
-    DAO.offlineData.fetchAll(function(store, arr) {
+    var no_offline_template = $( '#no-offline-event-template' );
+    var no_offline_template_compiled = _.template( no_offline_template.html() );
+
+    var offline_template = $( '#offline-event-template' );
+    var offline_template_compiled = _.template( offline_template.html() );
+
+    return DAO.offlineData.getAll( 'dataValues' ).done( function ( arr ) {
         var orgUnitId = selection.getSelected();
         var programId = $('#programId').val();
 
         var target = $( '#offlineEventList' );
         target.children().remove();
 
-        var no_offline_template = $( '#no-offline-event-template' );
-        var no_offline_template_compiled = _.template( no_offline_template.html() );
-
-        var offline_template = $( '#offline-event-template' );
-        var offline_template_compiled = _.template( offline_template.html() );
-
         if ( arr.length > 0 ) {
             var matched = false;
 
@@ -89,9 +149,7 @@
         } else {
             target.append( no_offline_template_compiled() );
         }
-
-        $( document ).trigger('dhis2.anonymous.checkOfflineEvents');
-    });
+    } );
 }
 
 function showOfflineEvents() {
@@ -105,12 +163,10 @@
 var haveLocalData = false;
 
 function checkOfflineData(callback) {
-    DAO.offlineData.fetchAll( function ( store, arr ) {
+    return DAO.offlineData.getAll( 'dataValues' ).done( function ( arr ) {
         haveLocalData = arr.length > 0;
-        $( document ).trigger('dhis2.anonymous.checkOfflineData');
-
         if(callback && typeof callback == 'function') callback(haveLocalData);
-    } );
+    });
 }
 
 function uploadOfflineData( item ) {
@@ -120,7 +176,7 @@
         data: JSON.stringify( item )
     } ).done(function(json) {
         if ( json.response == 'success' ) {
-            DAO.offlineData.remove( item.key, function ( store ) {
+            DAO.offlineData.delete( 'dataValues', item.id ).done( function () {
                 updateOfflineEvents();
                 searchEvents( eval( getFieldValue( 'listAll' ) ) );
             } );
@@ -131,7 +187,7 @@
 function uploadLocalData() {
     setHeaderWaitMessage( i18n_uploading_data_notification );
 
-    DAO.offlineData.fetchAll( function ( store, arr ) {
+    DAO.offlineData.getAll( 'dataValues' ).done( function ( arr ) {
         if(arr.length == 0) {
             setHeaderDelayMessage( i18n_sync_success );
             return;
@@ -151,7 +207,7 @@
         });
 
         deferred.resolve();
-    } );
+    });
 }
 
 function sync_failed_button() {
@@ -170,16 +226,21 @@
     } );
 
     $( "#orgUnitTree" ).one( "ouwtLoaded", function () {
-        $( document ).one( 'dhis2.anonymous.programStagesInitialized', initializePrograms );
-        $( document ).one( 'dhis2.anonymous.programsInitialized', updateOfflineEvents );
-        $( document ).one( 'dhis2.anonymous.checkOfflineEvents', checkOfflineData );
-        $( document ).one( 'dhis2.anonymous.checkOfflineData', function () {
+        var def = $.Deferred();
+        var promise = def.promise();
+        promise = promise.then(loadPrograms);
+        promise = promise.then(loadProgramStages);
+        promise = promise.then(loadOptionSets);
+        promise = promise.then(updateOfflineEvents);
+        promise = promise.then(checkOfflineData);
+        promise = promise.then(function() {
+            selection.setListenerFunction( organisationUnitSelected );
+
             dhis2.availability.startAvailabilityCheck();
             selection.responseReceived();
-        } );
+        });
 
-        initalizeProgramStages();
-        initializeOfflineData();
+        def.resolve();
     } );
 
     $( document ).bind( 'dhis2.online', function ( event, loggedIn ) {
@@ -292,7 +353,7 @@
     hideById( 'listDiv' );
     hideById( 'dataEntryInfor' );
 
-    DAO.programs.fetchAll( function ( store, arr ) {
+    DAO.metaData.getAll( 'programs' ).done( function (arr) {
         var programs = [];
 
         $.each( arr, function ( idx, item ) {
@@ -317,7 +378,7 @@
 
     for ( var i = 0; i < arr.length; i++ ) {
         jQuery( '#programId' ).append(
-            '<option value="' + arr[i].key
+            '<option value="' + arr[i].id
             + '" puid="' + arr[i].uid
             + '" programType="' + arr[i].type
             + '" psid="' + arr[i].programStages[0].id
@@ -747,10 +808,9 @@
 
     if( s.indexOf("local") != -1) {
         if ( confirm( i18n_comfirm_delete_event ) ) {
-            DAO.offlineData.remove(programStageId, function(store) {
-                // redisplay list
+            DAO.offlineData.delete( 'dataValues', programStageId ).always( function () {
                 updateOfflineEvents();
-            });
+            } );
         }
     } else {
         removeItem( programStageId, '', i18n_comfirm_delete_event, 'removeCurrentEncounter.action' );
@@ -910,7 +970,7 @@
                 }
             } ).fail( function () {
                 if(programStageInstanceId == 0) {
-                    DAO.offlineData.keys(function(store, keys) {
+                    DAO.offlineData.getKeys( 'dataValues' ).done( function ( keys ) {
                         var i = 100;
 
                         for(; i<10000; i++) {
@@ -924,10 +984,11 @@
                         showUpdateEvent( programStageInstanceId );
 
                         var data = {};
+                        data.id = programStageInstanceId;
                         data.executionDate = createExecutionDate(programId, programStageInstanceId, executionDate, organisationUnitId);
                         data.executionDate.completed = false;
 
-                        DAO.offlineData.add(programStageInstanceId, data);
+                        this.set( 'dataValues', data );
                     });
                 } else {
                     // if we have a programStageInstanceId, just reuse that one
@@ -1003,65 +1064,35 @@
     return data;
 }
 
+function createProgramStage( programStageId, programStageInstanceId, organisationUnitId ) {
+    var data = {};
+
+    if(programStageId)
+        data.programStageId = programStageId;
+
+    if(programStageInstanceId)
+        data.programStageInstanceId = programStageInstanceId;
+
+    if(organisationUnitId)
+        data.organisationUnitId = organisationUnitId;
+
+    return data;
+}
+
 function loadProgramStage( programStageId, programStageInstanceId, organisationUnitId, success, fail ) {
-    DAO.programStages.fetch(programStageId, function(store, arr) {
-        if ( arr.length > 0 ) {
-            if(success) success(arr[0]);
-        } else {
-            var data = {};
-
-            if(programStageId)
-                data.programStageId = programStageId;
-
-            if(programStageInstanceId)
-                data.programStageInstanceId = programStageInstanceId;
-
-            if(organisationUnitId)
-                data.organisationUnitId = organisationUnitId;
-
-            $.ajax( {
-                url: 'dataentryform.action',
-                data: data,
-                dataType: 'html'
-            } ).done(function(data) {
-                DAO.programStages.add(programStageId, data);
-                if(success) success(data);
-            } ).fail(function() {
-                if(fail) fail();
-            });
-        }
+    var data = createProgramStage( programStageId, programStageInstanceId, organisationUnitId );
+
+    DAO.metaData.get('programStages', programStageId ).done(function(obj) {
+        if(success) success(obj.form);
+    } ).fail(function() {
+        $.ajax( {
+            url: 'dataentryform.action',
+            data: data,
+            dataType: 'html'
+        } ).done(function(data) {
+            if(success) success(data);
+        } ).fail(function() {
+            if(fail) fail();
+        });
     });
 }
-
-function loadOptionSets(uids, usernames, success ) {
-    DAO.optionSets = new dhis2.storage.Store( {name: OPTION_SET_STORE, adapter: 'dom-ss'}, function ( store ) {
-        DAO.usernames = new dhis2.storage.Store( {name: USERNAME_STORE, adapter: 'dom-ss'}, function ( store ) {
-            var deferred = $.Deferred();
-            var promise = deferred.promise();
-
-            _.each( uids, function(item, idx) {
-                promise = promise.pipe( $.ajax( {
-                    url: 'getOptionSet.action?dataElementUid=' + item,
-                    dataType: 'json',
-                    success: function ( json ) {
-                        DAO.optionSets.add( item, json );
-                        if ( success && typeof success == 'function' ) success( json );
-                    }
-                } ) );
-            });
-
-            if ( usernames ) {
-                promise = promise.pipe( $.ajax( {
-                    url: 'getUsernames.action',
-                    dataType: 'json',
-                    success: function ( json ) {
-                        DAO.usernames.add( 'usernames', json.usernames );
-                        if ( success && typeof success == 'function' ) success( json );
-                    }
-                } ) );
-            }
-
-            deferred.resolve();
-        });
-    } );
-}

=== modified file 'dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/javascript/dhis2.storage.anonymous.js'
--- dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/javascript/dhis2.storage.anonymous.js	2013-04-08 04:57:19 +0000
+++ dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/javascript/dhis2.storage.anonymous.js	2013-05-02 13:55:39 +0000
@@ -1,3 +1,4 @@
+/*
 dhis2.storage.Store.plugin( 'online-anonymous-programs', (function () {
     return {
         call: function ( args, success, failure ) {
@@ -28,3 +29,4 @@
         }
     };
 })() );
+*/

=== modified file 'dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/javascript/entry.js'
--- dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/javascript/entry.js	2013-04-24 06:11:42 +0000
+++ dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/javascript/entry.js	2013-05-02 13:55:39 +0000
@@ -297,22 +297,21 @@
             var dataValueKey = $( '#programStageInstanceId' ).val();
             var key = dataElementUid;
 
-            DAO.offlineData.fetch( dataValueKey, function ( store, arr ) {
-                if ( arr.length == 0 ) {
+            DAO.offlineData.get( 'dataValues', dataValueKey ).done( function ( obj ) {
+                if ( !obj ) {
                     markValue( ERROR );
                     window.alert( i18n_saving_value_failed_error_code + '\n\n' + errorCode );
                     return;
                 }
 
-                var obj = arr[0];
-
                 if ( !obj.values ) {
                     obj.values = {};
                 }
 
+                obj.id = dataValueKey;
                 obj.values[key] = data;
 
-                store.add( dataValueKey, obj );
+                this.set( 'dataValues', obj );
                 markValue( resultColor );
             } );
         } else {
@@ -598,12 +597,13 @@
                         jQuery(".stage-object-selected").css('border-color', COLOR_GREEN);
                         jQuery(".stage-object-selected").css('background-color', COLOR_LIGHT_GREEN);
 
-                        DAO.offlineData.fetch( programStageInstanceId, function ( store, arr ) {
-                            if ( arr.length > 0 ) {
-                                var obj = arr[0];
-                                obj.executionDate.completed = true;
-                                DAO.offlineData.add( programStageInstanceId, obj );
+                        DAO.offlineData.get( 'dataValues', programStageInstanceId ).done( function ( obj ) {
+                            if ( !obj ) {
+                                return;
                             }
+
+                            obj.executionDate.completed = true;
+                            DAO.offlineData.set('dataValues', obj);
                         } );
 
                         var blocked = jQuery('#entryFormContainer [id=blockEntryForm]').val();
@@ -645,12 +645,13 @@
                 var programStageInstanceId = getFieldValue( 'programStageInstanceId' );
 
                 if ( window.DAO && window.DAO.offlineData ) {
-                    DAO.offlineData.fetch( programStageInstanceId, function ( store, arr ) {
-                        if ( arr.length > 0 ) {
-                            var obj = arr[0];
-                            obj.executionDate.completed = false;
-                            DAO.offlineData.add( programStageInstanceId, obj );
+                    DAO.offlineData.get( 'dataValues', programStageInstanceId ).done( function ( obj ) {
+                        if(!obj) {
+                            return;
                         }
+
+                        obj.executionDate.completed = false;
+                        DAO.offlineData.set( 'dataValues', obj );
                     } );
                 }
 
@@ -723,27 +724,23 @@
         $( "#programStageInstanceId" ).val( programStageInstanceId );
         $( "#entryFormContainer input[id='programStageInstanceId']" ).val( programStageInstanceId );
 
-        DAO.offlineData.fetch(programStageInstanceId, function(store, arr) {
-            if(arr.length > 0 ) {
-                var obj = arr[0];
-
-                if(obj.values !== undefined ) {
-                    _.each( _.keys(obj.values), function(key, idx) {
-                        var fieldId = getProgramStageUid() + '-' + key + '-val';
-                        var field = $('#' + fieldId);
-
-                        if ( field ) {
-                            field.val( decodeURI( obj.values[key].value ) );
-                        }
-                    });
-                }
+        DAO.offlineData.get( 'dataValues', programStageInstanceId ).done( function ( obj ) {
+            if(obj && obj.values !== undefined ) {
+                _.each( _.keys(obj.values), function(key, idx) {
+                    var fieldId = getProgramStageUid() + '-' + key + '-val';
+                    var field = $('#' + fieldId);
+
+                    if ( field ) {
+                        field.val( decodeURI( obj.values[key].value ) );
+                    }
+                });
             }
 
             if( always ) always();
 
             $('#commentInput').attr('disabled', true);
             $('#validateBtn').attr('disabled', true);
-        });
+        } );
     } else {
         return $.ajax({
             url: 'getProgramStageInstance.action',
@@ -907,10 +904,9 @@
 }
 
 function searchOptionSet( uid, query, success ) {
-    if(window.DAO !== undefined && window.DAO.optionSets !== undefined ) {
-        DAO.optionSets.fetch(uid, function(store, arr) {
-            if ( arr.length > 0 ) {
-                var obj = arr[0];
+    if(window.DAO !== undefined && window.DAO.metaData !== undefined ) {
+        DAO.metaData.get( 'optionSets', uid ).done( function ( obj ) {
+            if(obj) {
                 var options = [];
 
                 if(query == null || query == "") {

=== modified file 'dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/jsonProgramMetaData.vm'
--- dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/jsonProgramMetaData.vm	2013-04-24 08:54:41 +0000
+++ dhis-2/dhis-web/dhis-web-caseentry/src/main/webapp/dhis-web-caseentry/jsonProgramMetaData.vm	2013-05-02 13:55:39 +0000
@@ -5,6 +5,7 @@
 #set( $psize = $programs.size() )
 #foreach( $program in $programs )
 "${program.id}":{
+    "id":"${program.id}",
     "name":"$encoder.jsonEncode( ${program.displayName} )",
     "uid":"$encoder.jsonEncode( ${program.uid} )",
     "description":"$encoder.jsonEncode( ${program.description} )",

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/cacheManifest.vm'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/cacheManifest.vm	2013-03-22 13:47:40 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/cacheManifest.vm	2013-05-02 13:55:39 +0000
@@ -26,10 +26,11 @@
 javascripts/dhis2/dhis2.select.js
 javascripts/dhis2/dhis2.comparator.js
 javascripts/dhis2/dhis2.availability.js
+javascripts/dhis2/dhis2.storage.memory.js
+javascripts/dhis2/dhis2.storage.ss.js
+javascripts/dhis2/dhis2.storage.ls.js
+javascripts/dhis2/dhis2.storage.idb.js
 javascripts/dhis2/dhis2.storage.js
-javascripts/dhis2/dhis2.storage.dom-ss.js
-javascripts/dhis2/dhis2.storage.dom.js
-javascripts/dhis2/dhis2.storage.idb.js
 javascripts/jQuery/jquery.cookie.js
 ../main.js
 ouwt/ouwt.js 

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.sharing.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.sharing.js	2013-03-13 15:24:18 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.sharing.js	2013-05-02 13:55:39 +0000
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2004-2013, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the HISP project nor the names of its contributors may
+ *   be used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+dhis2.util.namespace( 'dhis2.sharing' );
 
 function loadSharingSettings( type, uid ) {
     return $.ajax( {
@@ -45,8 +73,8 @@
 }
 
 function removeUserGroupAccess(e) {
+    e.preventDefault();
     $( this ).parent().parent().remove();
-    e.preventDefault();
 }
 
 function clearUserGroupAccesses() {
@@ -111,7 +139,7 @@
             $( '#sharingPublicAccess' ).attr( 'disabled', true );
         }
 
-        $( '.removeUserGroupAccess' ).unbind( 'click' )
+        $( '.removeUserGroupAccess' ).unbind( 'click' );
         $( document ).on( 'click', '.removeUserGroupAccess', removeUserGroupAccess );
         $( '#addUserGroupAccess' ).unbind( 'click' ).bind( 'click', addUserGroupAccessSelectedItem );
 

=== removed file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.dom-ss.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.dom-ss.js	2013-04-04 07:57:59 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.dom-ss.js	1970-01-01 00:00:00 +0000
@@ -1,158 +0,0 @@
-// dom storage support (sessionStorage)
-dhis2.storage.Store.adapter( 'dom-ss', (function () {
-    var storage = window.sessionStorage;
-
-    var indexer = function ( dbname, name ) {
-        return {
-            key: dbname + '.' + name + '.__index__',
-
-            all: function () {
-                var a = storage.getItem( this.key );
-
-                if ( a ) {
-                    try {
-                        a = JSON.parse( a );
-                    } catch ( e ) {
-                        a = null;
-                    }
-                }
-
-                if ( a == null ) {
-                    storage.setItem( this.key, JSON.stringify( [] ) );
-                }
-
-                return JSON.parse( storage.getItem( this.key ) );
-            },
-
-            add: function ( key ) {
-                var a = this.all();
-                a.push( key );
-                storage.setItem( this.key, JSON.stringify( a ) );
-            },
-
-            remove: function ( key ) {
-                var a = this.all();
-
-                if ( a.indexOf( key ) != -1 ) {
-                    dhis2.array.remove( a, a.indexOf( key ), a.indexOf( key ) );
-                    storage.setItem( this.key, JSON.stringify( a ) );
-                }
-            },
-
-            find: function ( key ) {
-                var a = this.all();
-                return a.indexOf( key );
-            }
-        }
-    }
-
-    return {
-        valid: function () {
-            return !!storage;
-        },
-
-        init: function ( options, callback ) {
-            this.indexer = indexer( this.dbname, this.name );
-            if ( callback ) callback.call( this, this, options );
-        },
-
-        add: function ( key, obj, callback ) {
-            var key = this.dbname + '.' + this.name + '.' + key;
-            if ( this.indexer.find( key ) == -1 ) this.indexer.add( key );
-            storage.setItem( key, JSON.stringify( obj ) );
-            if ( callback ) callback.call( this, this, obj );
-
-            return this;
-        },
-
-        addAll: function ( keys, objs, callback ) {
-            var that = this;
-
-            while ( keys.length != 0 ) {
-                var key = keys.pop();
-                var obj = objs.pop();
-
-                try {
-                    that.add( key, obj );
-                } catch ( e ) {
-                    break;
-                }
-            }
-
-            if ( callback ) callback.call( that, that );
-        },
-
-        remove: function ( key, callback ) {
-            var key = this.dbname + '.' + this.name + '.' + key;
-            this.indexer.remove( key );
-            storage.removeItem( key );
-            if ( callback ) callback.call( this, this );
-
-            return this;
-        },
-
-        exists: function ( key, callback ) {
-            var key = this.dbname + '.' + this.name + '.' + key;
-            var success = storage.getItem( key ) != null;
-            if ( callback ) callback.call( this, this, success );
-
-            return this;
-        },
-
-        keys: function ( callback ) {
-            var that = this;
-            var keys = this.indexer.all().map( function ( r ) {
-                return r.replace( that.dbname + '.' + that.name + '.', '' )
-            } );
-
-            if ( callback ) callback.call( this, this, keys );
-
-            return this;
-        },
-
-        fetch: function ( key, callback ) {
-            var arr = [];
-            var keys = $.isArray( key ) ? key : [ key ];
-
-            for ( var k = 0; k < keys.length; k++ ) {
-                var storage_key = this.dbname + '.' + this.name + '.' + keys[k];
-                var obj = storage.getItem( storage_key );
-
-                if ( obj ) {
-                    obj = JSON.parse( obj );
-                    obj.key = keys[k];
-                    arr.push( obj );
-                }
-            }
-
-            if ( callback ) callback.call( this, this, arr );
-
-            return this;
-        },
-
-        fetchAll: function ( callback ) {
-            var arr = [];
-            var idx = this.indexer.all();
-
-            for ( var k = 0; k < idx.length; k++ ) {
-                var obj = JSON.parse( storage.getItem( idx[k] ) );
-                obj.key = idx[k].replace( this.dbname + '.' + this.name + '.', '' );
-                arr.push( obj );
-            }
-
-            if ( callback ) callback.call( this, this, arr );
-
-            return this;
-        },
-
-        destroy: function () {
-            this.keys( function ( store, keys ) {
-                for ( var key in keys ) {
-                    this.remove( key );
-                }
-            } );
-
-            storage.removeItem( this.dbname + '.' + this.name + '.__index__' );
-        }
-    };
-})() );

=== removed file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.dom.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.dom.js	2013-04-04 07:57:59 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.dom.js	1970-01-01 00:00:00 +0000
@@ -1,158 +0,0 @@
-// dom storage support (sessionStorage)
-dhis2.storage.Store.adapter( 'dom', (function () {
-    var storage = window.localStorage;
-
-    var indexer = function ( dbname, name ) {
-        return {
-            key: dbname + '.' + name + '.__index__',
-
-            all: function () {
-                var a = storage.getItem( this.key );
-
-                if ( a ) {
-                    try {
-                        a = JSON.parse( a );
-                    } catch ( e ) {
-                        a = null;
-                    }
-                }
-
-                if ( a == null ) {
-                    storage.setItem( this.key, JSON.stringify( [] ) );
-                }
-
-                return JSON.parse( storage.getItem( this.key ) );
-            },
-
-            add: function ( key ) {
-                var a = this.all();
-                a.push( key );
-                storage.setItem( this.key, JSON.stringify( a ) );
-            },
-
-            remove: function ( key ) {
-                var a = this.all();
-
-                if ( a.indexOf( key ) != -1 ) {
-                    dhis2.array.remove( a, a.indexOf( key ), a.indexOf( key ) );
-                    storage.setItem( this.key, JSON.stringify( a ) );
-                }
-            },
-
-            find: function ( key ) {
-                var a = this.all();
-                return a.indexOf( key );
-            }
-        }
-    }
-
-    return {
-        valid: function () {
-            return !!storage;
-        },
-
-        init: function ( options, callback ) {
-            this.indexer = indexer( this.dbname, this.name );
-            if ( callback ) callback.call( this, this, options );
-        },
-
-        add: function ( key, obj, callback ) {
-            var key = this.dbname + '.' + this.name + '.' + key;
-            if ( this.indexer.find( key ) == -1 ) this.indexer.add( key );
-            storage.setItem( key, JSON.stringify( obj ) );
-            if ( callback ) callback.call( this, this, obj );
-
-            return this;
-        },
-
-        addAll: function ( keys, objs, callback ) {
-            var that = this;
-
-            while ( keys.length != 0 ) {
-                var key = keys.pop();
-                var obj = objs.pop();
-
-                try {
-                    that.add( key, obj );
-                } catch ( e ) {
-                    break;
-                }
-            }
-
-            if ( callback ) callback.call( that, that );
-        },
-
-        remove: function ( key, callback ) {
-            var key = this.dbname + '.' + this.name + '.' + key;
-            this.indexer.remove( key );
-            storage.removeItem( key );
-            if ( callback ) callback.call( this, this );
-
-            return this;
-        },
-
-        exists: function ( key, callback ) {
-            var key = this.dbname + '.' + this.name + '.' + key;
-            var success = storage.getItem( key ) != null;
-            if ( callback ) callback.call( this, this, success );
-
-            return this;
-        },
-
-        keys: function ( callback ) {
-            var that = this;
-            var keys = this.indexer.all().map( function ( r ) {
-                return r.replace( that.dbname + '.' + that.name + '.', '' )
-            } );
-
-            if ( callback ) callback.call( this, this, keys );
-
-            return this;
-        },
-
-        fetch: function ( key, callback ) {
-            var arr = [];
-            var keys = $.isArray( key ) ? key : [ key ];
-
-            for ( var k = 0; k < keys.length; k++ ) {
-                var storage_key = this.dbname + '.' + this.name + '.' + keys[k];
-                var obj = storage.getItem( storage_key );
-
-                if ( obj ) {
-                    obj = JSON.parse( obj );
-                    obj.key = keys[k];
-                    arr.push( obj );
-                }
-            }
-
-            if ( callback ) callback.call( this, this, arr );
-
-            return this;
-        },
-
-        fetchAll: function ( callback ) {
-            var arr = [];
-            var idx = this.indexer.all();
-
-            for ( var k = 0; k < idx.length; k++ ) {
-                var obj = JSON.parse( storage.getItem( idx[k] ) );
-                obj.key = idx[k].replace( this.dbname + '.' + this.name + '.', '' );
-                arr.push( obj );
-            }
-
-            if ( callback ) callback.call( this, this, arr );
-
-            return this;
-        },
-
-        destroy: function () {
-            this.keys( function ( store, keys ) {
-                for ( var key in keys ) {
-                    this.remove( key );
-                }
-            } );
-
-            storage.removeItem( this.dbname + '.' + this.name + '.__index__' );
-        }
-    };
-})() );

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.idb.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.idb.js	2013-02-20 08:03:09 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.idb.js	2013-05-02 13:55:39 +0000
@@ -1,160 +1,491 @@
-// web storage support (indexedDb)
-dhis2.storage.Store.adapter( 'indexed-db', (function () {
-    window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.oIndexedDB || window.msIndexedDB;
-    window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
-    window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
-
-    var IDB = IDB || {};
-    IDB.TransactionModes = {
-        READ_ONLY: 'readonly',
-        READ_WRITE: 'readwrite'
-    };
-
-    function getTransaction( mode ) {
-        return IDB.db.transaction( [ IDB.options.name ], mode );
-    }
-
-    function getReadOnlyObjectStore() {
-        return getTransaction( IDB.TransactionModes.READ_ONLY ).objectStore( IDB.options.name );
-    }
-
-    function getReadWriteObjectStore() {
-        return getTransaction( IDB.TransactionModes.READ_WRITE ).objectStore( IDB.options.name );
-    }
-
-    function defaultErrorHandler() {
-        console.log( "error:", e );
-    }
-
-    function defaultBlockingHandler() {
-        console.log( "blocked:", e );
-    }
-
-    return {
-        valid: function () {
-            return false;
-            // return !!(window.indexedDB && window.IDBTransaction && window.IDBKeyRange);
-        },
-
-        init: function ( options, callback ) {
-            var that = this;
-            IDB.options = options;
-            var request = window.indexedDB.open( this.dbname, "1" );
-
-            request.onupgradeneeded = function ( e ) {
-                IDB.db = e.target.result;
-
-                if ( IDB.db.objectStoreNames.contains( that.name ) ) {
-                    IDB.db.deleteObjectStore( that.name );
-                }
-
-                IDB.db.createObjectStore( that.name );
-            };
-
-            request.onsuccess = function ( e ) {
-                IDB.db = e.target.result;
-                if ( callback ) callback.call( that, that );
-            };
-
-            request.onerror = defaultErrorHandler;
-            request.onblocked = defaultBlockingHandler;
-        },
-
-        add: function ( key, obj, callback ) {
-            var that = this;
-            var request = getReadWriteObjectStore().put( obj, key );
-
-            request.onsuccess = function ( e ) {
-                if ( callback ) callback.call( that, that, obj );
-            };
-
-            request.onerror = defaultErrorHandler;
-            request.onblocked = defaultBlockingHandler;
-        },
-
-        addAll: function ( keys, objs, callback ) {
-            var that = this;
-
-            if ( keys.length == 0 || objs.length == 0 ) {
-                if ( callback ) callback.call( that, that );
-                return;
-            }
-
-            var key = keys.pop();
-            var obj = objs.pop();
-
-            this.add( key, obj, function ( store ) {
-                that.addAll( keys, objs, callback );
-            } );
-        },
-
-        remove: function ( key, callback ) {
-            var that = this;
-            var request = getReadWriteObjectStore().delete( key );
-
-            request.onsuccess = function ( e ) {
-                if ( callback ) callback.call( that, that );
-            };
-
-            request.onerror = defaultErrorHandler;
-            request.onblocked = defaultBlockingHandler;
-        },
-
-        exists: function ( key, callback ) {
-            var that = this;
-            var request = getReadOnlyObjectStore().get( key );
-
-            request.onsuccess = function ( e ) {
-                if ( callback ) callback.call( that, that, e.target.result != null );
-            };
-
-            request.onerror = defaultErrorHandler;
-            request.onblocked = defaultBlockingHandler;
-        },
-
-        fetch: function ( key, callback ) {
-            var that = this;
-            var request = getReadOnlyObjectStore().get( key );
-
-            request.onsuccess = function ( e ) {
-                if ( callback ) callback.call( that, that, e.target.result );
-            };
-
-            request.onerror = defaultErrorHandler;
-            request.onblocked = defaultBlockingHandler;
-        },
-
-        fetchAll: function ( callback ) {
-            var that = this;
-            var records = [];
-            var request = getReadOnlyObjectStore().openCursor();
-
-            request.onsuccess = function ( e ) {
-                var cursor = e.target.result;
-
-                if ( cursor ) {
-                    records.push( cursor.value );
-                    cursor.continue();
-                } else {
-                    if ( callback ) callback.call( that, that, records );
-                }
-            };
-
-            request.onerror = defaultErrorHandler;
-            request.onblocked = defaultBlockingHandler;
-        },
-
-        destroy: function ( callback ) {
-            var that = this;
-            IDB.db.close();
-            var request = window.indexedDB.deleteDatabase( IDB.options.dbname );
-
-            request.onsuccess = function ( e ) {
-                if ( callback ) callback.call( that, that );
-            }
-
-            request.onerror = defaultErrorHandler;
-            request.onblocked = defaultBlockingHandler;
-        }
-    };
-})() );
+"use strict";
+
+/*
+ * Copyright (c) 2004-2013, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the HISP project nor the names of its contributors may
+ *   be used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+dhis2.util.namespace( 'dhis2.storage' );
+
+(function ( $, window, document, undefined ) {
+    if ( typeof window.indexedDB === 'undefined' ) {
+        window.indexedDB = window.webkitIndexedDB || window.mozIndexedDB || window.oIndexedDB || window.msIndexedDB;
+    }
+
+    if ( typeof window.IDBKeyRange === 'undefined' ) {
+        window.IDBKeyRange = window.webkitIDBKeyRange || window.msIDBKeyRange;
+    }
+
+    if ( typeof window.IDBTransaction === 'undefined' ) {
+        window.IDBTransaction = window.webkitIDBTransaction || window.msIDBTransaction
+    }
+
+    dhis2.storage.IndexedDBAdapter = function ( options ) {
+        if ( !(this instanceof dhis2.storage.IndexedDBAdapter) ) {
+            return new dhis2.storage.IndexedDBAdapter( options );
+        }
+
+        Object.defineProperties( this, {
+            'name': {
+                value: options.name,
+                enumerable: true
+            },
+            'version': {
+                value: options.version,
+                enumerable: true
+            },
+            'objectStoreNames': {
+                value: options.objectStores,
+                enumerable: true
+            },
+            'keyPath': {
+                value: options.keyPath,
+                enumerable: true
+            }
+        } );
+
+        return this;
+    };
+
+    Object.defineProperties( dhis2.storage.IndexedDBAdapter.prototype, {
+        'open': {
+            value: function () {
+                var self = this;
+                var request = window.indexedDB.open( this.name, self.version );
+
+                request.onupgradeneeded = function ( e ) {
+                    self._db = e.target.result;
+
+                    $.each( self.objectStoreNames, function ( idx, item ) {
+                        if ( self._db.objectStoreNames.contains( item ) ) {
+                            self._db.deleteObjectStore( item );
+                        }
+                    } );
+
+                    $.each( self.objectStoreNames, function ( idx, item ) {
+                        self._db.createObjectStore( item );
+                    } );
+                };
+
+                var deferred = $.Deferred();
+
+                request.onsuccess = function ( e ) {
+                    self._db = e.target.result;
+                    deferred.resolveWith( self );
+                };
+
+                request.onerror = function ( e ) {
+                    console.log( 'error' );
+                    deferred.rejectWith( self, e );
+                };
+
+                request.onblocked = function ( e ) {
+                    console.log( 'blocked' );
+                    deferred.rejectWith( self, e );
+                };
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'set': {
+            value: function ( store, object ) {
+                var self = this;
+
+                if ( typeof self._db === 'undefined' ) {
+                    throw new Error( 'Database is not open.' );
+                }
+
+                if ( typeof object === 'undefined' || typeof object[self.keyPath] === 'undefined' ) {
+                    throw new Error( 'Invalid object' );
+                }
+
+                object = JSON.parse( JSON.stringify( object ) );
+
+                var key = object[self.keyPath];
+                delete object[self.keyPath];
+
+                var tx = self._db.transaction( [ store ], "readwrite" );
+                var objectStore = tx.objectStore( store );
+                var request = objectStore.put( object, key );
+
+                var deferred = $.Deferred();
+
+                request.onsuccess = function () {
+                    deferred.resolveWith( self, [ object ] );
+                };
+
+                request.onerror = function ( e ) {
+                    console.log( 'error' );
+                    deferred.rejectWith( self, e );
+                };
+
+                request.onblocked = function ( e ) {
+                    console.log( 'blocked' );
+                    deferred.rejectWith( self, e );
+                };
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'setAll': {
+            value: function ( store, arr ) {
+                var self = this;
+                var deferred = $.Deferred();
+                var chained = deferred.then();
+
+                $.each( arr, function ( idx, item ) {
+                    chained = chained.then( function () {
+                        return self.set( store, item );
+                    } );
+                } );
+
+                deferred.resolveWith( this );
+
+                return chained;
+            },
+            enumerable: true
+        },
+        'get': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof self._db === 'undefined' ) {
+                    throw new Error( 'Database is not open.' );
+                }
+
+                if ( typeof key === 'undefined' ) {
+                    throw new Error( 'Invalid key: ', key );
+                }
+
+                var tx = self._db.transaction( [ store ], "readonly" );
+                var objectStore = tx.objectStore( store );
+                var request = objectStore.get( key );
+
+                var deferred = $.Deferred();
+
+                request.onsuccess = function ( e ) {
+                    var object = e.target.result;
+
+                    if ( typeof object !== 'undefined' ) {
+                        object[self.keyPath] = key;
+                    }
+
+                    deferred.resolveWith( self, [ object ] );
+                };
+
+                request.onerror = function ( e ) {
+                    console.log( 'error' );
+                    deferred.rejectWith( self, e );
+                };
+
+                request.onblocked = function ( e ) {
+                    console.log( 'blocked' );
+                    deferred.rejectWith( self, e );
+                };
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'getAll': {
+            value: function ( store, predicate ) {
+                var self = this;
+                var deferred = $.Deferred();
+
+                var records = [];
+                var filtered = typeof predicate === 'function';
+
+                var tx = self._db.transaction( [ store ], "readonly" );
+                var objectStore = tx.objectStore( store );
+                var request = objectStore.openCursor();
+
+                request.onsuccess = function ( e ) {
+                    var cursor = e.target.result;
+
+                    if ( cursor ) {
+                        cursor.value[self.keyPath] = cursor.key;
+
+                        if ( filtered ) {
+                            if ( predicate( cursor.value ) ) {
+                                records.push( cursor.value );
+                            }
+                        } else {
+                            records.push( cursor.value );
+                        }
+
+                        cursor.continue();
+                    } else {
+                        deferred.resolveWith( self, [ records ] );
+                    }
+                };
+
+                request.onerror = function ( e ) {
+                    console.log( 'error' );
+                    deferred.rejectWith( self, e );
+                };
+
+                request.onblocked = function ( e ) {
+                    console.log( 'blocked' );
+                    deferred.rejectWith( self, e );
+                };
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'getKeys': {
+            value: function ( store ) {
+                var self = this;
+                var deferred = $.Deferred();
+                var keys = [];
+                var tx = self._db.transaction( [ store ], "readonly" );
+                var objectStore = tx.objectStore( store );
+                var request = objectStore.openCursor();
+
+                request.onsuccess = function ( e ) {
+                    var cursor = e.target.result;
+
+                    if ( cursor ) {
+                        keys.push( cursor.key );
+                        cursor.continue();
+                    } else {
+                        deferred.resolveWith( self, [ keys ] );
+                    }
+                };
+
+                request.onerror = function ( e ) {
+                    console.log( 'error' );
+                    deferred.rejectWith( self, e );
+                };
+
+                request.onblocked = function ( e ) {
+                    console.log( 'blocked' );
+                    deferred.rejectWith( self, e );
+                };
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'delete': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof self._db === 'undefined' ) {
+                    throw new Error( 'Database is not open.' );
+                }
+
+                if ( typeof key === 'undefined' ) {
+                    throw new Error( 'Invalid key: ', key );
+                }
+
+                var tx = self._db.transaction( [ store ], "readwrite" );
+                var objectStore = tx.objectStore( store );
+                var request = objectStore.delete( key );
+
+                var deferred = $.Deferred();
+
+                request.onsuccess = function ( e ) {
+                    deferred.resolveWith( self );
+                };
+
+                request.onerror = function ( e ) {
+                    console.log( 'error' );
+                    deferred.rejectWith( self, e );
+                };
+
+                request.onblocked = function ( e ) {
+                    console.log( 'blocked' );
+                    deferred.rejectWith( self, e );
+                };
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'clear': {
+            value: function ( store ) {
+                var self = this;
+
+                if ( typeof self._db === 'undefined' ) {
+                    throw new Error( 'Database is not open.' );
+                }
+
+                var tx = self._db.transaction( [ store ], "readwrite" );
+                var objectStore = tx.objectStore( store );
+                var request = objectStore.clear();
+
+                var deferred = $.Deferred();
+
+                request.onsuccess = function ( e ) {
+                    deferred.resolveWith( self );
+                };
+
+                request.onerror = function ( e ) {
+                    console.log( 'error' );
+                    deferred.rejectWith( self, e );
+                };
+
+                request.onblocked = function ( e ) {
+                    console.log( 'blocked' );
+                    deferred.rejectWith( self, e );
+                };
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'contains': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof self._db === 'undefined' ) {
+                    throw new Error( 'Database is not open.' );
+                }
+
+                if ( typeof key === 'undefined' ) {
+                    throw new Error( 'Invalid key: ', key );
+                }
+
+                var tx = self._db.transaction( [ store ], "readonly" );
+                var objectStore = tx.objectStore( store );
+                var request = objectStore.get( key );
+
+                var deferred = $.Deferred();
+
+                request.onsuccess = function ( e ) {
+                    deferred.resolveWith( self, [ e.target.result !== undefined ] );
+                };
+
+                request.onerror = function ( e ) {
+                    console.log( 'error' );
+                    deferred.rejectWith( self, e );
+                };
+
+                request.onblocked = function ( e ) {
+                    console.log( 'blocked' );
+                    deferred.rejectWith( self, e );
+                };
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'count': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof self._db === 'undefined' ) {
+                    throw new Error( 'Database is not open.' );
+                }
+
+                if ( typeof key === 'undefined' ) {
+                    key = null;
+                }
+
+                var tx = self._db.transaction( [ store ], "readonly" );
+                var objectStore = tx.objectStore( store );
+                var request = objectStore.count( key );
+
+                var deferred = $.Deferred();
+
+                request.onsuccess = function ( e ) {
+                    deferred.resolveWith( self, [ e.target.result ] );
+                };
+
+                request.onerror = function ( e ) {
+                    console.log( 'error' );
+                    deferred.rejectWith( self, e );
+                };
+
+                request.onblocked = function ( e ) {
+                    console.log( 'blocked' );
+                    deferred.rejectWith( self, e );
+                };
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'close': {
+            value: function () {
+                var deferred = $.Deferred();
+
+                if ( typeof idb._db === 'undefined' ) {
+                    deferred.resolve();
+                }
+
+                idb._db.close();
+                idb._db = undefined;
+
+                deferred.resolve();
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'destroy': {
+            value: function () {
+                var self = this;
+                var deferred = $.Deferred();
+
+                if ( typeof self._db !== 'undefined' ) {
+                    self._db.close();
+                }
+
+                var request = window.indexedDB.deleteDatabase( self.name );
+
+                request.onsuccess = function ( e ) {
+                    self._db = undefined;
+                    deferred.resolveWith( self, [ e ] );
+                };
+
+                request.onerror = function ( e ) {
+                    console.log( 'error' );
+                    deferred.rejectWith( self, [ e ] );
+                };
+
+                request.onblocked = function ( e ) {
+                    console.log( 'blocked' );
+                    deferred.rejectWith( self, [ e ] );
+                };
+
+                return deferred.promise();
+            },
+            enumerable: true
+        }
+    } );
+
+    Object.defineProperties( dhis2.storage.IndexedDBAdapter, {
+        'isSupported': {
+            value: function () {
+                return !!(window.indexedDB && window.IDBTransaction && window.IDBKeyRange);
+            },
+            enumerable: true
+        }
+    } );
+})( jQuery, window, document );

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.js	2013-03-30 10:45:13 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.js	2013-05-02 13:55:39 +0000
@@ -1,94 +1,153 @@
-// big chunks of this is based on code from:
-// http://brian.io/lawnchair
+"use strict";
+
+/*
+ * Copyright (c) 2004-2013, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the HISP project nor the names of its contributors may
+ *   be used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
 
 dhis2.util.namespace( 'dhis2.storage' );
 
-dhis2.storage.Store = function ( options, callback ) {
-    var Store = dhis2.storage.Store;
-
-    this.name = options.name || 'records';
-    this.dbname = options.dbname || 'dhis2';
-    this.record = options.record || 'record';
-
-    if ( arguments.length <= 2 && arguments.length > 0 ) {
-        callback = (typeof arguments[0] === 'function') ? arguments[0] : arguments[1];
-        options = (typeof arguments[0] === 'function') ? {} : arguments[0];
-    } else {
-        throw 'Incorrect # of ctor args!'
-    }
+dhis2.storage.Store = function ( options ) {
+    var self = this;
+
+    if ( !(this instanceof dhis2.storage.Store) ) {
+        return new dhis2.storage.Store( options );
+    }
+
+    if ( typeof options.name === 'undefined' ) {
+        throw Error( 'Constructor needs a valid database name as a argument' );
+    }
+
+    if ( typeof options.objectStores === 'undefined' || !$.isArray( options.objectStores ) || options.objectStores.length == 0 ) {
+        throw Error( 'Constructor needs a valid objectStores array as a argument' );
+    }
+
+    options.keyPath = options.keyPath || 'id';
+    options.version = options.version || '1';
 
     if ( !JSON ) throw 'JSON unavailable! Include http://www.json.org/json2.js to fix.';
-    if ( typeof callback !== 'function' ) throw 'No callback was provided.';
-    if ( Store.adapters.length == 0 ) throw 'No adapters was provided.';
-
-    var adapter;
-
-    if ( options.adapter ) {
-        for ( var i = 0, l = Store.adapters.length; i < l; i++ ) {
-            if ( options.adapter === Store.adapters[i].id ) {
-                adapter = Store.adapters[i].valid() ? Store.adapters[i] : undefined;
-                break;
-            }
-        }
-    } else {
-        for ( var i = 0, l = Store.adapters.length; i < l; i++ ) {
-            adapter = Store.adapters[i].valid() ? Store.adapters[i] : undefined;
-            if ( adapter ) break;
-        }
-    }
-
-    if ( !adapter ) throw 'No valid adapter.';
-
-    // mixin adapter functions
-    for ( var i in adapter ) {
-        if ( adapter.hasOwnProperty( i ) ) {
-            this[i] = adapter[i];
-        }
-    }
-
-    this.init( options, callback );
-};
-
-dhis2.storage.Store.adapters = [];
-
-dhis2.storage.Store.adapter = function ( id, obj ) {
-    var Store = dhis2.storage.Store;
-    var adapter_interface = "init add addAll remove exists fetch fetchAll destroy".split( ' ' );
-
-    var missing_functions = [];
-    // verify adapter
-    for ( var i in adapter_interface ) {
-        if ( !obj.hasOwnProperty( adapter_interface[i] ) || typeof obj[adapter_interface[i]] !== 'function' ) {
-            missing_functions.push( adapter_interface[i] );
-        }
-    }
-
-    if ( missing_functions.length > 0 ) {
-        throw 'Adapter \'' + id + '\' does not meet interface requirements, missing: ' + missing_functions.join( ' ' );
-    }
-
-    obj['id'] = id;
-    Store.adapters.splice( 0, 0, obj );
-};
-
-dhis2.storage.Store.plugins = {};
-
-dhis2.storage.Store.plugin = function ( id, obj ) {
-    var Store = dhis2.storage.Store;
-    var plugin_interface = "call".split( ' ' );
-
-    var missing_functions = [];
-    // verify plugin
-    for ( var i in plugin_interface ) {
-        if ( !obj.hasOwnProperty( plugin_interface[i] ) || typeof obj[plugin_interface[i]] !== 'function' ) {
-            missing_functions.push( plugin_interface[i] );
-        }
-    }
-
-    if ( missing_functions.length > 0 ) {
-        throw 'Plugin\'' + id + '\' does not meet interface requirements, missing: ' + missing_functions.join( ' ' );
-    }
-
-    obj['id'] = id;
-    Store.plugins[id] = obj;
-};
+
+    var Adapter;
+
+    for ( var i = 0, len = options.adapters.length; i < len; i++ ) {
+        if ( dhis2.storage.Store.verifyAdapter( options.adapters[i] ) && options.adapters[i].isSupported() ) {
+            Adapter = options.adapters[i];
+            break;
+        }
+    }
+
+    if ( !Adapter ) throw 'No valid adapter.';
+
+    var adapter = new Adapter( options );
+
+    Object.defineProperty( this, 'adapter', {
+        value: adapter
+    } );
+
+    $.each( dhis2.storage.Store.adapterMethods, function ( idx, item ) {
+        var descriptor = Object.getOwnPropertyDescriptor( Adapter, item ) || Object.getOwnPropertyDescriptor( Adapter.prototype, item );
+        Object.defineProperty( self, item, descriptor );
+    } );
+
+    $.each( dhis2.storage.Store.adapterProperties, function ( idx, item ) {
+        var descriptor = Object.getOwnPropertyDescriptor( adapter, item );
+        Object.defineProperty( self, item, descriptor );
+    } );
+
+    if ( typeof adapter.customApi !== 'undefined' ) {
+        $.each( adapter.customApi, function ( idx, item ) {
+            self[item] = adapter[item];
+        } );
+    }
+};
+
+dhis2.storage.Store.adapterMethods = "open set setAll get getAll getKeys count contains clear close delete destroy".split( ' ' );
+dhis2.storage.Store.adapterProperties = "name version objectStoreNames keyPath".split( ' ' );
+
+dhis2.storage.Store.verifyAdapter = function ( Adapter ) {
+    var failed = [];
+
+    if ( typeof Adapter === 'undefined' ) {
+        return false;
+    }
+
+    $.each( dhis2.storage.Store.adapterMethods, function ( idx, item ) {
+        // should probably go up the prototype chain here
+        var descriptor = Object.getOwnPropertyDescriptor( Adapter, item ) || Object.getOwnPropertyDescriptor( Adapter.prototype, item );
+
+        if ( typeof descriptor.value !== 'function' ) {
+            failed.push( item );
+        }
+    } );
+
+    return failed.length === 0;
+};
+
+/*
+var STUDENT_STORE = 'students';
+var COURSE_STORE = 'courses';
+
+var store1 = new dhis2.storage.Store( {
+    name: 'store',
+    adapters: [ dhis2.storage.InMemoryAdapter ],
+    objectStores: [ STUDENT_STORE ]
+} );
+
+var store2 = new dhis2.storage.Store( {
+    name: 'store',
+    adapters: [ dhis2.storage.InMemoryAdapter ],
+    objectStores: [ COURSE_STORE ]
+} );
+
+var students = [
+    {'id': 'abc1', name: 'Morten 1'},
+    {'id': 'abc2', name: 'Morten 2'},
+    {'id': 'abc3', name: 'Morten 3'},
+    {'id': 'abc4', name: 'Morten 4'},
+];
+
+var courses = [
+    {'id': 'abc1', name: 'Morten 1'},
+    {'id': 'abc2', name: 'Morten 2'},
+    {'id': 'abc3', name: 'Morten 3'},
+    {'id': 'abc4', name: 'Morten 4'},
+];
+
+store1.open().done( function () {
+    store1.setAll( STUDENT_STORE, students ).then( function () {
+        store1.count( STUDENT_STORE ).done( function ( n ) {
+            console.log( n );
+        } );
+    } );
+} );
+
+store2.open().done( function () {
+    store2.setAll( COURSE_STORE, courses ).then( function () {
+        store2.count( COURSE_STORE ).done( function ( n ) {
+            console.log( n );
+        } );
+    } );
+} );
+*/
\ No newline at end of file

=== added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.ls.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.ls.js	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.ls.js	2013-05-02 13:55:39 +0000
@@ -0,0 +1,338 @@
+"use strict";
+
+/*
+ * Copyright (c) 2004-2013, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the HISP project nor the names of its contributors may
+ *   be used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+dhis2.util.namespace( 'dhis2.storage' );
+
+(function ( $, window, document, undefined ) {
+    dhis2.storage.DomLocalStorageAdapter = function ( options ) {
+        this.storage = window.localStorage;
+
+        if ( !(this instanceof dhis2.storage.DomLocalStorageAdapter) ) {
+            return new dhis2.storage.DomLocalStorageAdapter( options );
+        }
+
+        Object.defineProperties( this, {
+            'name': {
+                value: options.name,
+                enumerable: true
+            },
+            'version': {
+                value: options.version,
+                enumerable: true
+            },
+            'objectStoreNames': {
+                value: options.objectStores,
+                enumerable: true
+            },
+            'keyPath': {
+                value: options.keyPath,
+                enumerable: true
+            }
+        } );
+
+        this.customApi = 'Indexer storage'.split( ' ' );
+
+        this.Indexer = function ( name, objectStore, storage ) {
+            return {
+                key: name + '.' + objectStore + '.**index**',
+
+                all: function () {
+                    var a = storage.getItem( this.key );
+
+                    if ( a ) {
+                        try {
+                            a = JSON.parse( a );
+                        } catch ( e ) {
+                            a = null;
+                        }
+                    }
+
+                    if ( a == null ) {
+                        storage.setItem( this.key, JSON.stringify( [] ) );
+                    }
+
+                    return JSON.parse( storage.getItem( this.key ) );
+                },
+
+                add: function ( key ) {
+                    var a = this.all();
+                    a.push( key );
+                    storage.setItem( this.key, JSON.stringify( a ) );
+                },
+
+                remove: function ( key ) {
+                    var a = this.all();
+
+                    if ( a.indexOf( key ) != -1 ) {
+                        dhis2.array.remove( a, a.indexOf( key ), a.indexOf( key ) );
+                        storage.setItem( this.key, JSON.stringify( a ) );
+                    }
+                },
+
+                find: function ( key ) {
+                    var a = this.all();
+                    return a.indexOf( key );
+                },
+
+                // warning: will just delete index, no check
+                destroy: function () {
+                    storage.removeItem( this.key );
+                }
+            }
+        };
+
+        return this;
+    };
+
+    Object.defineProperties( dhis2.storage.DomLocalStorageAdapter.prototype, {
+        'open': {
+            value: function () {
+                var self = this;
+                var deferred = $.Deferred();
+
+                self.indexer = {};
+
+                $.each( self.objectStoreNames, function ( idx, item ) {
+                    self.indexer[item] = self.Indexer( self.name, item, self.storage );
+                } );
+
+                deferred.resolveWith( self );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'set': {
+            value: function ( store, object ) {
+                var self = this;
+
+                if ( typeof object === 'undefined' || typeof object[self.keyPath] === 'undefined' ) {
+                    throw new Error( 'Invalid object' );
+                }
+
+                object = JSON.parse( JSON.stringify( object ) );
+
+                var key = object[self.keyPath];
+                delete object[self.keyPath];
+
+                key = this.name + '.' + store + '.' + key;
+                if ( this.indexer[store].find( key ) == -1 ) this.indexer[store].add( key );
+
+                var deferred = $.Deferred();
+
+                try {
+                    this.storage.setItem( key, JSON.stringify( object ) );
+                    deferred.resolveWith( self, [ object] );
+                } catch ( err ) {
+                    deferred.rejectWith( self, [ err ] );
+                }
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'setAll': {
+            value: function ( store, arr ) {
+                var self = this;
+                var deferred = $.Deferred();
+                var chained = deferred.then();
+
+                $.each( arr, function ( idx, item ) {
+                    chained = chained.then( function () {
+                        return self.set( store, item );
+                    } );
+                } );
+
+                deferred.resolveWith( this );
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'get': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof key === 'undefined' ) {
+                    throw new Error( 'Invalid key: ', key );
+                }
+
+                var deferred = $.Deferred();
+                var object = this.storage.getItem( this.name + '.' + store + '.' + key );
+
+                if ( object ) {
+                    object = JSON.parse( object );
+                    object[this.keyPath] = key;
+                }
+
+                deferred.resolveWith( self, [ object ] );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'getAll': {
+            value: function ( store, predicate ) {
+                var self = this;
+                var deferred = $.Deferred();
+                var idx = this.indexer[store].all();
+                var objects = [];
+
+                if ( typeof predicate !== 'undefined' ) {
+                    // just log and continue
+                    console.log( 'predicate filtering is currently not supported in dom storage getAll, returning all' );
+                }
+
+                for ( var i = 0, len = idx.length; i < len; i++ ) {
+                    var object = this.storage.getItem( idx[i] );
+
+                    if ( object ) {
+                        object = JSON.parse( object );
+                        object[this.keyPath] = idx[i].replace( self.name + '.' + store + '.', '' );
+                        objects.push( object );
+                    }
+                }
+
+                deferred.resolveWith( self, [ objects ] );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'getKeys': {
+            value: function ( store ) {
+                var self = this;
+                var deferred = $.Deferred();
+
+                var keys = this.indexer[store].all().map( function ( r ) {
+                    return r.replace( self.name + '.' + store + '.', '' )
+                } );
+
+                deferred.resolveWith( self, [ keys ] );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'delete': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof key === 'undefined' ) {
+                    throw new Error( 'Invalid key: ', key );
+                }
+
+                var deferred = $.Deferred();
+
+                key = this.name + '.' + store + '.' + key;
+                this.indexer[store].remove( key );
+                this.storage.removeItem( key );
+
+                deferred.resolveWith( self );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'clear': {
+            value: function ( store ) {
+                var self = this;
+                var deferred = $.Deferred();
+
+                this.getKeys( store ).done( function ( keys ) {
+                    $.each( keys, function ( idx, item ) {
+                        self.delete( store, item );
+                    } );
+
+                    deferred.resolveWith( self );
+                } );
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'contains': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof key === 'undefined' ) {
+                    throw new Error( 'Invalid key: ', key );
+                }
+
+                key = this.name + '.' + store + '.' + key;
+                var deferred = $.Deferred();
+                deferred.resolveWith( self, [ this.indexer[store].find( key ) != -1 ] );
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'count': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof key !== 'undefined' ) {
+                    throw new Error( 'key based count is not supported by DomLocalStorageAdapter' );
+                }
+
+                var deferred = $.Deferred();
+                deferred.resolveWith( self, [ this.indexer[store].all().length ] );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'close': {
+            value: function () {
+                var deferred = $.Deferred();
+                deferred.resolve();
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'destroy': {
+            value: function () {
+                var self = this;
+                var deferred = $.Deferred();
+
+                $.each( self.objectStoreNames, function ( idx, item ) {
+                    self.clear( item );
+                    self.indexer[item].destroy();
+                } );
+
+                deferred.resolve();
+                return deferred.promise();
+            },
+            enumerable: true
+        }
+    } );
+
+    Object.defineProperties( dhis2.storage.DomLocalStorageAdapter, {
+        'isSupported': {
+            value: function () {
+                return !!window.localStorage;
+            },
+            enumerable: true
+        }
+    } );
+})( jQuery, window, document );

=== added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.memory.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.memory.js	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.memory.js	2013-05-02 13:55:39 +0000
@@ -0,0 +1,338 @@
+"use strict";
+
+/*
+ * Copyright (c) 2004-2013, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the HISP project nor the names of its contributors may
+ *   be used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+dhis2.util.namespace( 'dhis2.storage' );
+
+(function ( $, window, document, undefined ) {
+    dhis2.storage.InMemoryAdapter = function ( options ) {
+        this.storage = {};
+
+        if ( !(this instanceof dhis2.storage.InMemoryAdapter) ) {
+            return new dhis2.storage.InMemoryAdapter( options );
+        }
+
+        Object.defineProperties( this, {
+            'name': {
+                value: options.name,
+                enumerable: true
+            },
+            'version': {
+                value: options.version,
+                enumerable: true
+            },
+            'objectStoreNames': {
+                value: options.objectStores,
+                enumerable: true
+            },
+            'keyPath': {
+                value: options.keyPath,
+                enumerable: true
+            }
+        } );
+
+        this.customApi = 'Indexer storage'.split( ' ' );
+
+        this.Indexer = function ( name, objectStore, storage ) {
+            return {
+                key: name + '.' + objectStore + '.**index**',
+
+                all: function () {
+                    var a = storage[ this.key ];
+
+                    if ( a ) {
+                        try {
+                            a = JSON.parse( a );
+                        } catch ( e ) {
+                            a = null;
+                        }
+                    }
+
+                    if ( a == null ) {
+                        storage[this.key] = JSON.stringify( [] );
+                    }
+
+                    return JSON.parse( storage[ this.key ] );
+                },
+
+                add: function ( key ) {
+                    var a = this.all();
+                    a.push( key );
+                    storage[this.key] = JSON.stringify( a );
+                },
+
+                remove: function ( key ) {
+                    var a = this.all();
+
+                    if ( a.indexOf( key ) != -1 ) {
+                        dhis2.array.remove( a, a.indexOf( key ), a.indexOf( key ) );
+                        storage[this.key] = JSON.stringify( a );
+                    }
+                },
+
+                find: function ( key ) {
+                    var a = this.all();
+                    return a.indexOf( key );
+                },
+
+                // warning: will just delete index, no check
+                destroy: function () {
+                    delete storage[this.key];
+                }
+            }
+        };
+
+        return this;
+    };
+
+    Object.defineProperties( dhis2.storage.InMemoryAdapter.prototype, {
+        'open': {
+            value: function () {
+                var self = this;
+                var deferred = $.Deferred();
+
+                self.indexer = {};
+
+                $.each( self.objectStoreNames, function ( idx, item ) {
+                    self.indexer[item] = self.Indexer( self.name, item, self.storage );
+                } );
+
+                deferred.resolveWith( self );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'set': {
+            value: function ( store, object ) {
+                var self = this;
+
+                if ( typeof object === 'undefined' || typeof object[self.keyPath] === 'undefined' ) {
+                    throw new Error( 'Invalid object' );
+                }
+
+                object = JSON.parse( JSON.stringify( object ) );
+
+                var key = object[self.keyPath];
+                delete object[self.keyPath];
+
+                key = this.name + '.' + store + '.' + key;
+                if ( this.indexer[store].find( key ) == -1 ) this.indexer[store].add( key );
+
+                var deferred = $.Deferred();
+
+                try {
+                    this.storage[ key ] = JSON.stringify( object );
+                    deferred.resolveWith( self, [ object] );
+                } catch ( err ) {
+                    deferred.rejectWith( self, [ err ] );
+                }
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'setAll': {
+            value: function ( store, arr ) {
+                var self = this;
+                var deferred = $.Deferred();
+                var chained = deferred.then();
+
+                $.each( arr, function ( idx, item ) {
+                    chained = chained.then( function () {
+                        return self.set( store, item );
+                    } );
+                } );
+
+                deferred.resolveWith( this );
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'get': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof key === 'undefined' ) {
+                    throw new Error( 'Invalid key: ', key );
+                }
+
+                var deferred = $.Deferred();
+                var object = this.storage[this.name + '.' + store + '.' + key];
+
+                if ( object ) {
+                    object = JSON.parse( object );
+                    object[this.keyPath] = key;
+                }
+
+                deferred.resolveWith( self, [ object ] );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'getAll': {
+            value: function ( store, predicate ) {
+                var self = this;
+                var deferred = $.Deferred();
+                var idx = this.indexer[store].all();
+                var objects = [];
+
+                if ( typeof predicate !== 'undefined' ) {
+                    // just log and continue
+                    console.log( 'predicate filtering is currently not supported in dom storage getAll, returning all' );
+                }
+
+                for ( var i = 0, len = idx.length; i < len; i++ ) {
+                    var object = this.storage[ idx[i] ];
+
+                    if ( object ) {
+                        object = JSON.parse( object );
+                        object[this.keyPath] = idx[i].replace( self.name + '.' + store + '.', '' );
+                        objects.push( object );
+                    }
+                }
+
+                deferred.resolveWith( self, [ objects ] );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'getKeys': {
+            value: function ( store ) {
+                var self = this;
+                var deferred = $.Deferred();
+
+                var keys = this.indexer[store].all().map( function ( r ) {
+                    return r.replace( self.name + '.' + store + '.', '' )
+                } );
+
+                deferred.resolveWith( self, [ keys ] );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'delete': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof key === 'undefined' ) {
+                    throw new Error( 'Invalid key: ', key );
+                }
+
+                var deferred = $.Deferred();
+
+                key = this.name + '.' + store + '.' + key;
+                this.indexer[store].remove( key );
+                delete this.storage[key ];
+
+                deferred.resolveWith( self );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'clear': {
+            value: function ( store ) {
+                var self = this;
+                var deferred = $.Deferred();
+
+                this.getKeys( store ).done( function ( keys ) {
+                    $.each( keys, function ( idx, item ) {
+                        self.delete( store, item );
+                    } );
+
+                    deferred.resolveWith( self );
+                } );
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'contains': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof key === 'undefined' ) {
+                    throw new Error( 'Invalid key: ', key );
+                }
+
+                key = this.name + '.' + store + '.' + key;
+                var deferred = $.Deferred();
+                deferred.resolveWith( self, [ this.indexer[store].find( key ) != -1 ] );
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'count': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof key !== 'undefined' ) {
+                    throw new Error( 'key based count is not supported by InMemoryAdapter' );
+                }
+
+                var deferred = $.Deferred();
+                deferred.resolveWith( self, [ this.indexer[store].all().length ] );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'close': {
+            value: function () {
+                var deferred = $.Deferred();
+                deferred.resolve();
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'destroy': {
+            value: function () {
+                var self = this;
+                var deferred = $.Deferred();
+
+                $.each( self.objectStoreNames, function ( idx, item ) {
+                    self.clear( item );
+                    self.indexer[item].destroy();
+                } );
+
+                deferred.resolve();
+                return deferred.promise();
+            },
+            enumerable: true
+        }
+    } );
+
+    Object.defineProperties( dhis2.storage.InMemoryAdapter, {
+        'isSupported': {
+            value: function () {
+                return true;
+            },
+            enumerable: true
+        }
+    } );
+})( jQuery, window, document );

=== added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.ss.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.ss.js	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.storage.ss.js	2013-05-02 13:55:39 +0000
@@ -0,0 +1,338 @@
+"use strict";
+
+/*
+ * Copyright (c) 2004-2013, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the HISP project nor the names of its contributors may
+ *   be used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+dhis2.util.namespace( 'dhis2.storage' );
+
+(function ( $, window, document, undefined ) {
+    dhis2.storage.DomSessionStorageAdapter = function ( options ) {
+        this.storage = window.sessionStorage;
+
+        if ( !(this instanceof dhis2.storage.DomSessionStorageAdapter) ) {
+            return new dhis2.storage.DomSessionStorageAdapter( options );
+        }
+
+        Object.defineProperties( this, {
+            'name': {
+                value: options.name,
+                enumerable: true
+            },
+            'version': {
+                value: options.version,
+                enumerable: true
+            },
+            'objectStoreNames': {
+                value: options.objectStores,
+                enumerable: true
+            },
+            'keyPath': {
+                value: options.keyPath,
+                enumerable: true
+            }
+        } );
+
+        this.customApi = 'Indexer storage'.split( ' ' );
+
+        this.Indexer = function ( name, objectStore, storage ) {
+            return {
+                key: name + '.' + objectStore + '.**index**',
+
+                all: function () {
+                    var a = storage.getItem( this.key );
+
+                    if ( a ) {
+                        try {
+                            a = JSON.parse( a );
+                        } catch ( e ) {
+                            a = null;
+                        }
+                    }
+
+                    if ( a == null ) {
+                        storage.setItem( this.key, JSON.stringify( [] ) );
+                    }
+
+                    return JSON.parse( storage.getItem( this.key ) );
+                },
+
+                add: function ( key ) {
+                    var a = this.all();
+                    a.push( key );
+                    storage.setItem( this.key, JSON.stringify( a ) );
+                },
+
+                remove: function ( key ) {
+                    var a = this.all();
+
+                    if ( a.indexOf( key ) != -1 ) {
+                        dhis2.array.remove( a, a.indexOf( key ), a.indexOf( key ) );
+                        storage.setItem( this.key, JSON.stringify( a ) );
+                    }
+                },
+
+                find: function ( key ) {
+                    var a = this.all();
+                    return a.indexOf( key );
+                },
+
+                // warning: will just delete index, no check
+                destroy: function () {
+                    storage.removeItem( this.key );
+                }
+            }
+        };
+
+        return this;
+    };
+
+    Object.defineProperties( dhis2.storage.DomSessionStorageAdapter.prototype, {
+        'open': {
+            value: function () {
+                var self = this;
+                var deferred = $.Deferred();
+
+                self.indexer = {};
+
+                $.each( self.objectStoreNames, function ( idx, item ) {
+                    self.indexer[item] = self.Indexer( self.name, item, self.storage );
+                } );
+
+                deferred.resolveWith( self );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'set': {
+            value: function ( store, object ) {
+                var self = this;
+
+                if ( typeof object === 'undefined' || typeof object[self.keyPath] === 'undefined' ) {
+                    throw new Error( 'Invalid object' );
+                }
+
+                object = JSON.parse( JSON.stringify( object ) );
+
+                var key = object[self.keyPath];
+                delete object[self.keyPath];
+
+                key = this.name + '.' + store + '.' + key;
+                if ( this.indexer[store].find( key ) == -1 ) this.indexer[store].add( key );
+
+                var deferred = $.Deferred();
+
+                try {
+                    this.storage.setItem( key, JSON.stringify( object ) );
+                    deferred.resolveWith( self, [ object] );
+                } catch ( err ) {
+                    deferred.rejectWith( self, [ err ] );
+                }
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'setAll': {
+            value: function ( store, arr ) {
+                var self = this;
+                var deferred = $.Deferred();
+                var chained = deferred.then();
+
+                $.each( arr, function ( idx, item ) {
+                    chained = chained.then( function () {
+                        return self.set( store, item );
+                    } );
+                } );
+
+                deferred.resolveWith( this );
+
+                return chained;
+            },
+            enumerable: true
+        },
+        'get': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof key === 'undefined' ) {
+                    throw new Error( 'Invalid key: ', key );
+                }
+
+                var deferred = $.Deferred();
+                var object = this.storage.getItem( this.name + '.' + store + '.' + key );
+
+                if ( object ) {
+                    object = JSON.parse( object );
+                    object[this.keyPath] = key;
+                }
+
+                deferred.resolveWith( self, [ object ] );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'getAll': {
+            value: function ( store, predicate ) {
+                var self = this;
+                var deferred = $.Deferred();
+                var idx = this.indexer[store].all();
+                var objects = [];
+
+                if ( typeof predicate !== 'undefined' ) {
+                    // just log and continue
+                    console.log( 'predicate filtering is currently not supported in dom storage getAll, returning all' );
+                }
+
+                for ( var i = 0, len = idx.length; i < len; i++ ) {
+                    var object = this.storage.getItem( idx[i] );
+
+                    if ( object ) {
+                        object = JSON.parse( object );
+                        object[this.keyPath] = idx[i].replace( self.name + '.' + store + '.', '' );
+                        objects.push( object );
+                    }
+                }
+
+                deferred.resolveWith( self, [ objects ] );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'getKeys': {
+            value: function ( store ) {
+                var self = this;
+                var deferred = $.Deferred();
+
+                var keys = this.indexer[store].all().map( function ( r ) {
+                    return r.replace( self.name + '.' + store + '.', '' )
+                } );
+
+                deferred.resolveWith( self, [ keys ] );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'delete': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof key === 'undefined' ) {
+                    throw new Error( 'Invalid key: ', key );
+                }
+
+                var deferred = $.Deferred();
+
+                key = this.name + '.' + store + '.' + key;
+                this.indexer[store].remove( key );
+                this.storage.removeItem( key );
+
+                deferred.resolveWith( self );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'clear': {
+            value: function ( store ) {
+                var self = this;
+                var deferred = $.Deferred();
+
+                this.getKeys( store ).done( function ( keys ) {
+                    $.each( keys, function ( idx, item ) {
+                        self.delete( store, item );
+                    } );
+
+                    deferred.resolveWith( self );
+                } );
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'contains': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof key === 'undefined' ) {
+                    throw new Error( 'Invalid key: ', key );
+                }
+
+                key = this.name + '.' + store + '.' + key;
+                var deferred = $.Deferred();
+                deferred.resolveWith( self, [ this.indexer[store].find( key ) != -1 ] );
+
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'count': {
+            value: function ( store, key ) {
+                var self = this;
+
+                if ( typeof key !== 'undefined' ) {
+                    throw new Error( 'key based count is not supported by DomSessionStorageAdapter' );
+                }
+
+                var deferred = $.Deferred();
+                deferred.resolveWith( self, [ this.indexer[store].all().length ] );
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'close': {
+            value: function () {
+                var deferred = $.Deferred();
+                deferred.resolve();
+                return deferred.promise();
+            },
+            enumerable: true
+        },
+        'destroy': {
+            value: function () {
+                var self = this;
+                var deferred = $.Deferred();
+
+                $.each( self.objectStoreNames, function ( idx, item ) {
+                    self.clear( item );
+                    self.indexer[item].destroy();
+                } );
+
+                deferred.resolve();
+                return deferred.promise();
+            },
+            enumerable: true
+        }
+    } );
+
+    Object.defineProperties( dhis2.storage.DomSessionStorageAdapter, {
+        'isSupported': {
+            value: function () {
+                return !!window.sessionStorage;
+            },
+            enumerable: true
+        }
+    } );
+})( jQuery, window, document );

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/main.vm'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/main.vm	2013-04-29 13:14:35 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/main.vm	2013-05-02 13:55:39 +0000
@@ -47,9 +47,11 @@
     <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.availability.js"></script>
     <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.trigger.js"></script>
     <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.sharing.js"></script>
-    <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.storage.js"></script>
-    <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.storage.dom-ss.js"></script>
-    <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.storage.dom.js"></script>
+    <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.storage.ss.js"></script>
+    <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.storage.ls.js"></script>
+    <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.storage.idb.js"></script>
+    <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.storage.memory.js"></script>
+      <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.storage.js"></script>
     <script type="text/javascript" src="../dhis-web-commons/i18nJavaScript.action"></script>
     <script type="text/javascript" src="../main.js"></script>
     <script type="text/javascript" src="../request.js"></script>