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