Currently Being Moderated

Hello again, this is another blog post in the series that Chris Paine and I are writing about our mobile application working with Netweaver Cloud. In my previous post I covered the basics of creating the application in PhoneGap, with the promise to cover more on local storage and how to handle data discrepancies between local versions and server versions.

 

What is Local Storage?

 

With HTML5, we have been introduced to this nifty little thing called local storage. Well the name is pretty self explanatory; local storage is the ability to store data on the device. This comes in really handy in our Timesheet app because we wanted to store settings, projects and entries on the device so the user could still use the app while offline.

 

The main three statements you will be using in local storage are the following:

 

localStorage.setItem(key, value);

var value = localStorage.getItem(key);

localStorage.removeItem(key);

 

How am I using it?

 

I'm glad you asked! Local storage plays a major part in the app, as it holds not only the entries that a user has created/changed/deleted, projects and settings, but it also stores the authentication data to communicate with the server. As Chris has mentioned earlier in his blog post, we are using basic authentication when communicating with the server through ajax calls. In a nutshell, when a new device is added, a pairing ID is provided which is then input into the app. We use that pairing ID to get an id and secret (which serves as the password) and store it on the device, so it can be used for authentication every time an ajax call is made.

 

User settings, such as default hours, default start and end times, etc can be shared across a user's devices. These settings are stored on the device once they have successfully paired it. The same goes for any existing entries a user has, and projects. The example is shown below of the ajax call to pair and the subsequent functions to save the settings, entries and projects.

 

$.mobile.showPageLoadingMsg();

$.ajax({

  url: url,

  dataType: 'json',

  crossDomain: true,

  success: function(data) {

    $.mobile.hidePageLoadingMsg();

 

    // Save the id and secret to local storage

    localStorage.setItem("PairingID", data.Devices.Id);

    localStorage.setItem("PairingPWD", data.Devices.Secret);

    localStorage.setItem("UserID", data.Devices.AssociatedUserId);

 

    // Now load the settings for the first time with defaults

    loadInitialSettings();

    loadInitialEntries();

    loadProjectsFromServer();

 

    $('#pop_pair_device').popup('close');

    $.mobile.changePage("#home", { transition: "flip"});

  },

  error: function(xhr, options, error) {

    $.mobile.hidePageLoadingMsg();

 

    alert(xhr.status);

    alert(error);

  }

});


function loadInitialSettings()

{

    var url = baseURL + '/UserSettings/' + localStorage["UserID"];

    alert(url);

    $.ajax({

        url: url,

        dataType: 'json',

        crossDomain: true,

        beforeSend: function(xhr) {

           var requestAuth = "Basic " + encodeBase64(localStorage["PairingID"] + ":" + localStorage["PairingPWD"]);

           xhr.setRequestHeader("Authorization", requestAuth);

        },

        success: function(data, textStatus) {

           alert(textStatus + ' getting initial settings ' + data);

           $.mobile.hidePageLoadingMsg();

 

           localStorage.setItem("DefaultEntryType", data.defaultEntryType);

           localStorage.setItem("DefaultStartTime", data.defaultStartTime);

           localStorage.setItem("DefaultEndTime", data.defaultEndTime);

           localStorage.setItem("DefaultHours", data.defaultHours);

           localStorage.setItem("email", data.email);

           localStorage.setItem("DefaultBillable", data.defaultBillable);

           localStorage.setItem("Monday Checked", data.monDetails.default);

           localStorage.setItem("MonStartTime", data.monDetails.startTime);

           localStorage.setItem("MonEndTime", data.monDetails.endTime);

           localStorage.setItem("MonHours", data.monDetails.hours);

           localStorage.setItem("Tuesday Checked", data.tueDetails.default);

           localStorage.setItem("TuesStartTime", data.tueDetails.startTime);

           localStorage.setItem("TuesEndTime", data.tueDetails.endTime);

           localStorage.setItem("TuesHours", data.tueDetails.hours);

           localStorage.setItem("Wednesday Checked", data.wedDetails.default);

           localStorage.setItem("WedStartTime", data.wedDetails.startTime);

           localStorage.setItem("WedEndTime", data.wedDetails.endTime);

           localStorage.setItem("WedHours", data.wedDetails.hours);

           localStorage.setItem("Thursday Checked", data.thuDetails.default);

           localStorage.setItem("ThursStartTime", data.thuDetails.startTime);

           localStorage.setItem("ThursEndTime", data.thuDetails.endTime);

           localStorage.setItem("ThursHours", data.thuDetails.hours);

           localStorage.setItem("Friday Checked", data.friDetails.default);

           localStorage.setItem("FriStartTime", data.friDetails.startTime);

           localStorage.setItem("FriEndTime", data.friDetails.endTime);

           localStorage.setItem("FriHours", data.friDetails.hours);

           localStorage.setItem("Saturday Checked", data.satDetails.default);

           localStorage.setItem("SatStartTime", data.satDetails.startTime);

           localStorage.setItem("SatEndTime", data.satDetails.endTime);

           localStorage.setItem("SatHours", data.satDetails.hours);

           localStorage.setItem("Sunday Checked", data.sunDetails.default);

           localStorage.setItem("SunStartTime", data.sunDetails.startTime);

           localStorage.setItem("SunEndTime", data.sunDetails.endTime);

           localStorage.setItem("SunHours", data.sunDetails.hours);

 

        },

        error: function(xhr, options, error) {

           $.mobile.hidePageLoadingMsg();

 

           alert(xhr.status);

           alert(error);

        }

    });

 

}

 

function loadProjectsFromServer()

{

    var url = baseURL + '/projects/' + localStorage["UserID"];

    $.ajax({

        url: url,

        dataType: 'json',

        crossDomain: true,

        beforeSend: function(xhr) {

           var requestAuth = "Basic " + encodeBase64(localStorage["PairingID"] + ":" + localStorage["PairingPWD"]);

           xhr.setRequestHeader("Authorization", requestAuth);

        },

        success: function(data, textStatus) {

           alert(textStatus + ' for projects load');

           $.each(data.projects, function (index, value) {

                // Save to local storage

                var key = 'Project_' + value.projectCode;

                localStorage.setItem(key, value.projectName);

           });

        },

        error: function(xhr, options, error) {

            $.mobile.hidePageLoadingMsg();

 

            alert(xhr.status);

            alert(error);

        }

    });

}

 

function loadInitialEntries()

{

    alert("Inside loadInitialEntries");

    var url = baseURL + '/entries/' + localStorage["UserID"];

    alert(url);

    $.ajax({

        url: url,

        dataType: 'json',

        crossDomain: true,

        beforeSend: function(xhr) {

           var requestAuth = "Basic " + encodeBase64(localStorage["PairingID"] + ":" + localStorage["PairingPWD"]);

           xhr.setRequestHeader("Authorization", requestAuth);

        },

        success: function(data, textStatus) {

           alert(textStatus + ' getting initial entries ' + data);

           $.mobile.hidePageLoadingMsg();

 

           $.each(data.entries, function (index, value) {

                $.each(value.lineEntries, function (index, value) {

                    var key = value.date + delimiter + value.id;

                    var values = value.date + delimiter;

                    values += value.projectCode + delimiter;

                    values += value.hours + delimiter;

                    values += value.startTime + delimiter;

                    values += value.endTime + delimiter;

                    values += value.comments + delimiter;

                    values += value.billable + delimiter;

                    values += value.status + delimiter;

                    values += value.modified;

 

                    // First check if the date modified is more recent than the one in storage

                    var currItem = localStorage.getItem[key];

                    var currValues = currItem.split(delimiter);

                    var localDate = new Date(currValues[8]);

                    var serverDate = new Date(value.modified);

                    if(localDate > serverDate)

                    {

                        $('#pop_msg').html('Are you sure you want to overwrite the entry for ' + value.date + ' with an older version?');

                        $('#pop_overwrite').popup('open', 'pop' 'window');

                    }

                    else

                    {

                       localStorage.setItem(key, values);

                    }

                });

 

           });

 

           initializeDates();

 

        },

        error: function(xhr, options, error) {

           $.mobile.hidePageLoadingMsg();

 

           alert(xhr.status);

           alert(error);

        }

    });

 

 

}

 

 

As you can see, all the ajax calls are done with basic authentication using the id and secret received earlier which has been encoded. All data is sent and received in JSON format, so when a GET is performed, all I had to do to save to local storage was assign the relevant data to storage keys.

 

Something you need to know about local storage

 

Since all the stuff we've been looking at is in JavaScript land, all the variables are type agnostic. However in local storage, you are only allowed to save in a string format. So if you take your JSON data that has just been received and try to save it directly to storage, it won't work. If you'd like to save an entire JSON object into local storage, all you have to do is convert it to a string; I've used JSON.stringify(<JSONobject>) and you can find the source .js here).

 

Other than that little piece of information, everything about local storage is very straightforward.

 

Comparing values

 

When you are downloading data from the server onto your device, you need to consider if that data that is coming from the server is more up to date or not. In our case, settings and projects would always be downloaded and saved as this data would always be saved on a device and immediately updated on the server. So the information would always be accurate coming back.

 

However, in the case of time entries, we need to check the integrity of the data because the app provides only the option to submit all entries at once. Let's take for example the scenario where I have an entry on my iPhone for the 21st of January and I've saved and sent it to the server. Say for instance I also have that entry downloaded on my iPad because I set it up as another device. I modify the same entry on the iPad and send it to the server.

 

Then I go back to my iPhone (because I have a short attention span) and change the same entry on it. Now, if I perform a sync on my iPhone, which will download all the data for my user, the version for that entry is more up to date on my iPhone than the one coming from the server. This is when we introduced the modified date into the JSON object, and made it into a UTC date so the time zone doesn't make a difference.

 

In this case, when I sync, the app will ask me if I want to overwrite my data with older data from the server. If I choose yes, the data will be overwritten, but if not I can complete my changes and send it to the server first before syncing. This is the bit of code that is doing the check:

 

var localDate = new Date(currValues[8]);

var serverDate = new Date(value.modified);

if(localDate > serverDate)

{

    $('#pop_msg').html('Are you sure you want to overwrite the entry for ' + value.date + ' with an older version?');

    $('#pop_overwrite').popup('open', 'pop' 'window');

}

else

{

    localStorage.setItem(key, values);

}

 

Obviously it might get a bit tedious checking for every discrepancy, so if you find the volume is too high, consider implementing a global overwrite flag so they can just say something to the effect of "Replace All".

 

So there you have it. Local storage is a powerful tool that can be used in your web/hybrid apps, as it reduces your dependency on a server connection, and it's so easy to implement.

 

Key Learnings


  • Local storage values are only saved in string format
  • Local storage values will remain on the device until the app is deleted
  • When getting data from a server, you may need to check for the most recent data before overwriting
  • Consider a sync option so the user can manually decide whether or not to download data

 

Hope you enjoyed this blog! Stay tuned for more from Chris and me.

 

Disclaimer: The above code, examples etc. are supplied with no guarantee to them actually being fit for use. Copy at your peril!

 


Comments

Actions

Filter Blog

By author:
By date:
By tag: