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:

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.

WordPress Blog Configured For Success

As promised in my previous post, I got with my good friend Devin Walker, Co-Founder and Creative Technologist at ThoughtHouse, and picked his brain on what I should be doing do get this WordPress blog configured for success. Not surprisingly, Devin had some great insight for me on what I should be taking into account. Here is what I came away with…

Theme

WordPress was originally designed as a blogging engine but has now grown into an extremely versatile CMS. As a result, themes are now being developed to really focused on the type of content that is going to be published. A blog should definitely be using a WP theme that is more “blog focused” opposed to a theme perhaps designed for portfolio showcasing or business marketing.

After some deliberation, I decided to go with a totally free theme called Edin. It appealed to me for a number of reasons but ultimately won out for the following reasons:

  • Content Focused: This theme is visually simplistic in a good way. There is very little to distract readers.
  • Responsive Design: Yes, “responsive design” it probably the most overhyped tech term of 2014, but it is important to serve your content up to a diverse context of consumers.
  • Menu Options: In short, the menu options are easily configurable and feature rich. Very cool.

Plugins

The WordPress community is big… really big. As a result there is an overwhelming number of free and premium plugins available to enhance the out of the box features WordPress provides. Luckily, some of the best plugins available today are free and well supported. Currently I’m utilizing an arsenal that is focused on web performance, SEO, and code sharing. The two listed below are my current favorites.

  • WP Super Cache: I’m big into web performance and this simple plugin is a perfect (and in my eyes crucial)  solution to handling the dynamic nature WordPress. In short, instead of making round trips to the database and dynamically rendering html on the server then sending to the client on every request, this plugin caches static html on the server and serves up the cached content. Pretty rad.
  • WordPress SEO: Holly SEO Batman! This plugin by Yoast provides immediate analysis on your content, even before you publish. I’m still digging into what the full extent of this plugins capabilities are, but it has already had an impact on how I am writing and structuring content.