2 * ----------------------------- JSTORAGE -------------------------------------
3 * Simple local storage wrapper to save data on the browser side, supporting
4 * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+
6 * Copyright (c) 2010 - 2012 Andris Reinman, andris.reinman@gmail.com
7 * Project homepage: www.jstorage.info
9 * Licensed under MIT-style license:
11 * Permission is hereby granted, free of charge, to any person obtaining a copy
12 * of this software and associated documentation files (the "Software"), to deal
13 * in the Software without restriction, including without limitation the rights
14 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 * copies of the Software, and to permit persons to whom the Software is
16 * furnished to do so, subject to the following conditions:
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 /* jStorage version */
30 JSTORAGE_VERSION = "0.3.0",
32 /* detect a dollar object or create one if not found */
33 $ = window.jQuery || window.$ || (window.$ = {}),
35 /* check for a JSON handling support */
38 window.JSON && (window.JSON.parse || window.JSON.decode) ||
39 String.prototype.evalJSON && function(str){return String(str).evalJSON();} ||
44 window.JSON && (window.JSON.stringify || window.JSON.encode) ||
48 // Break if no JSON support was found
49 if(!JSON.parse || !JSON.stringify){
50 throw new Error("No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page");
54 /* This is the object, that holds the cached values */
57 /* Actual browser storage (localStorage or globalStorage['domain']) */
58 _storage_service = {jStorage:"{}"},
60 /* DOM element for older IE versions, holds userData behavior */
63 /* How much space does the storage take */
66 /* which backend is currently used */
69 /* onchange observers */
72 /* timeout to wait after onchange event */
73 _observer_timeout = false,
75 /* last update time */
78 /* pubsub observers */
79 _pubsub_observers = {},
81 /* skip published items older than current timestamp */
82 _pubsub_last = +new Date(),
84 /* Next check for TTL */
88 _crc32Table = "00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 "+
89 "0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 1DB71064 "+
90 "6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 83D385C7 136C9856 646BA8C0 "+
91 "FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 8D080DF5 3B6E20C8 4C69105E D56041E4 "+
92 "A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B 35B5A8FA 42B2986C DBBBC9D6 ACBCF940 "+
93 "32D86CE3 45DF5C75 DCD60DCF ABD13D59 26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 "+
94 "56B3C423 CFBA9599 B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 "+
95 "C1611DAB B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 "+
96 "E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01 "+
97 "6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 65B0D9C6 "+
98 "12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 4DB26158 3AB551CE "+
99 "A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D D3D6F4FB 4369E96A 346ED9FC AD678846 "+
100 "DA60B8D0 44042D73 33031DE5 AA0A4C5F DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 "+
101 "5768B525 206F85B3 B966D409 CE61E49F 5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 "+
102 "2EB40D81 B7BD5C3B C0BA6CAD EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF "+
103 "04DB2615 73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 "+
104 "7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 6E6B06E7 "+
105 "FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5 D6D6A3E8 "+
106 "A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B D80D2BDA AF0A1B4C "+
107 "36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 CB61B38C BC66831A 256FD2A0 "+
108 "5268E236 CC0C7795 BB0B4703 220216B9 5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 "+
109 "C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 "+
110 "EB0E363F 72076785 05005713 95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D "+
111 "7CDCEFB7 0BDBDF21 86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 "+
112 "18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 166CCF45 "+
113 "A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D 3E6E77DB AED16A4A "+
114 "D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9 BDBDF21C CABAC28A "+
115 "53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF B3667A2E C4614AB8 5D681B02 "+
116 "2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D",
119 * XML encoding and decoding as XML nodes can't be JSON'ized
120 * XML nodes are encoded and decoded if the node is the value to be saved
121 * but not if it's as a property of another object
123 * $.jStorage.set("key", xmlNode); // IS OK
124 * $.jStorage.set("key", {xml: xmlNode}); // NOT OK
129 * Validates a XML node to be XML
130 * based on jQuery.isXML function
132 isXML: function(elm){
133 var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
134 return documentElement ? documentElement.nodeName !== "HTML" : false;
138 * Encodes a XML node to string
139 * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
141 encode: function(xmlNode) {
142 if(!this.isXML(xmlNode)){
145 try{ // Mozilla, Webkit, Opera
146 return new XMLSerializer().serializeToString(xmlNode);
156 * Decodes a XML node from string
157 * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
159 decode: function(xmlString){
160 var dom_parser = ("DOMParser" in window && (new DOMParser()).parseFromString) ||
161 (window.ActiveXObject && function(_xmlString) {
162 var xml_doc = new ActiveXObject('Microsoft.XMLDOM');
163 xml_doc.async = 'false';
164 xml_doc.loadXML(_xmlString);
171 resultXML = dom_parser.call("DOMParser" in window && (new DOMParser()) || window, xmlString, 'text/xml');
172 return this.isXML(resultXML)?resultXML:false;
176 _localStoragePolyfillSetKey = function(){};
179 ////////////////////////// PRIVATE METHODS ////////////////////////
182 * Initialization function. Detects if the browser supports DOM Storage
183 * or userData behavior and behaves accordingly.
186 /* Check if browser supports localStorage */
187 var localStorageReallyWorks = false;
188 if("localStorage" in window){
190 window.localStorage.setItem('_tmptest', 'tmpval');
191 localStorageReallyWorks = true;
192 window.localStorage.removeItem('_tmptest');
193 } catch(BogusQuotaExceededErrorOnIos5) {
194 // Thanks be to iOS5 Private Browsing mode which throws
195 // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
199 if(localStorageReallyWorks){
201 if(window.localStorage) {
202 _storage_service = window.localStorage;
203 _backend = "localStorage";
204 _observer_update = _storage_service.jStorage_update;
206 } catch(E3) {/* Firefox fails when touching localStorage and cookies are disabled */}
208 /* Check if browser supports globalStorage */
209 else if("globalStorage" in window){
211 if(window.globalStorage) {
212 _storage_service = window.globalStorage[window.location.hostname];
213 _backend = "globalStorage";
214 _observer_update = _storage_service.jStorage_update;
216 } catch(E4) {/* Firefox fails when touching localStorage and cookies are disabled */}
218 /* Check if browser supports userData behavior */
220 _storage_elm = document.createElement('link');
221 if(_storage_elm.addBehavior){
223 /* Use a DOM element to act as userData storage */
224 _storage_elm.style.behavior = 'url(#default#userData)';
226 /* userData element needs to be inserted into the DOM! */
227 document.getElementsByTagName('head')[0].appendChild(_storage_elm);
230 _storage_elm.load("jStorage");
232 // try to reset cache
233 _storage_elm.setAttribute("jStorage", "{}");
234 _storage_elm.save("jStorage");
235 _storage_elm.load("jStorage");
240 data = _storage_elm.getAttribute("jStorage");
244 _observer_update = _storage_elm.getAttribute("jStorage_update");
247 _storage_service.jStorage = data;
248 _backend = "userDataBehavior";
255 // Load data from storage
261 // create localStorage and sessionStorage polyfills if needed
262 _createPolyfillStorage("local");
263 _createPolyfillStorage("session");
265 // start listening for changes
268 // initialize publish-subscribe service
271 // handle cached navigation
272 if("addEventListener" in window){
273 window.addEventListener("pageshow", function(event){
282 * Create a polyfill for localStorage (type="local") or sessionStorage (type="session")
284 * @param {String} type Either "local" or "session"
285 * @param {Boolean} forceCreate If set to true, recreate the polyfill (needed with flush)
287 function _createPolyfillStorage(type, forceCreate){
288 var _skipSave = false,
294 var rand = Math.random();
296 if(!forceCreate && typeof window[type+"Storage"] != "undefined"){
300 // Use globalStorage for localStorage if available
301 if(type == "local" && window.globalStorage){
302 localStorage = window.globalStorage[window.location.hostname];
306 // only IE6/7 from this point on
307 if(_backend != "userDataBehavior"){
311 // Remove existing storage element if available
312 if(forceCreate && window[type+"Storage"] && window[type+"Storage"].parentNode){
313 window[type+"Storage"].parentNode.removeChild(window[type+"Storage"]);
316 storage = document.createElement("button");
317 document.getElementsByTagName('head')[0].appendChild(storage);
320 storage_source = _storage;
321 }else if(type == "session"){
322 _sessionStoragePolyfillUpdate();
325 for(i in storage_source){
327 if(storage_source.hasOwnProperty(i) && i != "__jstorage_meta" && i != "length" && typeof storage_source[i] != "undefined"){
331 storage[i] = storage_source[i];
338 * Indicates how many keys are stored in the storage
340 storage.length = _length;
343 * Returns the key of the nth stored value
345 * @param {Number} n Index position
346 * @return {String} Key name of the nth stored value
348 storage.key = function(n){
350 _sessionStoragePolyfillUpdate();
351 for(i in storage_source){
352 if(storage_source.hasOwnProperty(i) && i != "__jstorage_meta" && i!="length" && typeof storage_source[i] != "undefined"){
362 * Returns the current value associated with the given key
364 * @param {String} key key name
365 * @return {Mixed} Stored value
367 storage.getItem = function(key){
368 _sessionStoragePolyfillUpdate();
369 if(type == "session"){
370 return storage_source[key];
372 return $.jStorage.get(key);
376 * Sets or updates value for a give key
378 * @param {String} key Key name to be updated
379 * @param {String} value String value to be stored
381 storage.setItem = function(key, value){
382 if(typeof value == "undefined"){
385 storage[key] = (value || "").toString();
389 * Removes key from the storage
391 * @param {String} key Key name to be removed
393 storage.removeItem = function(key){
395 return $.jStorage.deleteKey(key);
398 storage[key] = undefined;
402 storage.removeAttribute(key);
410 storage.clear = function(){
411 if(type == "session"){
413 _createPolyfillStorage("session", true);
421 _localStoragePolyfillSetKey = function(key, value){
426 if(typeof value == "undefined"){
429 storage.removeAttribute(key);
432 if(!(key in storage)){
435 storage[key] = (value || "").toString();
437 storage.length = _length;
442 function _sessionStoragePolyfillUpdate(){
443 if(type != "session"){
447 storage_source = JSON.parse(window.name || "{}");
453 function _sessionStoragePolyfillSave(){
454 if(type != "session"){
457 window.name = JSON.stringify(storage_source);
460 storage.attachEvent("onpropertychange", function(e){
461 if(e.propertyName == "length"){
465 if(_skipSave || e.propertyName == "length"){
470 if(!(e.propertyName in storage_source) && typeof storage[e.propertyName] != "undefined"){
473 }else if(type == "session"){
474 _sessionStoragePolyfillUpdate();
475 if(typeof storage[e.propertyName] != "undefined" && !(e.propertyName in storage_source)){
476 storage_source[e.propertyName] = storage[e.propertyName];
478 }else if(typeof storage[e.propertyName] == "undefined" && e.propertyName in storage_source){
479 delete storage_source[e.propertyName];
482 storage_source[e.propertyName] = storage[e.propertyName];
485 _sessionStoragePolyfillSave();
486 storage.length = _length;
490 $.jStorage.set(e.propertyName, storage[e.propertyName]);
491 storage.length = _length;
494 window[type+"Storage"] = storage;
498 * Reload data from storage when needed
500 function _reloadData(){
503 if(_backend == "userDataBehavior"){
504 _storage_elm.load("jStorage");
507 data = _storage_elm.getAttribute("jStorage");
511 _observer_update = _storage_elm.getAttribute("jStorage_update");
514 _storage_service.jStorage = data;
526 * Sets up a storage change observer
528 function _setupObserver(){
529 if(_backend == "localStorage" || _backend == "globalStorage"){
530 if("addEventListener" in window){
531 window.addEventListener("storage", _storageObserver, false);
533 document.attachEvent("onstorage", _storageObserver);
535 }else if(_backend == "userDataBehavior"){
536 setInterval(_storageObserver, 1000);
541 * Fired on any kind of data change, needs to check if anything has
542 * really been changed
544 function _storageObserver(){
546 // cumulate change notifications with timeout
547 clearTimeout(_observer_timeout);
548 _observer_timeout = setTimeout(function(){
550 if(_backend == "localStorage" || _backend == "globalStorage"){
551 updateTime = _storage_service.jStorage_update;
552 }else if(_backend == "userDataBehavior"){
553 _storage_elm.load("jStorage");
555 updateTime = _storage_elm.getAttribute("jStorage_update");
559 if(updateTime && updateTime != _observer_update){
560 _observer_update = updateTime;
568 * Reloads the data and checks if any keys are changed
570 function _checkUpdatedKeys(){
571 var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)),
575 newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32));
581 for(key in oldCrc32List){
582 if(oldCrc32List.hasOwnProperty(key)){
583 if(!newCrc32List[key]){
587 if(oldCrc32List[key] != newCrc32List[key]){
593 for(key in newCrc32List){
594 if(newCrc32List.hasOwnProperty(key)){
595 if(!oldCrc32List[key]){
601 _fireObservers(updated, "updated");
602 _fireObservers(removed, "deleted");
606 * Fires observers for updated keys
608 * @param {Array|String} keys Array of key names or a key
609 * @param {String} action What happened with the value (updated, deleted, flushed)
611 function _fireObservers(keys, action){
612 keys = [].concat(keys || []);
613 if(action == "flushed"){
615 for(var key in _observers){
616 if(_observers.hasOwnProperty(key)){
622 for(var i=0, len = keys.length; i<len; i++){
623 if(_observers[keys[i]]){
624 for(var j=0, jlen = _observers[keys[i]].length; j<jlen; j++){
625 _observers[keys[i]][j](keys[i], action);
632 * Publishes key change to listeners
634 function _publishChange(){
635 var updateTime = (+new Date()).toString();
637 if(_backend == "localStorage" || _backend == "globalStorage"){
638 _storage_service.jStorage_update = updateTime;
639 }else if(_backend == "userDataBehavior"){
640 _storage_elm.setAttribute("jStorage_update", updateTime);
641 _storage_elm.save("jStorage");
648 * Loads the data from the storage based on the supported mechanism
650 function _load_storage(){
651 /* if jStorage string is retrieved, then decode it */
652 if(_storage_service.jStorage){
654 _storage = JSON.parse(String(_storage_service.jStorage));
655 }catch(E6){_storage_service.jStorage = "{}";}
657 _storage_service.jStorage = "{}";
659 _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;
661 if(!_storage.__jstorage_meta){
662 _storage.__jstorage_meta = {};
664 if(!_storage.__jstorage_meta.CRC32){
665 _storage.__jstorage_meta.CRC32 = {};
670 * This functions provides the "save" mechanism to store the jStorage object
673 _dropOldEvents(); // remove expired events
675 _storage_service.jStorage = JSON.stringify(_storage);
676 // If userData is used as the storage engine, additional
678 _storage_elm.setAttribute("jStorage",_storage_service.jStorage);
679 _storage_elm.save("jStorage");
681 _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;
682 }catch(E7){/* probably cache is full, nothing is saved this way*/}
686 * Function checks if a key is set and is string or numberic
688 * @param {String} key Key name
690 function _checkKey(key){
691 if(!key || (typeof key != "string" && typeof key != "number")){
692 throw new TypeError('Key name must be string or numeric');
694 if(key == "__jstorage_meta"){
695 throw new TypeError('Reserved key name');
701 * Removes expired keys
703 function _handleTTL(){
704 var curtime, i, TTL, CRC32, nextExpire = Infinity, changed = false, deleted = [];
706 clearTimeout(_ttl_timeout);
708 if(!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != "object"){
709 // nothing to do here
713 curtime = +new Date();
714 TTL = _storage.__jstorage_meta.TTL;
716 CRC32 = _storage.__jstorage_meta.CRC32;
718 if(TTL.hasOwnProperty(i)){
719 if(TTL[i] <= curtime){
725 }else if(TTL[i] < nextExpire){
732 if(nextExpire != Infinity){
733 _ttl_timeout = setTimeout(_handleTTL, nextExpire - curtime);
740 _fireObservers(deleted, "deleted");
745 * Checks if there's any events on hold to be fired to listeners
747 function _handlePubSub(){
748 if(!_storage.__jstorage_meta.PubSub){
752 _pubsubCurrent = _pubsub_last;
754 for(var i=len=_storage.__jstorage_meta.PubSub.length-1; i>=0; i--){
755 pubelm = _storage.__jstorage_meta.PubSub[i];
756 if(pubelm[0] > _pubsub_last){
757 _pubsubCurrent = pubelm[0];
758 _fireSubscribers(pubelm[1], pubelm[2]);
762 _pubsub_last = _pubsubCurrent;
766 * Fires all subscriber listeners for a pubsub channel
768 * @param {String} channel Channel name
769 * @param {Mixed} payload Payload data to deliver
771 function _fireSubscribers(channel, payload){
772 if(_pubsub_observers[channel]){
773 for(var i=0, len = _pubsub_observers[channel].length; i<len; i++){
774 // send immutable data that can't be modified by listeners
775 _pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload)));
781 * Remove old events from the publish stream (at least 2sec old)
783 function _dropOldEvents(){
784 if(!_storage.__jstorage_meta.PubSub){
788 var retire = +new Date() - 2000;
790 for(var i=0, len = _storage.__jstorage_meta.PubSub.length; i<len; i++){
791 if(_storage.__jstorage_meta.PubSub[i][0] <= retire){
792 // deleteCount is needed for IE6
793 _storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i);
798 if(!_storage.__jstorage_meta.PubSub.length){
799 delete _storage.__jstorage_meta.PubSub;
805 * Publish payload to a channel
807 * @param {String} channel Channel name
808 * @param {Mixed} payload Payload to send to the subscribers
810 function _publish(channel, payload){
811 if(!_storage.__jstorage_meta){
812 _storage.__jstorage_meta = {};
814 if(!_storage.__jstorage_meta.PubSub){
815 _storage.__jstorage_meta.PubSub = [];
818 _storage.__jstorage_meta.PubSub.unshift([+new Date, channel, payload]);
825 * CRC32 calculation based on http://noteslog.com/post/crc32-for-javascript/
827 * @param {String} str String to be hashed
828 * @param {Number} [crc] Last crc value in case of streams
830 function _crc32(str, crc){
833 var n = 0, //a number between 0 and 255
834 x = 0; //an hex number
837 for(var i = 0, len = str.length; i < len; i++){
838 n = (crc ^ str.charCodeAt(i)) & 0xFF;
839 x = "0x" + _crc32Table.substr(n * 9, 8);
845 ////////////////////////// PUBLIC INTERFACE /////////////////////////
849 version: JSTORAGE_VERSION,
852 * Sets a key's value.
854 * @param {String} key Key to set. If this value is not set or not
855 * a string an exception is raised.
856 * @param {Mixed} value Value to set. This can be any value that is JSON
857 * compatible (Numbers, Strings, Objects etc.).
858 * @param {Object} [options] - possible options to use
859 * @param {Number} [options.TTL] - optional TTL value
860 * @return {Mixed} the used value
862 set: function(key, value, options){
865 options = options || {};
867 // undefined values are deleted automatically
868 if(typeof value == "undefined"){
873 if(_XMLService.isXML(value)){
874 value = {_is_xml:true,xml:_XMLService.encode(value)};
875 }else if(typeof value == "function"){
876 return undefined; // functions can't be saved!
877 }else if(value && typeof value == "object"){
878 // clone the object before saving to _storage tree
879 value = JSON.parse(JSON.stringify(value));
882 _storage[key] = value;
884 _storage.__jstorage_meta.CRC32[key] = _crc32(JSON.stringify(value));
886 this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange
888 _localStoragePolyfillSetKey(key, value);
890 _fireObservers(key, "updated");
895 * Looks up a key in cache
897 * @param {String} key - Key to look up.
898 * @param {mixed} def - Default value to return, if key didn't exist.
899 * @return {Mixed} the key value, default value or null
901 get: function(key, def){
904 if(_storage[key] && typeof _storage[key] == "object" &&
905 _storage[key]._is_xml &&
906 _storage[key]._is_xml){
907 return _XMLService.decode(_storage[key].xml);
909 return _storage[key];
912 return typeof(def) == 'undefined' ? null : def;
916 * Deletes a key from cache.
918 * @param {String} key - Key to delete.
919 * @return {Boolean} true if key existed or false if it didn't
921 deleteKey: function(key){
924 delete _storage[key];
925 // remove from TTL list
926 if(typeof _storage.__jstorage_meta.TTL == "object" &&
927 key in _storage.__jstorage_meta.TTL){
928 delete _storage.__jstorage_meta.TTL[key];
931 delete _storage.__jstorage_meta.CRC32[key];
932 _localStoragePolyfillSetKey(key, undefined);
936 _fireObservers(key, "deleted");
943 * Sets a TTL for a key, or remove it if ttl value is 0 or below
945 * @param {String} key - key to set the TTL for
946 * @param {Number} ttl - TTL timeout in milliseconds
947 * @return {Boolean} true if key existed or false if it didn't
949 setTTL: function(key, ttl){
950 var curtime = +new Date();
952 ttl = Number(ttl) || 0;
955 if(!_storage.__jstorage_meta.TTL){
956 _storage.__jstorage_meta.TTL = {};
959 // Set TTL value for the key
961 _storage.__jstorage_meta.TTL[key] = curtime + ttl;
963 delete _storage.__jstorage_meta.TTL[key];
977 * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set
979 * @param {String} key Key to check
980 * @return {Number} Remaining TTL in milliseconds
982 getTTL: function(key){
983 var curtime = +new Date(), ttl;
985 if(key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]){
986 ttl = _storage.__jstorage_meta.TTL[key] - curtime;
993 * Deletes everything in cache.
995 * @return {Boolean} Always true
998 _storage = {__jstorage_meta:{CRC32:{}}};
999 _createPolyfillStorage("local", true);
1002 _fireObservers(null, "flushed");
1007 * Returns a read-only copy of _storage
1009 * @return {Object} Read-only copy of _storage
1011 storageObj: function(){
1013 F.prototype = _storage;
1018 * Returns an index of all used keys as an array
1019 * ['key1', 'key2',..'keyN']
1021 * @return {Array} Used keys
1026 if(_storage.hasOwnProperty(i) && i != "__jstorage_meta"){
1034 * How much space in bytes does the storage take?
1036 * @return {Number} Storage size in chars (not the same as in bytes,
1037 * since some chars may take several bytes)
1039 storageSize: function(){
1040 return _storage_size;
1044 * Which backend is currently in use?
1046 * @return {String} Backend name
1048 currentBackend: function(){
1053 * Test if storage is available
1055 * @return {Boolean} True if storage can be used
1057 storageAvailable: function(){
1062 * Register change listeners
1064 * @param {String} key Key name
1065 * @param {Function} callback Function to run when the key changes
1067 listenKeyChange: function(key, callback){
1069 if(!_observers[key]){
1070 _observers[key] = [];
1072 _observers[key].push(callback);
1076 * Remove change listeners
1078 * @param {String} key Key name to unregister listeners against
1079 * @param {Function} [callback] If set, unregister the callback, if not - unregister all
1081 stopListening: function(key, callback){
1084 if(!_observers[key]){
1089 delete _observers[key];
1093 for(var i = _observers[key].length - 1; i>=0; i--){
1094 if(_observers[key][i] == callback){
1095 _observers[key].splice(i,1);
1101 * Subscribe to a Publish/Subscribe event stream
1103 * @param {String} channel Channel name
1104 * @param {Function} callback Function to run when the something is published to the channel
1106 subscribe: function(channel, callback){
1107 channel = (channel || "").toString();
1109 throw new TypeError('Channel not defined');
1111 if(!_pubsub_observers[channel]){
1112 _pubsub_observers[channel] = [];
1114 _pubsub_observers[channel].push(callback);
1118 * Publish data to an event stream
1120 * @param {String} channel Channel name
1121 * @param {Mixed} payload Payload to deliver
1123 publish: function(channel, payload){
1124 channel = (channel || "").toString();
1126 throw new TypeError('Channel not defined');
1129 _publish(channel, payload);
1133 * Reloads the data from browser storage
1140 // Initialize jStorage