Preventing tab-close data loss in JS Web Apps

Often, when you're building a JavaScript web application, you want to warn the user about any pending Ajax requests before they close the window.

Perhaps you've updated the UI before sending an Ajax request to the server, or perhaps you're making long running background request. Either way you want to warn the user that, if they continue, they may lose data.

Browsers don't offer an API to automatically block closing tabs (which would be open to abuse), but they do have a rather archaic API to specify a tab close prompt, window.onbeforeunload.

window.onbeforeunload = function(){
   return "Don't leave me!";
};

As you can see, this isn't a normal event. You can only have one listener, and it should return a string that is ultimately displayed to the client.

jQuery has a undocumented active property, a integer representing the amount of co-current Ajax requests. We can check this to easily know if we need to notify the user.

window.onbeforeunload = function(){
   if (jQuery.active) return 'I will never let go!';
};

However, this will notify the user for non-destructive Ajax requests, such as GET ones. To customize which request types we want to check, we'll need to create our own counter, activeTransforms.

(function(){
  var $ = jQuery;

  var TRANSFORM_TYPES = ['PUT', 'POST', 'DELETE'];

  $.activeTransforms = 0;

  $(document).ajaxSend(function(e, xhr, settings) {
    if (TRANSFORM_TYPES.indexOf(settings.type) < 0) return;
    return $.activeTransforms += 1;
  });

  $(document).ajaxComplete(function(e, xhr, settings) {
    if (TRANSFORM_TYPES.indexOf(settings.type) < 0) return;
    return $.activeTransforms -= 1;
  });

  window.onbeforeunload || (window.onbeforeunload = function() {
    if ($.activeTransforms) {
      return 'There are some pending network requests which\n' +
                 'means closing the page may lose unsaved data.';
    }
  });
}).call(this);

See the full code (including CoffeeScript).


Josh Peek makes the good point that adding an unload handler can mess with page caching.