Ajax Cache backed by localStorage for jQuery
/**
* https://github.com/SaneMethod/jquery-ajax-localstorage-cache
*/
; (function($, window){
'use strict';
/**
* Generate the cache key under which to store the local data - either the cache key supplied,
* or one generated from the url, the type and, if present, the data.
*/
var genCacheKey = function(options) {
var url;
// If cacheKey is specified, and a function, return the result of calling that function
// as the cacheKey. Otherwise, just return the specified cacheKey as-is.
if (options.cacheKey){
return (typeof options.cacheKey === 'function') ?
options.cacheKey(options) : options.cacheKey;
}
url = options.url.replace(/jQuery.*/, '');
// Strip _={timestamp}, if cache is set to false
if (options.cache === false) {
url = url.replace(/([?&])_=[^&]*/, '');
}
return url + options.type + (options.data || '');
};
/**
* Determine whether we're using localStorage or, if the user has specified something other than a boolean
* value for options.localCache, whether the value appears to satisfy the plugin's requirements.
* Otherwise, throw a new TypeError indicating what type of value we expect.
* @param {boolean|object} storage
* @returns {boolean|object}
*/
var getStorage = function(storage){
if (!storage) return false;
if (storage === true) return window.localStorage;
if (typeof storage === "object" && 'getItem' in storage &&
'removeItem' in storage && 'setItem' in storage)
{
return storage;
}
throw new TypeError("localCache must either be a boolean value, " +
"or an object which implements the Storage interface.");
};
/**
* Remove the item specified by cacheKey and its attendant meta items from storage.
* @param {Storage|object} storage
* @param {string} cacheKey
*/
var removeFromStorage = function(storage, cacheKey){
storage.removeItem(cacheKey);
storage.removeItem(cacheKey + 'cachettl');
storage.removeItem(cacheKey + 'dataType');
};
/**
* Prefilter for caching ajax calls.
* See also $.ajaxTransport for the elements that make this compatible with jQuery Deferred.
* New parameters available on the ajax call:
* localCache : true // required - either a boolean (in which case localStorage is used), or an object
* implementing the Storage interface, in which case that object is used instead.
* cacheTTL : 5, // optional - cache time in hours, default is 5.
* cacheKey : 'post', // optional - key under which cached string will be stored.
* isCacheValid : function // optional - return true for valid, false for invalid.
* isResponseValid: function // optional - return true to cache response, false to skip caching response.
* thenResponse: function // optional - chains on request to potentially alter the response data that
* gets stored - must return whatever you want stored.
* @method $.ajaxPrefilter
* @param options {Object} Options for the ajax call, modified with ajax standard settings.
* @param orginalOptions {object} Options for ajax as specified in the original call.
* @param jqXHR {jQuery.xhr} jQuery ajax object.
*/
$.ajaxPrefilter(function(options, originalOptions, jqXHR){
var storage = getStorage(options.localCache),
hourstl = options.cacheTTL || 5,
cacheKey = options.cacheKey = genCacheKey(options),
cacheValid = options.isCacheValid,
responseValid = options.isResponseValid,
thenResponse = options.thenResponse || null,
ttl,
value;
if (!storage) return;
ttl = storage.getItem(cacheKey + 'cachettl');
if (cacheValid && typeof cacheValid === 'function' && !cacheValid()){
removeFromStorage(storage, cacheKey);
ttl = 0;
}
if (ttl && ttl < +new Date()){
removeFromStorage(storage, cacheKey);
ttl = 0;
}
value = storage.getItem(cacheKey);
if (!value){
// If value not in the cache, add a then block to request to store the results on success.
jqXHR.then(thenResponse).then(function(data, status, jqXHR){
var strdata = data,
dataType = options.dataType || jqXHR.getResponseHeader('Content-Type') || 'text/plain';
if (!(responseValid && typeof responseValid === 'function' && !responseValid(data, status, jqXHR))) {
if (dataType.toLowerCase().indexOf('json') !== -1) strdata = JSON.stringify(data);
// Save the data to storage catching exceptions (possibly QUOTA_EXCEEDED_ERR)
try {
storage.setItem(cacheKey, strdata);
// Store timestamp and dataType
storage.setItem(cacheKey + 'cachettl', +new Date() + 1000 * 60 * 60 * hourstl);
storage.setItem(cacheKey + 'dataType', dataType);
} catch (e) {
// Remove any incomplete data that may have been saved before the exception was caught
removeFromStorage(storage, cacheKey);
console.log('Cache Error:'+e, cacheKey, strdata);
}
}
});
}
});
/**
* This function performs the fetch from cache portion of the functionality needed to cache ajax
* calls and still fulfill the jqXHR Deferred Promise interface.
* See also $.ajaxPrefilter
* @method $.ajaxTransport
* @params options {Object} Options for the ajax call, modified with ajax standard settings and our
* cacheKey for this call as determined in prefilter.
*/
$.ajaxTransport("+*", function(options){
if (options.localCache)
{
var cacheKey = options.cacheKey,
storage = getStorage(options.localCache),
dataType = options.dataType || storage.getItem(cacheKey + 'dataType') || 'text',
value = (storage) ? storage.getItem(cacheKey) : false;
if (value){
// In the cache? Get it, parse it to json if the dataType is JSON,
// and call the completeCallback with the fetched value.
if (dataType.toLowerCase().indexOf('json') !== -1) value = JSON.parse(value);
return {
send: function(headers, completeCallback) {
var response = {};
response[dataType] = value;
completeCallback(200, 'success', response, '');
},
abort: function() {
console.log("Aborted ajax transport for json cache.");
}
};
}
}
});
})(jQuery, window);