8

I have a simple contact form, which Angular submits via AJAX to my app on Google App Engine. (A POST handler uses the send_mail function to email the website owner). This is the client code:

$http.post('', jQuery.param(user), {
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
}).success(function(data, status, headers, config) {
    //...                        
}).error(function(data, status, headers, config) {
    alert('Please check your Internet connection and try again.');
});

Obviously the alert() is handling all errors. Assuming no errors in my server-side code, I'm guessing the chances of App Engine returning anything other than an HTTP status code of 200 is low. However I would still like to distinguish between server errors and the user having lost their connection.

I was thinking of using XMLHttpRequest's textStatus as per this SO answer, but it doesn't appear to be available to the $http.error() callback. I also thought of using Offline.js but I don't need most of what it does so that would seem like wasted bytes in this case.

The $http.error() status I get when offline is 0, but I'm not sure how cross-browser reliable that's going to be. What should I be using?

Community
  • 1
  • 1
KnewB
  • 353
  • 4
  • 16
  • About support: http://caniuse.com/#search=onLine – artur grzesiak Dec 15 '14 at 21:23
  • Yes, you should be checking for http status codes, but not necessarily for status 0. Check for valid ranges of codes. See http://stackoverflow.com/questions/23851424/how-to-detect-when-online-offline-status-changes. – user2943490 Dec 22 '14 at 06:07

3 Answers3

5

Before giving you the solution I just wanted to highlight the problems with browser provided is-offline flag

  1. Different browser's offline flag has different meanging

    a. for some browsers it means internet is not there

    b. for some browsers it means intranet is not there

    c. some browsers do allow offline mode, so even if there is no internet, you are not actually offline.

  2. not all browsers support offline in consistent way

Another problem I had with using browser based flag is development scenario, where having internet is not necessary, and that should not trigger the offline mode (for me I block all user interaction If my website goes offline). You can solve this problem by having another indicator telling you if you are in dev/prod, etc.

And most imp part, why do we care to find if browser is in offline mode, is because we do care only if our website is reachable or not, we don't actually care if the internet is there or not. So even if browser tell us it is offline, it is not exactly what we want. there is a tiny difference between what we want and what browser provides.

So considering all of above, I have solved the problem using an offline directive which I am using to block user interaction if user is offline

csapp.directive('csOffline', ["$http", '$interval', "$timeout", "$modal", function ($http, $interval, $timeout, $modal) {
    var linkFn = function (scope) {

        scope.interval = 10; //seconds

        var checkConnection = function () {

            $http({
                method: 'HEAD',
                url: document.location.pathname + "?rand=" + Math.floor((1 + Math.random()) * 0x10000)
            })
                .then(function (response) {
                     $scope.isOnline = true;
                }).catch(function () {
                     $scope.isOnline = false;
                });
        };

        scope.isOnline = true;

        $interval(checkConnection, scope.interval * 1000);

        scope.$watch('isOnline', function (newVal) {
            console.log("isOnline: ", newVal);
            //custom logic here
        });

    };

    return {
        scope: {},
        restrict: 'E',
        link: linkFn,
    };
}]);

I was about to use offline.js, it was too much and most of which I didnt need, so this is the solution I came up with which is purely in angular.js.

please note that http interceptor is invoked during these calls, I wanted to avoid that, hence I had used $.ajax for the calls

        $.ajax({
            type: "HEAD",
            url: document.location.pathname + "?rand=" + Math.floor((1 + Math.random()) * 0x10000),
            contentType: "application/json",
            error: function () {
                scope.isOnline = false;
            },
            success: function (data, textStatus, xhr) {
                var status = xhr.status;
                scope.isOnline = status >= 200 && status < 300 || status === 304;
            }
        }
        );

you can replace the logic inside isOnline true/false, with whatever custom logic you want.

harishr
  • 17,807
  • 9
  • 78
  • 125
  • IMHO polling the server shouldn't be necessary, but I guess it's the only way! I'd need it to be more responsive though as it my take less than 30 seconds for a user to fill out and submit the form. What would be a safe minimum to pass to `$interval`? – KnewB Dec 22 '14 at 16:14
  • Is `scope.interval` surplus to requirement? – KnewB Dec 22 '14 at 16:14
  • 1
    polling is needed, because each browser has its own way of telling the user if its offline, also for each browser offline means different things, like no internet or no intranet, so its better to have api based implementation. [read more here](http://stackoverflow.com/questions/3181080/how-to-detect-online-offline-event-cross-browser) – harishr Dec 22 '14 at 16:18
  • 1
    $inteval is totally upto you , I used 10 seconds, you can use any value you like. this api or call would take very less (few milliseconds) to execute and is cross browser – harishr Dec 22 '14 at 16:19
  • 1
    ah I see, is it meant to be `$interval(checkConnection, scope.interval);`? – KnewB Dec 22 '14 at 16:21
3

I'd go with response.status === 0 check. I've tested it using the code below (needs to be put on a webserver that you can switch on/off at will) and I'm getting 0 in current versions of Google Chrome, Mozilla Firefox, and Internet Explorer. You may use it to test all browsers you want to support.


Code for testing connection status:

<!DOCTYPE html>
<html>
    <head>
        <title>Connection status test</title>
        <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.5/angular.min.js"></script>
        <script type="text/javascript">
            var log = [],
                pendingCount = 0,
                pendingLimit = 5;
            angular.module('app', [])
                .run(function ($rootScope) {
                    $rootScope.log = log;
                })
                .run(function ($http, $interval, $rootScope) {
                    $interval(function () {
                        if (pendingCount >= pendingLimit) {
                            return;
                        }

                        var item = {
                            time: Date.now(),
                            text: 'Pending...'
                        };
                        ++pendingCount;
                        $http.get('.', {})
                            .then(function () {
                                item.text = 'Done';
                            }, function (response) {
                                item.text = 'Done (status ' + response.status + ')';
                            })
                            .finally(function () {
                                --pendingCount;
                            });
                        log.unshift(item);
                    }, 1000);
                });
        </script>
        <style rel="stylesheet" type="text/css">
            ul {
                list-style: none;
                padding: 0;
            }
        </style>
    </head>
    <body ng-app="app">
        <ul>
            <li ng-repeat="item in log">{{item.time | date:'HH:mm:ss'}}: {{item.text}}</li>
        </ul>
    </body>
</html>
hon2a
  • 7,006
  • 5
  • 41
  • 55
  • I'll play with your code out of interest/to learn, although unfortunately the thought of testing on a myriad of browser/device combinations scares me – KnewB Dec 22 '14 at 15:53
-3

try this one

$http.post('', jQuery.param(user), {
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
}).complete(function(data, status, headers, config) {
    //...                        
});
Nurdin
  • 23,382
  • 43
  • 130
  • 308