Offline Apps: Detecting When the User Is Connected - JUMP START HTML5 (2014)

JUMP START HTML5 (2014)

Chapter 22 Offline Apps: Detecting When the User Is Connected

Perhaps the hardest part of building an app that works offline is actually determining when the user is offline.

In this chapter, we’ll cover the ononline and onoffline events of the window object and the window.navigator.onLine property. We’ll also talk about their limits as well as an alternative way to test for connectivity.

Determining Whether the User Is Online

The window.navigator.onLine property tells us whether or not the user is connected to a network. It returns a Boolean true or false value:

if(navigator.onLine){

console.log('We are connected!');

} else {

console.log('We don't have connectivity.');

}

We can use this property with other offline APIs to determine when and whether to store data locally. Then we can sync it with a server once we have connectivity again.

Anytime the value of navigator.onLine changes, it fires one of two events:

· offline: when the value of navigator.onLine would be false

· online: when the value of navigator.onLine would be true

We can listen for these events, of course, and take action when one is fired.

Listening for Changes in Connectivity State

In Firefox, Internet Explorer 9+, and Safari, the online and offline events are fired on the document.body object. In Chrome and Opera 15+, however, they are fired on the window object.

The good news is that even when fired on the document.body, these online and offline events “bubble up” to the window object. This means we can listen for events fired there instead of setting event listeners or handlers for both.

There are two options in listening for our online and offline events. We can use the event attributes onoffline and ononline, as shown here:

window.ononline = function(){

// Alert the user that connectivity is back.

// Or sync local data with server data.

}

window.onoffline = function(){

// Alert the user that connectivity was lost.

// Or just silently save data locally.

}

Or we can use the addEventListener method and listen for the online or offline event:

window.addEventListener('offline', offlineHandler);

window.addEventListener('online', onlineHandler);

The addEventListener method requires two parameters: the event we want to listen for, and the function to invoke ― known as a callback ― when the event occurs.

For compatibility with some older versions of Firefox (< 6.0) and Opera (< 11.6), you will need to add a third parameter. This parameter tells the browser whether to use event capture or event bubbling. A value of true means that events should be captured. A false value means that events should bubble. In general, you should set it to false.

Most modern browsers use event bubbling by default, making the third parameter optional in those browsers. If you’d like to know more about bubbling versus capture, read Peter-Paul Koch’s 2006 article, “Event order.”

Online and Offline Events in Internet Explorer 8

The exception to this rule is Internet Explorer 8. IE8 fires online and offline events at the document.body object, but they do not bubble. What’s more, Internet Explorer doesn’t support addEventListener(). You’ll need to use the attachEvent() method instead, or a polyfill that smooths over the differences.

That said, the only component of offline applications that Internet Explorer supports is web storage (which we’ll cover in Chapter 24). Internet Explorer 8 does have some vendor-specific storage features, but in the interest of promoting web standards and shortening the length and complexity of this book, we will not cover them here.

Limitations of navigator.onLine

Here’s the downside of navigator.onLine: it’s unreliable. What’s more, it works differently depending on the browser and operating system.

Chrome, Safari, Opera 15+, and Internet Explorer 9+ can detect when the user has lost connectivity with the network. Losing or turning off a Wi-Fi connection will trigger an offline event in those browsers. Reconnecting will trigger an online event. The value of navigator.onLine will betrue if connected to the Internet, and false if it is not.

Opera 12 (and older versions) will not fire online or offline events at all. It does, however, report false for navigator.onLine when the user is disconnected from a network and true when connected.

Firefox for desktop and laptop computers is a special case. Whether it fires offline or online events depends on user activity. Firefox fires an offline event when the user selects the Work Offline menu option, and fires an online event when the user deselects it. It’s wholly unrelated to whether there is a functional internet connection. Even if the user has disconnected from the network, the value of navigator.onLine will be true if the user hasn’t also elected to work offline.

Firefox for Android and Firefox OS work differently, however. Those versions of the browser will fire online and offline events when connectivity is lost or turned off (as with Airplane Mode).

Where all browsers struggle, however, is when there is network connectivity but not internet connectivity. If the browser can connect to a local network, the value of navigator.onLine will be true, even if the connection from the local network to the wider internet is down.

In other words, the navigator.onLine property may not tell you what you want to know. So we need to use an alternative.

Checking Connectivity With XMLHttpRequest

A better way to determine connectivity is to do it at the time of a network request. We can do this using XMLHttpRequest and its error and timeout events.

In this example, we’ll attempt to send the contents of our form to the server. If we have no connection, or if the connection takes too long to complete, we’ll save our data to localStorage and tell the user.

First, let’s define our error and timeout event handler:

var form = document.querySelector('form');

var xhrerrorhandler = function(errorEvent){

var i = 0;

// Save the value if we have one

while(i < form.length){

if(form[i].value != ''){

localStorage.setItem(form[i].name, form[i].value);

}

i++;

}

}

We’ll use the same function for both events, since we want to do the same task in both cases. This function saves each form field name as a localStorage key, and each field’s corresponding value as a localStorage value.

Next, let’s define our submit event handler. This function will make the XHR request, monitor it for failure, and add our error handlers:

submithandler = function(submitEvent){

var fd, ls, i=0, key;

// Prevent form submission

submitEvent.preventDefault();

// If we have localStorage data, build a FormData object from it

// Otherwise, just use the form

if( localStorage.length > 0){

fd = new FormData();

ls = localStorage.length;

while(i < ls){

key = localStorage.key(0);

fd.append(key, localStorage.getItem(key));

i++;

}

} else {

fd = new FormData(form);

}

// Set a timeout for slow connections

xhr.timeout = 3000;

xhr.open('POST','return.php');

xhr.send(fd);

// Set our error handlers.

xhr.addEventListener('timeout', xhrerrorhandler);

xhr.addEventListener('error', xhrerrorhandler);

}

form.addEventListener('submit', submithandler);

In this function, if there is localStorage data, we will append it to a FormData object. Then we’ll send the FormData object as the payload for our request.

FormData is part of the XMLHttpRequest, Level 2 specification. It accepts a series of key-value pairs using its append() method (append(key,value)). Alternatively, you can pass a form object as an argument to the constructor. Here we’ve done both, based on whether or not we have saved anything to localStorage. A more robust version of this demo might handle this with separate features: one for sending form data and another for syncing with the server.

This doesn’t work if the server response contains an HTTP error code. You can work around this by checking the value of the status property in the XHR response, as shown here:

var xhronloadhandler = function(loadEvent){

if( loadEvent.target.status >= 400){

xhrerrorhandler();

} else {

// Parse the response.

}

}

If it’s 400 or greater―HTTP error codes start at 400―we’ll save the data locally. Otherwise, parse the response.

What You’ve Learned

In this chapter, we’ve discussed the advantages and limitations of the navigator.onLine property, an alternate way to test for connectivity. Now let’s talk about the browser features and APIs that let us create offline web applications. In our next chapter, we’ll look at the application cache.