From 573f8293653936b54fc1148885ac286b256c5780 Mon Sep 17 00:00:00 2001 From: Mike Lohmann Date: Fri, 1 Nov 2013 12:46:22 +0100 Subject: [PATCH 1/5] added packages.json to be installable via npm --- package.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 0000000..162c1ea --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "jstorage", + "version": "0.4.6", + "description": "jStorage is a cross-browser key-value store database to store data locally in the browser ", + "keywords": [ + "jStorage" + ], + "homepage": "http://www.jstorage.info", + "bugs": "https://github.com/andris9/jStorage/issues", + "repository": { + "type": "git", + "url": "git://github.com/andris9/jStorage.git" + }, + "main": "./jstorage.js", + "licenses": [ + { + "type": "MIT" + } + ] +} From 492704e7f948173abc487ae8125237195e94cddb Mon Sep 17 00:00:00 2001 From: Mike Lohmann Date: Mon, 2 Dec 2013 19:06:13 +0100 Subject: [PATCH 2/5] Added correct jStorage version number to component.json --- component.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component.json b/component.json index 793d335..15fc1ad 100644 --- a/component.json +++ b/component.json @@ -1,6 +1,6 @@ { "name": "jStorage", - "version": "0.4.4", + "version": "0.4.5", "main": "./jstorage.js", "author": "Andris Reinman ", "dependencies": {} From 31c2bdb25cf314e4d3057286cd149fbb215f6db8 Mon Sep 17 00:00:00 2001 From: Mike Lohmann Date: Mon, 2 Dec 2013 19:06:54 +0100 Subject: [PATCH 3/5] min.js is not needed in our fork --- jstorage.min.js | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 jstorage.min.js diff --git a/jstorage.min.js b/jstorage.min.js deleted file mode 100644 index 6a0e5f9..0000000 --- a/jstorage.min.js +++ /dev/null @@ -1,15 +0,0 @@ -(function(){function D(){var a="{}";if("userDataBehavior"==k){d.load("jStorage");try{a=d.getAttribute("jStorage")}catch(b){}try{r=d.getAttribute("jStorage_update")}catch(c){}h.jStorage=a}E();x();F()}function u(){var a;clearTimeout(G);G=setTimeout(function(){if("localStorage"==k||"globalStorage"==k)a=h.jStorage_update;else if("userDataBehavior"==k){d.load("jStorage");try{a=d.getAttribute("jStorage_update")}catch(b){}}if(a&&a!=r){r=a;var l=m.parse(m.stringify(c.__jstorage_meta.CRC32)),p;D();p=m.parse(m.stringify(c.__jstorage_meta.CRC32)); -var e,z=[],f=[];for(e in l)l.hasOwnProperty(e)&&(p[e]?l[e]!=p[e]&&"2."==String(l[e]).substr(0,2)&&z.push(e):f.push(e));for(e in p)p.hasOwnProperty(e)&&(l[e]||z.push(e));s(z,"updated");s(f,"deleted")}},25)}function s(a,b){a=[].concat(a||[]);if("flushed"==b){a=[];for(var c in g)g.hasOwnProperty(c)&&a.push(c);b="deleted"}c=0;for(var p=a.length;cB){var l=b[0],d=b[1];b=b[2];if(t[d])for(var e=0,h=t[d].length;e>>16)&65535)<<16),n^=n>>>24,n=1540483477*(n&65535)+((1540483477*(n>>>16)&65535)<<16),f=1540483477*(f&65535)+((1540483477*(f>>>16)&65535)<<16)^n,k-=4,++g;switch(k){case 3:f^=(e.charCodeAt(g+2)&255)<<16;case 2:f^=(e.charCodeAt(g+1)&255)<<8;case 1:f^=e.charCodeAt(g)&255,f=1540483477*(f&65535)+((1540483477*(f>>>16)&65535)<<16)}f^=f>>>13;f=1540483477*(f&65535)+((1540483477*(f>>>16)& -65535)<<16);h[a]="2."+((f^f>>>15)>>>0);this.setTTL(a,d.TTL||0);s(a,"updated");return b},get:function(a,b){q(a);return a in c?c[a]&&"object"==typeof c[a]&&c[a]._is_xml?C.decode(c[a].xml):c[a]:"undefined"==typeof b?null:b},deleteKey:function(a){q(a);return a in c?(delete c[a],"object"==typeof c.__jstorage_meta.TTL&&a in c.__jstorage_meta.TTL&&delete c.__jstorage_meta.TTL[a],delete c.__jstorage_meta.CRC32[a],w(),v(),s(a,"deleted"),!0):!1},setTTL:function(a,b){var d=+new Date;q(a);b=Number(b)||0;return a in -c?(c.__jstorage_meta.TTL||(c.__jstorage_meta.TTL={}),0 Date: Mon, 2 Dec 2013 19:07:25 +0100 Subject: [PATCH 4/5] made jStorage loadable by require without using $ --- jstorage.js | 678 ++++++++++++++++++++++++++-------------------------- 1 file changed, 336 insertions(+), 342 deletions(-) diff --git a/jstorage.js b/jstorage.js index 9921032..062a82e 100644 --- a/jstorage.js +++ b/jstorage.js @@ -24,25 +24,22 @@ * SOFTWARE. */ - (function(){ +var jStorage = function () { var - /* jStorage version */ + /* jStorage version */ JSTORAGE_VERSION = "0.4.5", - /* detect a dollar object or create one if not found */ - $ = window.jQuery || window.$ || (window.$ = {}), - - /* check for a JSON handling support */ + /* check for a JSON handling support */ JSON = { parse: window.JSON && (window.JSON.parse || window.JSON.decode) || - String.prototype.evalJSON && function(str){return String(str).evalJSON();} || - $.parseJSON || - $.evalJSON, + String.prototype.evalJSON && function(str){return String(str).evalJSON();} || + $.parseJSON || + $.evalJSON, stringify: Object.toJSON || - window.JSON && (window.JSON.stringify || window.JSON.encode) || - $.toJSON + window.JSON && (window.JSON.stringify || window.JSON.encode) || + $.toJSON }; // Break if no JSON support was found @@ -51,37 +48,37 @@ } var - /* This is the object, that holds the cached values */ + /* This is the object, that holds the cached values */ _storage = {__jstorage_meta:{CRC32:{}}}, - /* Actual browser storage (localStorage or globalStorage['domain']) */ + /* Actual browser storage (localStorage or globalStorage['domain']) */ _storage_service = {jStorage:"{}"}, - /* DOM element for older IE versions, holds userData behavior */ + /* DOM element for older IE versions, holds userData behavior */ _storage_elm = null, - /* How much space does the storage take */ + /* How much space does the storage take */ _storage_size = 0, - /* which backend is currently used */ + /* which backend is currently used */ _backend = false, - /* onchange observers */ + /* onchange observers */ _observers = {}, - /* timeout to wait after onchange event */ + /* timeout to wait after onchange event */ _observer_timeout = false, - /* last update time */ + /* last update time */ _observer_update = 0, - /* pubsub observers */ + /* pubsub observers */ _pubsub_observers = {}, - /* skip published items older than current timestamp */ + /* skip published items older than current timestamp */ _pubsub_last = +new Date(), - /* Next check for TTL */ + /* Next check for TTL */ _ttl_timeout, /** @@ -89,10 +86,10 @@ * XML nodes are encoded and decoded if the node is the value to be saved * but not if it's as a property of another object * Eg. - - * $.jStorage.set("key", xmlNode); // IS OK - * $.jStorage.set("key", {xml: xmlNode}); // NOT OK + * jStorage.set("key", xmlNode); // IS OK + * jStorage.set("key", {xml: xmlNode}); // NOT OK */ - _XMLService = { + _XMLService = { /** * Validates a XML node to be XML @@ -128,12 +125,12 @@ decode: function(xmlString){ var dom_parser = ("DOMParser" in window && (new DOMParser()).parseFromString) || (window.ActiveXObject && function(_xmlString) { - var xml_doc = new ActiveXObject('Microsoft.XMLDOM'); - xml_doc.async = 'false'; - xml_doc.loadXML(_xmlString); - return xml_doc; - }), - resultXML; + var xml_doc = new ActiveXObject('Microsoft.XMLDOM'); + xml_doc.async = 'false'; + xml_doc.loadXML(_xmlString); + return xml_doc; + }), + resultXML; if(!dom_parser){ return false; } @@ -176,12 +173,12 @@ else if("globalStorage" in window){ try { if(window.globalStorage) { - if(window.location.hostname == 'localhost'){ - _storage_service = window.globalStorage['localhost.localdomain']; - } - else{ - _storage_service = window.globalStorage[window.location.hostname]; - } + if(window.location.hostname == 'localhost'){ + _storage_service = window.globalStorage['localhost.localdomain']; + } + else{ + _storage_service = window.globalStorage[window.location.hostname]; + } _backend = "globalStorage"; _observer_update = _storage_service.jStorage_update; } @@ -612,9 +609,9 @@ while (l >= 4) { k = ((str.charCodeAt(i) & 0xff)) | - ((str.charCodeAt(++i) & 0xff) << 8) | - ((str.charCodeAt(++i) & 0xff) << 16) | - ((str.charCodeAt(++i) & 0xff) << 24); + ((str.charCodeAt(++i) & 0xff) << 8) | + ((str.charCodeAt(++i) & 0xff) << 16) | + ((str.charCodeAt(++i) & 0xff) << 24); k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16)); k ^= k >>> 24; @@ -641,310 +638,307 @@ } ////////////////////////// PUBLIC INTERFACE ///////////////////////// - - $.jStorage = { - /* Version number */ - version: JSTORAGE_VERSION, - - /** - * Sets a key's value. - * - * @param {String} key Key to set. If this value is not set or not - * a string an exception is raised. - * @param {Mixed} value Value to set. This can be any value that is JSON - * compatible (Numbers, Strings, Objects etc.). - * @param {Object} [options] - possible options to use - * @param {Number} [options.TTL] - optional TTL value - * @return {Mixed} the used value - */ - set: function(key, value, options){ - _checkKey(key); - - options = options || {}; - - // undefined values are deleted automatically - if(typeof value == "undefined"){ - this.deleteKey(key); - return value; - } - - if(_XMLService.isXML(value)){ - value = {_is_xml:true,xml:_XMLService.encode(value)}; - }else if(typeof value == "function"){ - return undefined; // functions can't be saved! - }else if(value && typeof value == "object"){ - // clone the object before saving to _storage tree - value = JSON.parse(JSON.stringify(value)); - } - - _storage[key] = value; - - _storage.__jstorage_meta.CRC32[key] = "2." + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c); - - this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange - - _fireObservers(key, "updated"); - return value; - }, - - /** - * Looks up a key in cache - * - * @param {String} key - Key to look up. - * @param {mixed} def - Default value to return, if key didn't exist. - * @return {Mixed} the key value, default value or null - */ - get: function(key, def){ - _checkKey(key); - if(key in _storage){ - if(_storage[key] && typeof _storage[key] == "object" && _storage[key]._is_xml) { - return _XMLService.decode(_storage[key].xml); - }else{ - return _storage[key]; - } - } - return typeof(def) == 'undefined' ? null : def; - }, - - /** - * Deletes a key from cache. - * - * @param {String} key - Key to delete. - * @return {Boolean} true if key existed or false if it didn't - */ - deleteKey: function(key){ - _checkKey(key); - if(key in _storage){ - delete _storage[key]; - // remove from TTL list - if(typeof _storage.__jstorage_meta.TTL == "object" && - key in _storage.__jstorage_meta.TTL){ - delete _storage.__jstorage_meta.TTL[key]; - } - - delete _storage.__jstorage_meta.CRC32[key]; - - _save(); - _publishChange(); - _fireObservers(key, "deleted"); - return true; - } - return false; - }, - - /** - * Sets a TTL for a key, or remove it if ttl value is 0 or below - * - * @param {String} key - key to set the TTL for - * @param {Number} ttl - TTL timeout in milliseconds - * @return {Boolean} true if key existed or false if it didn't - */ - setTTL: function(key, ttl){ - var curtime = +new Date(); - _checkKey(key); - ttl = Number(ttl) || 0; - if(key in _storage){ - - if(!_storage.__jstorage_meta.TTL){ - _storage.__jstorage_meta.TTL = {}; - } - - // Set TTL value for the key - if(ttl>0){ - _storage.__jstorage_meta.TTL[key] = curtime + ttl; - }else{ - delete _storage.__jstorage_meta.TTL[key]; - } - - _save(); - - _handleTTL(); - - _publishChange(); - return true; - } - return false; - }, - - /** - * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set - * - * @param {String} key Key to check - * @return {Number} Remaining TTL in milliseconds - */ - getTTL: function(key){ - var curtime = +new Date(), ttl; - _checkKey(key); - if(key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]){ - ttl = _storage.__jstorage_meta.TTL[key] - curtime; - return ttl || 0; - } - return 0; - }, - - /** - * Deletes everything in cache. - * - * @return {Boolean} Always true - */ - flush: function(){ - _storage = {__jstorage_meta:{CRC32:{}}}; - _save(); - _publishChange(); - _fireObservers(null, "flushed"); - return true; - }, - - /** - * Returns a read-only copy of _storage - * - * @return {Object} Read-only copy of _storage - */ - storageObj: function(){ - function F() {} - F.prototype = _storage; - return new F(); - }, - - /** - * Returns an index of all used keys as an array - * ['key1', 'key2',..'keyN'] - * - * @return {Array} Used keys - */ - index: function(){ - var index = [], i; - for(i in _storage){ - if(_storage.hasOwnProperty(i) && i != "__jstorage_meta"){ - index.push(i); - } - } - return index; - }, - - /** - * How much space in bytes does the storage take? - * - * @return {Number} Storage size in chars (not the same as in bytes, - * since some chars may take several bytes) - */ - storageSize: function(){ - return _storage_size; - }, - - /** - * Which backend is currently in use? - * - * @return {String} Backend name - */ - currentBackend: function(){ - return _backend; - }, - - /** - * Test if storage is available - * - * @return {Boolean} True if storage can be used - */ - storageAvailable: function(){ - return !!_backend; - }, - - /** - * Register change listeners - * - * @param {String} key Key name - * @param {Function} callback Function to run when the key changes - */ - listenKeyChange: function(key, callback){ - _checkKey(key); - if(!_observers[key]){ - _observers[key] = []; - } - _observers[key].push(callback); - }, - - /** - * Remove change listeners - * - * @param {String} key Key name to unregister listeners against - * @param {Function} [callback] If set, unregister the callback, if not - unregister all - */ - stopListening: function(key, callback){ - _checkKey(key); - - if(!_observers[key]){ - return; - } - - if(!callback){ - delete _observers[key]; - return; - } - - for(var i = _observers[key].length - 1; i>=0; i--){ - if(_observers[key][i] == callback){ - _observers[key].splice(i,1); + var jStorageIntance = false; + + this.createInstance = function () { + + if (jStorageIntance) { + return jStorageIntance; + } else { + jStorageIntance = { + /* Version number */ + version: JSTORAGE_VERSION, + + /** + * Sets a key's value. + * + * @param {String} key Key to set. If this value is not set or not + * a string an exception is raised. + * @param {Mixed} value Value to set. This can be any value that is JSON + * compatible (Numbers, Strings, Objects etc.). + * @param {Object} [options] - possible options to use + * @param {Number} [options.TTL] - optional TTL value + * @return {Mixed} the used value + */ + set: function(key, value, options){ + _checkKey(key); + + options = options || {}; + + // undefined values are deleted automatically + if(typeof value == "undefined"){ + this.deleteKey(key); + return value; + } + + if(_XMLService.isXML(value)){ + value = {_is_xml:true,xml:_XMLService.encode(value)}; + }else if(typeof value == "function"){ + return undefined; // functions can't be saved! + }else if(value && typeof value == "object"){ + // clone the object before saving to _storage tree + value = JSON.parse(JSON.stringify(value)); + } + + _storage[key] = value; + + _storage.__jstorage_meta.CRC32[key] = "2." + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c); + + this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange + + _fireObservers(key, "updated"); + return value; + }, + + /** + * Looks up a key in cache + * + * @param {String} key - Key to look up. + * @param {mixed} def - Default value to return, if key didn't exist. + * @return {Mixed} the key value, default value or null + */ + get: function(key, def){ + _checkKey(key); + if(key in _storage){ + if(_storage[key] && typeof _storage[key] == "object" && _storage[key]._is_xml) { + return _XMLService.decode(_storage[key].xml); + }else{ + return _storage[key]; + } + } + return typeof(def) == 'undefined' ? null : def; + }, + + /** + * Deletes a key from cache. + * + * @param {String} key - Key to delete. + * @return {Boolean} true if key existed or false if it didn't + */ + deleteKey: function(key){ + _checkKey(key); + if(key in _storage){ + delete _storage[key]; + // remove from TTL list + if(typeof _storage.__jstorage_meta.TTL == "object" && + key in _storage.__jstorage_meta.TTL){ + delete _storage.__jstorage_meta.TTL[key]; + } + + delete _storage.__jstorage_meta.CRC32[key]; + + _save(); + _publishChange(); + _fireObservers(key, "deleted"); + return true; + } + return false; + }, + + /** + * Sets a TTL for a key, or remove it if ttl value is 0 or below + * + * @param {String} key - key to set the TTL for + * @param {Number} ttl - TTL timeout in milliseconds + * @return {Boolean} true if key existed or false if it didn't + */ + setTTL: function(key, ttl){ + var curtime = +new Date(); + _checkKey(key); + ttl = Number(ttl) || 0; + if(key in _storage){ + + if(!_storage.__jstorage_meta.TTL){ + _storage.__jstorage_meta.TTL = {}; + } + + // Set TTL value for the key + if(ttl>0){ + _storage.__jstorage_meta.TTL[key] = curtime + ttl; + }else{ + delete _storage.__jstorage_meta.TTL[key]; + } + + _save(); + + _handleTTL(); + + _publishChange(); + return true; + } + return false; + }, + + /** + * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set + * + * @param {String} key Key to check + * @return {Number} Remaining TTL in milliseconds + */ + getTTL: function(key){ + var curtime = +new Date(), ttl; + _checkKey(key); + if(key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]){ + ttl = _storage.__jstorage_meta.TTL[key] - curtime; + return ttl || 0; + } + return 0; + }, + + /** + * Deletes everything in cache. + * + * @return {Boolean} Always true + */ + flush: function(){ + _storage = {__jstorage_meta:{CRC32:{}}}; + _save(); + _publishChange(); + _fireObservers(null, "flushed"); + return true; + }, + + /** + * Returns a read-only copy of _storage + * + * @return {Object} Read-only copy of _storage + */ + storageObj: function(){ + function F() {} + F.prototype = _storage; + return new F(); + }, + + /** + * Returns an index of all used keys as an array + * ['key1', 'key2',..'keyN'] + * + * @return {Array} Used keys + */ + index: function(){ + var index = [], i; + for(i in _storage){ + if(_storage.hasOwnProperty(i) && i != "__jstorage_meta"){ + index.push(i); + } + } + return index; + }, + + /** + * How much space in bytes does the storage take? + * + * @return {Number} Storage size in chars (not the same as in bytes, + * since some chars may take several bytes) + */ + storageSize: function(){ + return _storage_size; + }, + + /** + * Which backend is currently in use? + * + * @return {String} Backend name + */ + currentBackend: function(){ + return _backend; + }, + + /** + * Test if storage is available + * + * @return {Boolean} True if storage can be used + */ + storageAvailable: function(){ + return !!_backend; + }, + + /** + * Register change listeners + * + * @param {String} key Key name + * @param {Function} callback Function to run when the key changes + */ + listenKeyChange: function(key, callback){ + _checkKey(key); + if(!_observers[key]){ + _observers[key] = []; + } + _observers[key].push(callback); + }, + + /** + * Remove change listeners + * + * @param {String} key Key name to unregister listeners against + * @param {Function} [callback] If set, unregister the callback, if not - unregister all + */ + stopListening: function(key, callback){ + _checkKey(key); + + if(!_observers[key]){ + return; + } + + if(!callback){ + delete _observers[key]; + return; + } + + for(var i = _observers[key].length - 1; i>=0; i--){ + if(_observers[key][i] == callback){ + _observers[key].splice(i,1); + } + } + }, + + /** + * Subscribe to a Publish/Subscribe event stream + * + * @param {String} channel Channel name + * @param {Function} callback Function to run when the something is published to the channel + */ + subscribe: function(channel, callback){ + channel = (channel || "").toString(); + if(!channel){ + throw new TypeError('Channel not defined'); + } + if(!_pubsub_observers[channel]){ + _pubsub_observers[channel] = []; + } + _pubsub_observers[channel].push(callback); + }, + + /** + * Publish data to an event stream + * + * @param {String} channel Channel name + * @param {Mixed} payload Payload to deliver + */ + publish: function(channel, payload){ + channel = (channel || "").toString(); + if(!channel){ + throw new TypeError('Channel not defined'); + } + + _publish(channel, payload); + }, + + /** + * Reloads the data from browser storage + */ + reInit: function(){ + _reloadData(); } - } - }, - - /** - * Subscribe to a Publish/Subscribe event stream - * - * @param {String} channel Channel name - * @param {Function} callback Function to run when the something is published to the channel - */ - subscribe: function(channel, callback){ - channel = (channel || "").toString(); - if(!channel){ - throw new TypeError('Channel not defined'); - } - if(!_pubsub_observers[channel]){ - _pubsub_observers[channel] = []; - } - _pubsub_observers[channel].push(callback); - }, - - /** - * Publish data to an event stream - * - * @param {String} channel Channel name - * @param {Mixed} payload Payload to deliver - */ - publish: function(channel, payload){ - channel = (channel || "").toString(); - if(!channel){ - throw new TypeError('Channel not defined'); - } - - _publish(channel, payload); - }, - - /** - * Reloads the data from browser storage - */ - reInit: function(){ - _reloadData(); - }, + }; - /** - * Removes reference from global objects and saves it as jStorage - * - * @param {Boolean} option if needed to save object as simple 'jStorage' in windows context - */ - noConflict: function( saveInGlobal ) { - delete window.$.jStorage - - if ( saveInGlobal ) { - window.jStorage = this; - } + // Initialize jStorage + _init(); - return this; - } - }; + return jStorageIntance; + } + } - // Initialize jStorage - _init(); +}; -})(); +module.exports = new jStorage().createInstance(); From d114c93276a5d2ff3fe4eb46b327725d30f75261 Mon Sep 17 00:00:00 2001 From: Mike Lohmann Date: Tue, 3 Dec 2013 10:04:43 +0100 Subject: [PATCH 5/5] Made jStorage to be a class and inject JSON-method --- jstorage.js | 585 ++++++++++++++++++++++++++-------------------------- 1 file changed, 291 insertions(+), 294 deletions(-) diff --git a/jstorage.js b/jstorage.js index 062a82e..c405e5a 100644 --- a/jstorage.js +++ b/jstorage.js @@ -24,23 +24,11 @@ * SOFTWARE. */ -var jStorage = function () { - var +var jStorage = false; + +function JStorage(JSON) { /* jStorage version */ - JSTORAGE_VERSION = "0.4.5", - - /* check for a JSON handling support */ - JSON = { - parse: - window.JSON && (window.JSON.parse || window.JSON.decode) || - String.prototype.evalJSON && function(str){return String(str).evalJSON();} || - $.parseJSON || - $.evalJSON, - stringify: - Object.toJSON || - window.JSON && (window.JSON.stringify || window.JSON.encode) || - $.toJSON - }; + var JSTORAGE_VERSION = "0.4.5"; // Break if no JSON support was found if(!('parse' in JSON) || !('stringify' in JSON)){ @@ -638,307 +626,316 @@ var jStorage = function () { } ////////////////////////// PUBLIC INTERFACE ///////////////////////// - var jStorageIntance = false; - this.createInstance = function () { - if (jStorageIntance) { - return jStorageIntance; - } else { - jStorageIntance = { - /* Version number */ - version: JSTORAGE_VERSION, - - /** - * Sets a key's value. - * - * @param {String} key Key to set. If this value is not set or not - * a string an exception is raised. - * @param {Mixed} value Value to set. This can be any value that is JSON - * compatible (Numbers, Strings, Objects etc.). - * @param {Object} [options] - possible options to use - * @param {Number} [options.TTL] - optional TTL value - * @return {Mixed} the used value - */ - set: function(key, value, options){ - _checkKey(key); - - options = options || {}; - - // undefined values are deleted automatically - if(typeof value == "undefined"){ - this.deleteKey(key); - return value; - } + var jStorageIntance = { + /* Version number */ + version: JSTORAGE_VERSION, - if(_XMLService.isXML(value)){ - value = {_is_xml:true,xml:_XMLService.encode(value)}; - }else if(typeof value == "function"){ - return undefined; // functions can't be saved! - }else if(value && typeof value == "object"){ - // clone the object before saving to _storage tree - value = JSON.parse(JSON.stringify(value)); - } + /** + * Sets a key's value. + * + * @param {String} key Key to set. If this value is not set or not + * a string an exception is raised. + * @param {Mixed} value Value to set. This can be any value that is JSON + * compatible (Numbers, Strings, Objects etc.). + * @param {Object} [options] - possible options to use + * @param {Number} [options.TTL] - optional TTL value + * @return {Mixed} the used value + */ + set: function(key, value, options){ + _checkKey(key); - _storage[key] = value; + options = options || {}; - _storage.__jstorage_meta.CRC32[key] = "2." + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c); + // undefined values are deleted automatically + if(typeof value == "undefined"){ + this.deleteKey(key); + return value; + } - this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange + if(_XMLService.isXML(value)){ + value = {_is_xml:true,xml:_XMLService.encode(value)}; + }else if(typeof value == "function"){ + return undefined; // functions can't be saved! + }else if(value && typeof value == "object"){ + // clone the object before saving to _storage tree + value = JSON.parse(JSON.stringify(value)); + } - _fireObservers(key, "updated"); - return value; - }, - - /** - * Looks up a key in cache - * - * @param {String} key - Key to look up. - * @param {mixed} def - Default value to return, if key didn't exist. - * @return {Mixed} the key value, default value or null - */ - get: function(key, def){ - _checkKey(key); - if(key in _storage){ - if(_storage[key] && typeof _storage[key] == "object" && _storage[key]._is_xml) { - return _XMLService.decode(_storage[key].xml); - }else{ - return _storage[key]; - } - } - return typeof(def) == 'undefined' ? null : def; - }, - - /** - * Deletes a key from cache. - * - * @param {String} key - Key to delete. - * @return {Boolean} true if key existed or false if it didn't - */ - deleteKey: function(key){ - _checkKey(key); - if(key in _storage){ - delete _storage[key]; - // remove from TTL list - if(typeof _storage.__jstorage_meta.TTL == "object" && - key in _storage.__jstorage_meta.TTL){ - delete _storage.__jstorage_meta.TTL[key]; - } - - delete _storage.__jstorage_meta.CRC32[key]; - - _save(); - _publishChange(); - _fireObservers(key, "deleted"); - return true; - } - return false; - }, - - /** - * Sets a TTL for a key, or remove it if ttl value is 0 or below - * - * @param {String} key - key to set the TTL for - * @param {Number} ttl - TTL timeout in milliseconds - * @return {Boolean} true if key existed or false if it didn't - */ - setTTL: function(key, ttl){ - var curtime = +new Date(); - _checkKey(key); - ttl = Number(ttl) || 0; - if(key in _storage){ - - if(!_storage.__jstorage_meta.TTL){ - _storage.__jstorage_meta.TTL = {}; - } - - // Set TTL value for the key - if(ttl>0){ - _storage.__jstorage_meta.TTL[key] = curtime + ttl; - }else{ - delete _storage.__jstorage_meta.TTL[key]; - } - - _save(); - - _handleTTL(); - - _publishChange(); - return true; + _storage[key] = value; + + _storage.__jstorage_meta.CRC32[key] = "2." + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c); + + this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange + + _fireObservers(key, "updated"); + return value; + }, + + /** + * Looks up a key in cache + * + * @param {String} key - Key to look up. + * @param {mixed} def - Default value to return, if key didn't exist. + * @return {Mixed} the key value, default value or null + */ + get: function(key, def){ + _checkKey(key); + if(key in _storage){ + if(_storage[key] && typeof _storage[key] == "object" && _storage[key]._is_xml) { + return _XMLService.decode(_storage[key].xml); + }else{ + return _storage[key]; } - return false; - }, - - /** - * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set - * - * @param {String} key Key to check - * @return {Number} Remaining TTL in milliseconds - */ - getTTL: function(key){ - var curtime = +new Date(), ttl; - _checkKey(key); - if(key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]){ - ttl = _storage.__jstorage_meta.TTL[key] - curtime; - return ttl || 0; + } + return typeof(def) == 'undefined' ? null : def; + }, + + /** + * Deletes a key from cache. + * + * @param {String} key - Key to delete. + * @return {Boolean} true if key existed or false if it didn't + */ + deleteKey: function(key){ + _checkKey(key); + if(key in _storage){ + delete _storage[key]; + // remove from TTL list + if(typeof _storage.__jstorage_meta.TTL == "object" && + key in _storage.__jstorage_meta.TTL){ + delete _storage.__jstorage_meta.TTL[key]; } - return 0; - }, - - /** - * Deletes everything in cache. - * - * @return {Boolean} Always true - */ - flush: function(){ - _storage = {__jstorage_meta:{CRC32:{}}}; + + delete _storage.__jstorage_meta.CRC32[key]; + _save(); _publishChange(); - _fireObservers(null, "flushed"); + _fireObservers(key, "deleted"); return true; - }, - - /** - * Returns a read-only copy of _storage - * - * @return {Object} Read-only copy of _storage - */ - storageObj: function(){ - function F() {} - F.prototype = _storage; - return new F(); - }, - - /** - * Returns an index of all used keys as an array - * ['key1', 'key2',..'keyN'] - * - * @return {Array} Used keys - */ - index: function(){ - var index = [], i; - for(i in _storage){ - if(_storage.hasOwnProperty(i) && i != "__jstorage_meta"){ - index.push(i); - } - } - return index; - }, - - /** - * How much space in bytes does the storage take? - * - * @return {Number} Storage size in chars (not the same as in bytes, - * since some chars may take several bytes) - */ - storageSize: function(){ - return _storage_size; - }, - - /** - * Which backend is currently in use? - * - * @return {String} Backend name - */ - currentBackend: function(){ - return _backend; - }, - - /** - * Test if storage is available - * - * @return {Boolean} True if storage can be used - */ - storageAvailable: function(){ - return !!_backend; - }, - - /** - * Register change listeners - * - * @param {String} key Key name - * @param {Function} callback Function to run when the key changes - */ - listenKeyChange: function(key, callback){ - _checkKey(key); - if(!_observers[key]){ - _observers[key] = []; - } - _observers[key].push(callback); - }, - - /** - * Remove change listeners - * - * @param {String} key Key name to unregister listeners against - * @param {Function} [callback] If set, unregister the callback, if not - unregister all - */ - stopListening: function(key, callback){ - _checkKey(key); - - if(!_observers[key]){ - return; - } + } + return false; + }, - if(!callback){ - delete _observers[key]; - return; + /** + * Sets a TTL for a key, or remove it if ttl value is 0 or below + * + * @param {String} key - key to set the TTL for + * @param {Number} ttl - TTL timeout in milliseconds + * @return {Boolean} true if key existed or false if it didn't + */ + setTTL: function(key, ttl){ + var curtime = +new Date(); + _checkKey(key); + ttl = Number(ttl) || 0; + if(key in _storage){ + + if(!_storage.__jstorage_meta.TTL){ + _storage.__jstorage_meta.TTL = {}; } - for(var i = _observers[key].length - 1; i>=0; i--){ - if(_observers[key][i] == callback){ - _observers[key].splice(i,1); - } - } - }, - - /** - * Subscribe to a Publish/Subscribe event stream - * - * @param {String} channel Channel name - * @param {Function} callback Function to run when the something is published to the channel - */ - subscribe: function(channel, callback){ - channel = (channel || "").toString(); - if(!channel){ - throw new TypeError('Channel not defined'); + // Set TTL value for the key + if(ttl>0){ + _storage.__jstorage_meta.TTL[key] = curtime + ttl; + }else{ + delete _storage.__jstorage_meta.TTL[key]; } - if(!_pubsub_observers[channel]){ - _pubsub_observers[channel] = []; + + _save(); + + _handleTTL(); + + _publishChange(); + return true; + } + return false; + }, + + /** + * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set + * + * @param {String} key Key to check + * @return {Number} Remaining TTL in milliseconds + */ + getTTL: function(key){ + var curtime = +new Date(), ttl; + _checkKey(key); + if(key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]){ + ttl = _storage.__jstorage_meta.TTL[key] - curtime; + return ttl || 0; + } + return 0; + }, + + /** + * Deletes everything in cache. + * + * @return {Boolean} Always true + */ + flush: function(){ + _storage = {__jstorage_meta:{CRC32:{}}}; + _save(); + _publishChange(); + _fireObservers(null, "flushed"); + return true; + }, + + /** + * Returns a read-only copy of _storage + * + * @return {Object} Read-only copy of _storage + */ + storageObj: function(){ + function F() {} + F.prototype = _storage; + return new F(); + }, + + /** + * Returns an index of all used keys as an array + * ['key1', 'key2',..'keyN'] + * + * @return {Array} Used keys + */ + index: function(){ + var index = [], i; + for(i in _storage){ + if(_storage.hasOwnProperty(i) && i != "__jstorage_meta"){ + index.push(i); } - _pubsub_observers[channel].push(callback); - }, - - /** - * Publish data to an event stream - * - * @param {String} channel Channel name - * @param {Mixed} payload Payload to deliver - */ - publish: function(channel, payload){ - channel = (channel || "").toString(); - if(!channel){ - throw new TypeError('Channel not defined'); + } + return index; + }, + + /** + * How much space in bytes does the storage take? + * + * @return {Number} Storage size in chars (not the same as in bytes, + * since some chars may take several bytes) + */ + storageSize: function(){ + return _storage_size; + }, + + /** + * Which backend is currently in use? + * + * @return {String} Backend name + */ + currentBackend: function(){ + return _backend; + }, + + /** + * Test if storage is available + * + * @return {Boolean} True if storage can be used + */ + storageAvailable: function(){ + return !!_backend; + }, + + /** + * Register change listeners + * + * @param {String} key Key name + * @param {Function} callback Function to run when the key changes + */ + listenKeyChange: function(key, callback){ + _checkKey(key); + if(!_observers[key]){ + _observers[key] = []; + } + _observers[key].push(callback); + }, + + /** + * Remove change listeners + * + * @param {String} key Key name to unregister listeners against + * @param {Function} [callback] If set, unregister the callback, if not - unregister all + */ + stopListening: function(key, callback){ + _checkKey(key); + + if(!_observers[key]){ + return; + } + + if(!callback){ + delete _observers[key]; + return; + } + + for(var i = _observers[key].length - 1; i>=0; i--){ + if(_observers[key][i] == callback){ + _observers[key].splice(i,1); } + } + }, - _publish(channel, payload); - }, + /** + * Subscribe to a Publish/Subscribe event stream + * + * @param {String} channel Channel name + * @param {Function} callback Function to run when the something is published to the channel + */ + subscribe: function(channel, callback){ + channel = (channel || "").toString(); + if(!channel){ + throw new TypeError('Channel not defined'); + } + if(!_pubsub_observers[channel]){ + _pubsub_observers[channel] = []; + } + _pubsub_observers[channel].push(callback); + }, - /** - * Reloads the data from browser storage - */ - reInit: function(){ - _reloadData(); + /** + * Publish data to an event stream + * + * @param {String} channel Channel name + * @param {Mixed} payload Payload to deliver + */ + publish: function(channel, payload){ + channel = (channel || "").toString(); + if(!channel){ + throw new TypeError('Channel not defined'); } - }; - // Initialize jStorage - _init(); + _publish(channel, payload); + }, - return jStorageIntance; - } - } + /** + * Reloads the data from browser storage + */ + reInit: function(){ + _reloadData(); + } + }; + // Initialize jStorage + _init(); + + return jStorageIntance; + } +} + +var JSON = { + parse: + window.JSON && (window.JSON.parse || window.JSON.decode) || + String.prototype.evalJSON && function(str){return String(str).evalJSON();} || + $.parseJSON || + $.evalJSON, + stringify: + Object.toJSON || + window.JSON && (window.JSON.stringify || window.JSON.encode) || + $.toJSON }; -module.exports = new jStorage().createInstance(); +if (!jStorage) { + jStorage = new JStorage(JSON).createInstance(); +} + +module.exports = jStorage; \ No newline at end of file