From 6ed17d7e627c70d7df639ce2e6c875c3b478eeac Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Sun, 19 Aug 2012 19:15:05 +0100 Subject: [PATCH] Some cosmetic changes and fixes for IE. Fixes #7 --- locache.js | 225 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 136 insertions(+), 89 deletions(-) diff --git a/locache.js b/locache.js index e207c62..55f9ee0 100644 --- a/locache.js +++ b/locache.js @@ -1,3 +1,5 @@ +/*jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, jquery:true, indent:4, maxerr:200 */ + // locache VERSION-PLACEHOLDER // // (c) 2012 Dougal Matthews @@ -7,7 +9,7 @@ // with DOM Storage and proves a memcache inspired API for // setting and retrieving values. -(function(){ +(function () { "use strict"; @@ -17,6 +19,13 @@ // Save a reference to the global object, in most cases this is `window`. var root = this; + // Object context bnding shim to support older versions of IE. + var bind = function (func, thisValue) { + return function () { + return func.apply(thisValue, arguments); + }; + }; + // Cache class constructor. This is the base “class” for // locache and is used for the global instance plus any of your own // custom caches. @@ -24,9 +33,9 @@ // of the object to the instance. At the moment, this is only really used // to set the 'storage' property - so you can choose a builtin or use // your own storage mechanism. - function LocacheCache(options){ + function LocacheCache(options) { - if(options && options.storage){ + if (options && options.storage) { this.storage = options.storage; } @@ -34,8 +43,8 @@ // the instance of locache. This allows them to easily access the // other methods and storage objects. This is a bit of hack, and may // not be the best idea. - this.async.set = this.async.set.bind(this); - this.async.get = this.async.get.bind(this); + this.async.set = bind(this.async.set, this); + this.async.get = bind(this.async.get, this); } @@ -45,7 +54,7 @@ // Boolean value that determines if they browser supports localStorage or // not. This is based on the Modernizr implementation that can be found // in [the Modernizr GitHub repository.](https://github.com/Modernizr/Modernizr/blob/c56fb8b09515f629806ca44742932902ac145302/modernizr.js#L696-731) - var supportsLocalStorage = LocacheCache.prototype.supportsLocalStorage = (function() { + var supportsLocalStorage = LocacheCache.prototype.supportsLocalStorage = (function () { try { // Create a test value and attempt to set, get and remove the @@ -58,7 +67,7 @@ // that point we can flag the browser as not supporting // localStorage. return true; - } catch(e) { + } catch (e) { return false; } @@ -67,7 +76,7 @@ // Boolean value that determines if they browser supports sessionStorage or // not. This is based on the Modernizr implementation that can be found // in [the Modernizr GitHub repository.](https://github.com/Modernizr/Modernizr/blob/c56fb8b09515f629806ca44742932902ac145302/modernizr.js#L696-731) - var supportsSessionStorage = LocacheCache.prototype.supportsSessionStorage = (function() { + var supportsSessionStorage = LocacheCache.prototype.supportsSessionStorage = (function () { try { // Create a test value and attempt to set, get and remove the @@ -80,7 +89,7 @@ // that point we can flag the browser as not supporting // sessionStorage. return true; - } catch(e) { + } catch (e) { return false; } @@ -89,6 +98,9 @@ // Boolean flag to check if the browser supports native JSON. var supportsNativeJSON = LocacheCache.prototype.supportsNativeJSON = !!root.JSON; + // Booleant flat to check if the browser supports HTML postMessage. + var supportsPostMessage = LocacheCache.prototype.supportsPostMessage = !!window.postMessage; + // Internal utility functions // -------------------- @@ -96,7 +108,7 @@ // thread. This is exposed on the LocacheCache prototype, simply so it // can be accessed from within the unit tests. It's not intended for // public use. - var defer = LocacheCache.prototype._defer = (function(){ + var defer = LocacheCache.prototype._defer = (function () { // Store of the pending functions var timeouts = []; @@ -107,13 +119,24 @@ // have the message name defined above, don't do anything and allow it // to propogate to other handlers. Otherwise, its meant for us so stop // the event. - root.addEventListener("message", function (event) { + var addEventListener; + if (root.addEventListener) { + addEventListener = root.addEventListener; + } else if (root.attachEvent) { + addEventListener = root.attachEvent; + } - if (event.source !== root || event.data !== messageName) return; + addEventListener("message", function (event) { + + if (event.source !== root || event.data !== messageName) { + return; + } event.stopPropagation(); // Make sure we have some pending functions, otherwise return. - if (timeouts.length === 0) return; + if (timeouts.length === 0) { + return; + } // take the oldest from the 'queue' and call that functions. var fn = timeouts.shift(); @@ -126,34 +149,34 @@ // Constructor for the Defer, takes a function onject and stores it // on itself. - function Deferred(fn){ + function Deferred(fn) { this.fn = fn; } // The defer method runs the function and stores the result. Uppon // finishing, it looks for a finishedFunction, that if exists, is // called and passed the result. - Deferred.prototype.defer = function(){ + Deferred.prototype.defer = function () { this.resultValue = this.fn(); - if (this.hasOwnProperty('finishedFunction')){ + if (this.hasOwnProperty('finishedFunction')) { this.finishedFunction(this.resultValue); } }; // Return if the defer has finished. THis is determined by the // existance of the resultValue on the object. - Deferred.prototype.hasFinished = function(){ + Deferred.prototype.hasFinished = function () { return this.hasOwnProperty('resultValue'); }; // The finished function takes another function and assigns that to // be called when the deferred function has finished. - Deferred.prototype.finished = function(fn){ + Deferred.prototype.finished = function (fn) { this.finishedFunction = fn; // Check to see if the deferred function has finished, this can // happen if its very quick or the finished function is assigned // late. If it is, call it straight away. - if (this.hasFinished()){ + if (this.hasFinished()) { this.finishedFunction(this.resultValue); } // Make this object chainable. @@ -167,7 +190,7 @@ var d = new Deferred(fn); // Add the defer method on the Deffered object, with the instance // bound to the queue. - timeouts.push(d.defer.bind(d)); + timeouts.push(bind(d.defer, d)); // post a message to the window that can be recieved by the // message handler. root.postMessage(messageName, "*"); @@ -195,59 +218,59 @@ // Wrapper around localStorage - persistent local storage in the // browser. local: { - set: function(key, value){ + set: function (key, value) { return root.localStorage.setItem(key, value); }, - get: function(key){ + get: function (key) { return root.localStorage.getItem(key); }, - remove: function(key){ + remove: function (key) { return root.localStorage.removeItem(key); }, - length: function(key){ + length: function (key) { return root.localStorage.length; }, - key: function(index){ - if (index < 0 || index >= this.length()){ + key: function (index) { + if (index < 0 || index >= this.length()) { return; } return root.localStorage.key(index); }, - enabled: function(){ - return supportsLocalStorage; + enabled: function () { + return supportsNativeJSON && supportsLocalStorage; } }, // Wrapper around sessionStorage - storage in the browser that is // cleared each time a new session is started - new browser window etc. session: { - set: function(key, value){ + set: function (key, value) { return root.sessionStorage.setItem(key, value); }, - get: function(key){ + get: function (key) { return root.sessionStorage.getItem(key); }, - remove: function(key){ + remove: function (key) { return root.sessionStorage.removeItem(key); }, - length: function(key){ + length: function (key) { return root.sessionStorage.length; }, - key: function(index){ - if (index < 0 || index >= this.length()){ + key: function (index) { + if (index < 0 || index >= this.length()) { return; } return root.sessionStorage.key(index); }, - enabled: function(){ - return supportsSessionStorage; + enabled: function () { + return supportsNativeJSON && supportsSessionStorage; } } @@ -257,30 +280,30 @@ // Utility method to get the number of milliseconds since the Epoch. This // is used when comparing keys to see if they have expired. - var _currentTime = function(){ + var _currentTime = function () { return new Date().getTime(); }; // Given a key, return the key used internally for storing values without // the risk of collisions over usage of the storage backend. - LocacheCache.prototype.key = function(key){ + LocacheCache.prototype.key = function (key) { return this.cachePrefix + key; }; // Given a key, return the key to be used internally for expiry time. - LocacheCache.prototype.expirekey = function(key){ + LocacheCache.prototype.expirekey = function (key) { return this.expirePrefix + key; }; // Given a key, look up its expire time and determine if its in the past // or not. Returns a Boolean. - LocacheCache.prototype.hasExpired = function(key){ + LocacheCache.prototype.hasExpired = function (key) { var expireKey = this.expirekey(key); var expireValue = parseInt(this.storage.get(expireKey), 10); // If we have non-zero integer perform the comparison. - if (expireValue && expireValue < _currentTime()){ + if (expireValue && expireValue < _currentTime()) { return true; } @@ -293,16 +316,18 @@ // Given a key, a value and an optional number of seconds store the value // in the storage backend. - LocacheCache.prototype.set = function(key, value, seconds){ + LocacheCache.prototype.set = function (key, value, seconds) { // If the storage backend isn't supported or the key passed in is // falsy, perform a no-op. - if (!this.storage.enabled() || !key) return; + if (!this.storage.enabled() || !key) { + return; + } var expireKey = this.expirekey(key); var valueKey = this.key(key); - if(seconds){ + if (seconds) { // The time stored is in milliseconds, but this function expects // seconds, so multiply by 1000. var ms = seconds * 1000; @@ -319,15 +344,17 @@ // Fetch a value from the cache. Either returns the value, or if it // doesn't exist (or has expired) return null. - LocacheCache.prototype.get = function(key){ + LocacheCache.prototype.get = function (key) { // If the storage backend isn't supported or the key passed in is // falsy, perform a no-op and return null. - if (!this.storage.enabled() || !key) return null; + if (!this.storage.enabled() || !key) { + return null; + } // If the value has expired, before returning null remove the key // from the storage backend to free up the space. - if (this.hasExpired(key)){ + if (this.hasExpired(key)) { this.remove(this.key(key)); return null; } @@ -340,10 +367,10 @@ // be handled better but its hard to know what to do here? We only // set JSON and thus we expect JSON but we don't want to delete // values that must have come from another source. - if (value){ - try{ + if (value) { + try { return JSON.parse(value); - } catch(err){ + } catch (err) { return null; } } @@ -358,16 +385,16 @@ // contains all of the sync calls supports within locache LocacheCache.prototype.async = { - set: function(key, value, seconds){ - return defer(function(){ + set: function (key, value, seconds) { + return defer(bind(function () { return this.set(key, value, seconds); - }.bind(this)); + }, this)); }, - get: function(key){ - return defer(function(){ + get: function (key) { + return defer(bind(function () { return this.get(key); - }.bind(this)); + }, this)); } @@ -375,10 +402,12 @@ // When removing a key - delete from the storage both the value key/value // pair and the expiration time key/value pair. - LocacheCache.prototype.remove = function(key){ + LocacheCache.prototype.remove = function (key) { // If the storage backend isn't enabled perform a no-op. - if (!this.storage.enabled()) return; + if (!this.storage.enabled()) { + return; + } var expireKey = this.expirekey(key); var valueKey = this.key(key); @@ -393,13 +422,15 @@ // the increment. The fetched value is always parsed as an int to make // sure the increment will work - this means if a non-int was stored, it // will be converted first and thus reset the counter to zero. - LocacheCache.prototype.incr = function(key){ + LocacheCache.prototype.incr = function (key) { // If the storage backend isn't enabled perform a no-op. - if (!this.storage.enabled()) return; + if (!this.storage.enabled()) { + return; + } var current = parseInt(this.get(key), 10); - if (!current){ + if (!current) { current = 0; } current ++; @@ -409,13 +440,15 @@ }; // Exactly the same as the incr function, but with a decrementing value. - LocacheCache.prototype.decr = function(key){ + LocacheCache.prototype.decr = function (key) { // If the storage backend isn't enabled perform a no-op. - if (!this.storage.enabled()) return; + if (!this.storage.enabled()) { + return; + } var current = parseInt(this.get(key), 10); - if (!current){ + if (!current) { current = 0; } current --; @@ -426,10 +459,12 @@ // Given a properties object, in the form of {key: value, key:value} set // multiple keys. - LocacheCache.prototype.setMany = function(properties, seconds){ + LocacheCache.prototype.setMany = function (properties, seconds) { // If the storage backend isn't enabled perform a no-op. - if (!this.storage.enabled()) return; + if (!this.storage.enabled()) { + return; + } // Iterate through all the object properties. for (var key in properties) { @@ -444,15 +479,15 @@ // Given an array of keys, return an array of values. If values don't // exist, null will be in their place. - LocacheCache.prototype.getMany = function(keys){ + LocacheCache.prototype.getMany = function (keys) { var results = {}; - for (var i=0; i < keys.length; i++){ + for (var i = 0; i < keys.length; i++) { // To ensure that the correct structure is returned, if // the storage backend isn't enabled return an array of null // values with the correct length. - if (this.storage.enabled()){ + if (this.storage.enabled()) { results[keys[i]] = this.get(keys[i]); } else { results[keys[i]] = null; @@ -466,15 +501,15 @@ // Given an array of keys, return an array of values. If values don't // exist, null will be in their place. - LocacheCache.prototype.getManyValues = function(keys){ + LocacheCache.prototype.getManyValues = function (keys) { var results = []; - for (var i=0; i < keys.length; i++){ + for (var i = 0; i < keys.length; i++) { // To ensure that the correct structure is returned, if // the storage backend isn't enabled return an array of null // values with the correct length. - if (this.storage.enabled()){ + if (this.storage.enabled()) { results.push(this.get(keys[i])); } else { results.push(null); @@ -486,12 +521,14 @@ }; // Given an array of keys, remove all of them from the cache. - LocacheCache.prototype.removeMany = function(keys){ + LocacheCache.prototype.removeMany = function (keys) { // If the storage backend isn't enabled perform a no-op. - if (!this.storage.enabled()) return; + if (!this.storage.enabled()) { + return; + } - for (var i=0; i < keys.length; i++){ + for (var i = 0; i < keys.length; i++) { this.remove(keys[i]); } @@ -499,10 +536,12 @@ // Delete all stored values from the cache. This method will only remove // values added to the storage backend with the locache prefix in the key. - LocacheCache.prototype.flush = function(){ + LocacheCache.prototype.flush = function () { // If the storage backend isn't enabled perform a no-op. - if (!this.storage.enabled()) return; + if (!this.storage.enabled()) { + return; + } var length = this.storage.length(); var prefix = this.cachePrefix; @@ -510,27 +549,33 @@ // Iteratate through all the keys stored in the storage backend - if // the key tarts with the prefix cache prefix, then remove that key. // backwards to make sure removing items does not mess up the index - for (var i=length-1; i >= 0; i-- ) { + for (var i = length - 1; i >= 0; i--) { var key = this.storage.key(i); - if (key && key.indexOf(prefix) === 0) this.storage.remove(key); + if (key && key.indexOf(prefix) === 0) { + this.storage.remove(key); + } } }; // Return the number of cache values stored in the storage backend. This // only calculates the values stored by locache - LocacheCache.prototype.length = function(){ + LocacheCache.prototype.length = function () { // If the storage backend isn't supported perform a no-op and return // zero. - if (!this.storage.enabled()) return 0; + if (!this.storage.enabled()) { + return 0; + } var c = 0; var length = this.storage.length(); var prefix = this.cachePrefix; - for (var i=0; i < length; i++) { - if (this.storage.key(i).indexOf(prefix) === 0) c++; + for (var i = 0; i < length; i++) { + if (this.storage.key(i).indexOf(prefix) === 0) { + c++; + } } return c; @@ -541,23 +586,25 @@ // the keys stored in the storage backend. If they key is a locache key // (it has the prefix) then check to see if the key has expired. If it // has, remove the key from the cache. - LocacheCache.prototype.cleanup = function(){ + LocacheCache.prototype.cleanup = function () { // If the storage backend isn't enabled perform a no-op. - if (!this.storage.enabled()) return; + if (!this.storage.enabled()) { + return; + } var length = this.storage.length(); var prefix = this.cachePrefix; - for (var i=0; i < length; i++) { + for (var i = 0; i < length; i++) { var key = this.storage.key(i); // If the key matches, remove the prefix to get the original key // and then make use of the normal remove method that will clean // up the cache value key pair and the cache epiration time key // pair. - if (key && key.indexOf(prefix) === 0){ + if (key && key.indexOf(prefix) === 0) { var actualKey = key.substring(prefix.length, key.length); - if (this.hasExpired(actualKey)){ + if (this.hasExpired(actualKey)) { this.remove(actualKey); } } @@ -568,7 +615,7 @@ // A factory method added to the LocacheCache constructor to create // instances of itself. Rather than placing the class publicly, wrap // it up in a method and keep it for internal usage. - LocacheCache.prototype.createCache = function(options){ + LocacheCache.prototype.createCache = function (options) { return new LocacheCache(options); };