Offline Web Apps – Part 2: navigator.onLine

In Part 1 of this two part blog post, I covered a few of the basic fundamentals of offline web apps. Specifically, how to use a cache manifest to cache application resources offline in the browser. In part 2 we will dive into the how to ensure your application is intelligent enough to handle offline scenarios using navigator.onLine.

Meet navigator.onLine

Caching application resources is a nice feature provides end users with the ability to have client side interactions with your application while they are offline. But what happens when your application needs to communicate with a server? To do so, you need to write javascript code that will take into account how to handle user driven events based on online/offline status. With the HTML5, there is a helpful state property of the Navigator object onLine along with two events that get fired (ononline/onoffline) when the connection status changes.

A Real-world Example

Let’s say for example you have a client side application where users enter large amounts of text content that gets saved to a server somewhere. In order to prevent users from losing work by an unintended browser close, you want to occasionally “autosave” their data every 30 seconds. This code might look something like this:

window.addEventListener('load', function () {
    var saveInterval = setInterval(function () {
        // Function call (XHR etc) to save data goes here
        ...        
    }, 30000) // Call this function every 30 seconds
});

Unfortunately, as straightforward as this implementation is, it leaves some pretty big gaps:

  • The function call to save data will repeat every 30 seconds regardless of success/failure
  • Users have no idea whether or not their data is indeed being saved to the server

Let’s fix this by using navigator.onLine and the ononline/onoffline events:

window.addEventListener('load', function () {           

    function updateOnlineStatus() {
        var condition = navigator.onLine ? "online" : "offline",
            saveInterval = null;
        if (condition === "online") {
            // Make update to UI here to let user know they are online and autosave is available
            ...
            ...
            saveInterval = setInterval(function () {
                    // Function call (XHR etc) to save data goes here
                       ...
                       ...
                    }, 30000)
                }
                else {
                    // Make update to UI here to let user know they are offline and are not autosaving
                    ...
                    ...
                    // Stop the interval from repeating until connectivty is resolved
                    clearInterval(saveInterval); 
                }
            }

        // Setup event listeners for when online/offline status changes
        window.addEventListener('online', updateOnlineStatus);
        window.addEventListener('offline', updateOnlineStatus);

        // Make initialization call to updateOnlineStatus
        pdateOnlineStatus();
});

With the code above, we have pretty solid coverage for when (and when NOT) to save changes based on connection status. As you can imagine though, the more complex an application is, the more complex these scenarios might become.

Additional Considerations and Resources

Like any other that gets executed in the browser, there are some subtleties to watch out for when it comes to cross browser compatability. At this time of this writing Firefox ignores actual network connectivity and instead triggers on the browser’s “Work Offline” mode. In addition, some browser/operating system combinations may not be intelligent enough to determine the difference between a true network connection and a local network adapter (virtual) showing an online status. For more information, please take a look at the following links:

Detect Device Battery Level via JavaScript

battery-half

I’ve recently come across a handy feature that allows web applications to detect device battery level via javascript from the browser. Currently, the spec for this interface (Battery Status API) is in Working Draft status with W3C but will hopefully see wider adoption in coming months. Google Chrome and Opera have already adopted this feature for desktop and there appears to be early support for Chrome on Android.

At first glance, this might seem like a trivial piece of information that a web app would want to access, but the more I think about it, I see a lot of potential especially in the context of mobile. If an application can detect battery level and charging status it could be smart enough to throttle data requests, save progress to avoid data loss. It could even be used behind the scenes as a diagnostic tool to determine where an application is a power suck.

The Battery Manager Interface

Detecting a device battery level and charging status is done through the Battery Manager interface via a Promise. This object is accessible via the navigator object in the browser. When the promise is returned there are a number of read only properties available are:

  • level (double): Current battery level from 0 to 1.0
  • charging (boolean): Represents if a device is charging
  • chargingTime (unrestricted double): Set to 0 if battery is full or there is no battery attached to the device. Set to Infinity if the battery is discharging.
  • dischargingTime (unrestricted double): Set to Infinity if the battery is charging.

The initial call is very straightforward:

navigator.getBattery().then(function (batt) {
    // Access to interface properties here                
});

Events Available

Along with the read only properties on the interface, there are a handful of events that get fired. They are fairly straight forward and mirror the available properties:

  • onlevelchange (levelchange): Event added to the browser queue when battery level property changes
  • onchargingchange (chargingchange): Event added to the browser queue when charging property changes
  • onchargingtimechange (chargingtimechange): Event added to the browser queue when chargingTime property changes
  • ondischargingtimechange (dischargingtimechange): Event added to the browser queue when dischargingTime property changes

Battery Level via Javascript Example

To accompany this post I’ve put together a quick example that illustrates accessing properties on the interface as well as firing the onlevelchange and onchargingchange events. You can see live demo here (Remember, you will need to use Chrome or Opera).

(function () {

            //#region Constants
            var NOT_APPLICABLE = "N/A";
            //#endregion

            //#region Helper Methods
            function setBatteryLevel(level, callback) {
                document.getElementById("level").innerHTML = "Battery Level: " + level;

                if (callback && typeof (callback) === "function") {
                    callback();
                }
            };

            function setChargingStatus(status, callback) {
                document.getElementById("charging").innerHTML = "Charging Status: " + (status === NOT_APPLICABLE ? NOT_APPLICABLE : (status === true ? "Charging" : "Un-plugged"));

                if (callback && typeof (callback) === "function") {
                    callback();
                }
            };
            //#endregion

            try {

                navigator.getBattery().then(function (battery) {
                    // When initial promise from navigator.getBattery() is recieved set the current statuses.
                    setBatteryLevel(battery.level);
                    setChargingStatus(battery.charging);

                    battery.onlevelchange = function (evt) { // Event info is available as param
                        var _level = battery.level;
                        setBatteryLevel(_level, function () {
                            console.log("Battery level changed: " + _level, battery);
                        });
                    };

                    battery.onchargingchange = function (evt) { // Event info is available as param
                        var _charging = battery.charging;
                        setChargingStatus(_charging, function () {
                            console.log("Battery charging status changed: " + _charging, battery);
                        });
                    }
                });
            }
            catch (e) {
                // Catch the error if navigator.getBattery isn't defined
                document.getElementById("error").innerHTML = "Unable to retrieve battery status. You may be using an incompatible browser.

\
            At the time of this demo, the Battery Status API is only available in Google Chrome 38+ or Opera 25+ and is in Working Draft status.

\
            Error Message: " + e;

                // Update
                setBatteryLevel(NOT_APPLICABLE);
                setChargingStatus(NOT_APPLICABLE);

            }
        })();

Want to Learn More?

The following are a few valuable links on the subject:

Sorting Tables Using KnockoutJS

I recently came up with a useful method for sorting tables using KnockoutJS that I felt was worth sharing. Supporting sorting of a grid/table is a pretty common feature you see on the web. However, most implementations are using an AJAX callback (or sometimes a full postback) to request the data in the desired sort pattern. The example I outline here uses KnockoutJS’ client side data binding and dependency tracking to sort efficiently without extra trips to the server. (See Demo)

Setting up the ViewModel

There are two key object properties on the viewmodel that make the sorting possible: columns (self.columns) and data (self.players). Since we are using KnockoutJS, both of the objects are instances of observableArrays:

// Observable array that represents each column in the table
self.columns = ko.observableArray([
    { property: "firstName", header: "First Name", type: "string", state: ko.observable("") },
    { property: "lastName", header: "Last Name", type: "string", state: ko.observable("") },
    { property: "dob", header: "Date of Birth", type: "date", state: ko.observable("") },
    { property: "wsWon", header: "World Series Championships", type: "number", state: ko.observable("") },
    { property: "stats.hr", header: "Home Runs", type: "object", state: ko.observable("") },
    { property: "stats.avg", header: "Batting Average", type: "object", state: ko.observable("") }
]);

// Observable array that will be our data
self.players = ko.observableArray([
    { firstName: "Micky", lastName: "Mantle", dob: "10/20/1931", wsWon: "7", stats: { hr: "536", avg: ".298" } },
    { firstName: "Ken", lastName: "Griffey Jr.", dob: "11/21/1969", wsWon: "0", stats: { hr: "630", avg: ".284" } },
    { firstName: "Derek", lastName: "Jeter", dob: "6/26/1974", wsWon: "5", stats: { hr: "260", avg: ".310" } },
    { firstName: "Lenny", lastName: "Dykstra", dob: "2/10/1963", wsWon: "1", stats: { hr: "81", avg: ".285" } },
    { firstName: "Ty", lastName: "Cobb", dob: "12/18/1886", wsWon: "0", stats: { hr: "117", avg: ".367" } }
]);

Notice how each object in the self.columns observable array has a key “property” that’s value corresponds to a key (or in some cases a child object and key) in the object that makes of the observable array of self.players. It is this relationship that we will capitalize on.

Binding Data in the View

In the view, we use two separate KnockoutJS “foreach” data binds to define our table. The first in the table header that builds the headers for each column. The second in the table body that fills the table body with rows of data:

After the bindings are applied (and a little Twitter Bootstrap CSS) the output in the browser looks like this:Sortable Table

Performing Sorts for Different Data Types

One of key features in this implementation is ability to sort different data types. JavaScript’s Array.sort method is a handy feature (read more about it here) of the language but unfortunately needs different compare functions for different data types. Below are the different sorts based on data type:

 // Generic sort method for numbers and strings
 self.stringSort = function (column) { // Pass in the column object

     self.players(self.players().sort(function (a, b) {

         // Set strings to lowercase to sort in a predictive way
         var playerA = a[column.property].toLowerCase(), playerB = b[column.property].toLowerCase();
         if (playerA < playerB) {
             return (column.state() === self.ascending) ? -1 : 1;
         }
         else if (playerA > playerB) {
             return (column.state() === self.ascending) ? 1 : -1;
         }
         else {
             return 0
         }
     }));
 };

 // Sort numbers
 self.numberSort = function (column) {
     self.players(self.players().sort(function (a, b) {

         var playerA = a[column.property], playerB = b[column.property];
         if (column.state() === self.ascending) {
             return playerA - playerB;
         }
         else {
             return playerB - playerA;
         }
     }));
 };

 // Sort by date
 self.dateSort = function (column) {

     self.players(self.players().sort(function (a, b) {

         if (column.state() === self.ascending) {
             return new Date(a[column.property]) - new Date(b[column.property]);
         }
         else {
             return new Date(b[column.property]) - new Date(a[column.property]);
         }
     }));
 };

 // Using a deep get method to find nested object properties
 self.objectSort = function (column) {

     self.players(self.players().sort(function (a, b) {

         var playerA = self.deepGet(a, column.property),
         playerB = self.deepGet(b, column.property);

         if (playerA < playerB) {
             return (column.state() === self.ascending) ? -1 : 1;
         }
         else if (playerA > playerB) {
             return (column.state() === self.ascending) ? 1 : -1;
         }
         else {
             return 0
         }
     }));
 };

See the Code In Action

To see this example in action please check out the demo. The full code is also available on GitHub.