yippie-io/iUPB

View on GitHub
app/assets/javascripts/libs/jquery.offline.js

Summary

Maintainability
A
3 hrs
Test Coverage
/*!
 * jQuery Offline
 * Version 1.0.0
 *
 * http://github.com/wycats/jquery-offline
 *
 * Copyright 2010, Yehuda Katz
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * Date: Fri Jul 9 10:20:00 2010 -0800
 */

(function($) {

  var prefix = "offline.jquery:",
    mostRecent = null,
    requesting = {};

  // Allow the user to explicitly turn off localStorage
  // before loading this plugin
  if (typeof $.support.localStorage === "undefined") {
    $.support.localStorage = !!window.localStorage;
  }

  // modified getJSON which uses ifModified: true
  function getJSON(url, data, fn) {
    if (jQuery.isFunction(data)) {
      fn = data;
      data = null;
    }

    var requestingKey = url + "?" + $.param(data || {});
    if (requesting[requestingKey]) {
      return false;
    }

    requesting[requestingKey] = true;

    return jQuery.ajax({
      type: "GET",
      url: url,
      data: data,
      success: function(responseData, text) {
        delete requesting[requestingKey];

        // handle lack of response (error callback isn't called in this case)
        if (undefined === responseData) {
          if (!window.navigator.onLine) {
            // requeue the request for the next time we come online
            mostRecent = function() {
              getJSON(url, data, fn);
            };
          }
          return;
        }

        fn(responseData, text);
      },
      error: function() {
        delete requesting[requestingKey];
      },
      dataType: "json",
      ifModified: true
    });
  }

  if ($.support.localStorage) {
    // If localStorage is available, define jQuery.retrieveJSON
    // and jQuery.clearJSON to operate in terms of the offline
    // cache
    // If the user comes online, run the most recent request
    // that was queued due to the user being offline
    $(window).bind("online", function() {
      if (mostRecent) {
        mostRecent();
      }
    });

    // If the user goes offline, hide any loading bar
    // the user may have created
    $(window).bind("offline", function() {
      jQuery.event.trigger("ajaxStop");
    });

    $.retrieveJSON = function(url, data, fn) {
      // allow jQuery.retrieveJSON(url, fn)
      if ($.isFunction(data)) {
        fn = data;
        data = {};
      }

      // remember when this request started so we can report
      // the time when a follow-up Ajax request completes.
      // this is especially important when the user comes
      // back online, since retrieveDate may be minutes,
      // hours or even days before the Ajax request finally
      // completes
      var retrieveDate = new Date;

      // get a String value for the data passed in, and then
      // use it to calculate a cache key
      var param       = $.param(data),
          key         = prefix + url + ":" + param,
          text        = localStorage[key],
          dateString  = localStorage[key + ":date"],
          date        = new Date(Date.parse(dateString));

      function cleanupLocalStorage() {
        // take all date keys and remove the oldest half
        var dateKeys = [];
        for (var i = 0; i < localStorage.length; ++i) {
          var key = localStorage.key(i);
          if (/:date$/.test(key)) {
            dateKeys.push(key);
          }
        }
        dateKeys.sort(function(a, b) {
          var date_a = localStorage[a], date_b = localStorage[b];
          return date_a < date_b ? -1 : (date_a > date_b ? +1 : 0);
        });
        for (var i = 0; i < dateKeys.length / 2; ++i) {
          var key = dateKeys[i];
          delete localStorage[key];
          delete localStorage[key.substr(0, key.length - 5)]; // :date
        }
      }

      // create a function that will make an Ajax request and
      // store the result in the cache. This function will be
      // deferred until later if the user is offline
      function getData() {
        return getJSON(url, data, function(json, status) {
          if ( status == 'notmodified' ) {
            // Just return if the response has a 304 status code
            return false;
          }

          while (true) {
            try {
              localStorage[key] = JSON.stringify(json);
              localStorage[key + ":date"] = new Date;
              break;
            } catch (e) {
                if (e.name == "QUOTA_EXCEEDED_ERR" || e.name ==
                    "NS_ERROR_DOM_QUOTA_REACHED") {
                  cleanupLocalStorage();
                }
            }
          }

          // If this is a follow-up request, create an object
          // containing both the original time of the cached
          // data and the time that the data was originally
          // retrieved from the cache. With this information,
          // users of jQuery Offline can provide the user
          // with improved feedback if the lag is large
          var data = text && { cachedAt: date, retrievedAt: retrieveDate };
          fn(json, status, data);
        });
      }

      // If there is anything in the cache, call the callback
      // right away, with the "cached" status string
      if( text ) {
        var obj = $.parseJSON(text);
        var response = fn( obj, "cached", { cachedAt: date } );
        if( response === false ) {
          var dfd = $.Deferred().promise();
          dfd.done = function(callback) { callback(obj) };
          return dfd;
        }
      }

      // If the user is online, make the Ajax request right away;
      // otherwise, make it the most recent callback so it will
      // get triggered when the user comes online
      if (window.navigator.onLine) {
        return getData();
      } else {
        mostRecent = getData;
      }

      return true;
    };

    // jQuery.clearJSON is simply a wrapper around deleting the
    // localStorage for a URL/data pair
    $.clearJSON = function(url, data) {
      var param = $.param(data || {});
      delete localStorage[prefix + url + ":" + param];
      delete localStorage[prefix + url + ":" + param + ":date"];
    };
  } else {
    // If localStorage is unavailable, just make all requests
    // regular Ajax requests.
    $.retrieveJSON = getJSON;
    $.clearJSON = $.noop;
  }

})(jQuery);