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 Andris Reinman, andris.reinman@gmail.com
7 * Project homepage: www.jstorage.info
9 * Taken from Github with slight modifications by Hoo man
10 * https://raw.github.com/andris9/jStorage/master/jstorage.js
12 * Licensed under MIT-style license:
14 * Permission is hereby granted, free of charge, to any person obtaining a copy
15 * of this software and associated documentation files (the "Software"), to deal
16 * in the Software without restriction, including without limitation the rights
17 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 * copies of the Software, and to permit persons to whom the Software is
19 * furnished to do so, subject to the following conditions:
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35 * jStorage requires Prototype, MooTools or jQuery! If jQuery is used, then
36 * jQuery-JSON (http://code.google.com/p/jquery-json/) is also needed.
37 * (jQuery-JSON needs to be loaded BEFORE jStorage!)
41 * -set(key, value[, options])
42 * $.jStorage.set(key, value) -> saves a value
44 * -get(key[, default])
45 * value = $.jStorage.get(key [, default]) ->
46 * retrieves value if key exists, or default if it doesn't
49 * $.jStorage.deleteKey(key) -> removes a key from the storage
52 * $.jStorage.flush() -> clears the cache
55 * $.jStorage.storageObj() -> returns a read-ony copy of the actual storage
58 * $.jStorage.storageSize() -> returns the size of the storage in bytes
61 * $.jStorage.index() -> returns the used keys as an array
64 * $.jStorage.storageAvailable() -> returns true if storage is available
67 * $.jStorage.reInit() -> reloads the data from browser storage
69 * <value> can be any JSON-able value, including objects and arrays.
74 if(!$ || !($.toJSON
|| Object
.toJSON
|| window
.JSON
)){
75 throw new Error("jQuery, MooTools or Prototype needs to be loaded before jStorage!");
79 /* This is the object, that holds the cached values */
82 /* Actual browser storage (localStorage or globalStorage['domain']) */
83 _storage_service
= {jStorage
:"{}"},
85 /* DOM element for older IE versions, holds userData behavior */
88 /* How much space does the storage take */
91 /* function to encode objects to JSON strings */
92 json_encode
= $.toJSON
|| Object
.toJSON
|| (window
.JSON
&& (JSON
.encode
|| JSON
.stringify
)),
94 /* function to decode objects from JSON strings */
95 json_decode
= $.evalJSON
|| (window
.JSON
&& (JSON
.decode
|| JSON
.parse
)) || function(str
){
96 return String(str
).evalJSON();
99 /* which backend is currently used */
102 /* Next check for TTL */
106 * XML encoding and decoding as XML nodes can't be JSON'ized
107 * XML nodes are encoded and decoded if the node is the value to be saved
108 * but not if it's as a property of another object
110 * $.jStorage.set("key", xmlNode); // IS OK
111 * $.jStorage.set("key", {xml: xmlNode}); // NOT OK
116 * Validates a XML node to be XML
117 * based on jQuery.isXML function
119 isXML: function(elm
){
120 var documentElement
= (elm
? elm
.ownerDocument
|| elm
: 0).documentElement
;
121 return documentElement
? documentElement
.nodeName
!== "HTML" : false;
125 * Encodes a XML node to string
126 * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
128 encode: function(xmlNode
) {
129 if(!this.isXML(xmlNode
)){
132 try{ // Mozilla, Webkit, Opera
133 return new XMLSerializer().serializeToString(xmlNode
);
143 * Decodes a XML node from string
144 * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
146 decode: function(xmlString
){
147 var dom_parser
= ("DOMParser" in window
&& (new DOMParser()).parseFromString
) ||
148 (window
.ActiveXObject
&& function(_xmlString
) {
149 var xml_doc
= new ActiveXObject('Microsoft.XMLDOM');
150 xml_doc
.async
= 'false';
151 xml_doc
.loadXML(_xmlString
);
158 resultXML
= dom_parser
.call("DOMParser" in window
&& (new DOMParser()) || window
, xmlString
, 'text/xml');
159 return this.isXML(resultXML
)?resultXML
:false;
163 ////////////////////////// PRIVATE METHODS ////////////////////////
166 * Initialization function. Detects if the browser supports DOM Storage
167 * or userData behavior and behaves accordingly.
171 /* Check if browser supports localStorage */
172 var localStorageReallyWorks
= false;
173 if("localStorage" in window
){
175 window
.localStorage
.setItem('_tmptest', 'tmpval');
176 localStorageReallyWorks
= true;
177 window
.localStorage
.removeItem('_tmptest');
178 } catch(BogusQuotaExceededErrorOnIos5
) {
179 // Thanks be to iOS5 Private Browsing mode which throws
180 // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
183 if(localStorageReallyWorks
){
185 if(window
.localStorage
) {
186 _storage_service
= window
.localStorage
;
187 _backend
= "localStorage";
189 } catch(E3
) {/* Firefox fails when touching localStorage and cookies are disabled */}
191 /* Check if browser supports globalStorage */
192 else if("globalStorage" in window
){
194 if(window
.globalStorage
) {
195 _storage_service
= window
.globalStorage
[window
.location
.hostname
];
196 _backend
= "globalStorage";
198 } catch(E4
) {/* Firefox fails when touching localStorage and cookies are disabled */}
200 /* Check if browser supports userData behavior */
202 _storage_elm
= document
.createElement('link');
203 if(_storage_elm
.addBehavior
){
205 /* Use a DOM element to act as userData storage */
206 _storage_elm
.style
.behavior
= 'url(#default#userData)';
208 /* userData element needs to be inserted into the DOM! */
209 document
.getElementsByTagName('head')[0].appendChild(_storage_elm
);
211 _storage_elm
.load("jStorage");
214 data
= _storage_elm
.getAttribute("jStorage");
216 _storage_service
.jStorage
= data
;
217 _backend
= "userDataBehavior";
231 * Loads the data from the storage based on the supported mechanism
234 function _load_storage(){
235 /* if jStorage string is retrieved, then decode it */
236 if(_storage_service
.jStorage
){
238 _storage
= json_decode(String(_storage_service
.jStorage
));
239 }catch(E6
){_storage_service
.jStorage
= "{}";}
241 _storage_service
.jStorage
= "{}";
243 _storage_size
= _storage_service
.jStorage
?String(_storage_service
.jStorage
).length
:0;
247 * This functions provides the "save" mechanism to store the jStorage object
252 _storage_service
.jStorage
= json_encode(_storage
);
253 // If userData is used as the storage engine, additional
255 _storage_elm
.setAttribute("jStorage",_storage_service
.jStorage
);
256 _storage_elm
.save("jStorage");
258 _storage_size
= _storage_service
.jStorage
?String(_storage_service
.jStorage
).length
:0;
259 }catch(E7
){/* probably cache is full, nothing is saved this way*/}
263 * Function checks if a key is set and is string or numberic
265 function _checkKey(key
){
266 if(!key
|| (typeof key
!== "string" && typeof key
!== "number")){
267 throw new TypeError('Key name must be string or numeric');
269 if(key
=== "__jstorage_meta"){
270 throw new TypeError('Reserved key name');
276 * Removes expired keys
278 function _handleTTL(){
279 var curtime
, i
, TTL
, nextExpire
= Infinity
, changed
= false;
281 clearTimeout(_ttl_timeout
);
283 if(!_storage
.__jstorage_meta
|| typeof _storage
.__jstorage_meta
.TTL
!== "object"){
284 // nothing to do here
288 curtime
= +new Date();
289 TTL
= _storage
.__jstorage_meta
.TTL
;
291 if(TTL
.hasOwnProperty(i
)){
292 if(TTL
[i
] <= curtime
){
296 }else if(TTL
[i
] < nextExpire
){
303 if(nextExpire
!= Infinity
){
304 _ttl_timeout
= setTimeout(_handleTTL
, nextExpire
- curtime
);
313 ////////////////////////// PUBLIC INTERFACE /////////////////////////
320 * Sets a key's value.
322 * @param {String} key - Key to set. If this value is not set or not
323 * a string an exception is raised.
324 * @param {Mixed} value - Value to set. This can be any value that is JSON
325 * compatible (Numbers, Strings, Objects etc.).
326 * @param {Object} [options] - possible options to use
327 * @param {Number} [options.TTL] - optional TTL value
328 * @returns the used value
330 set: function(key
, value
, options
){
333 options
= options
|| {};
335 if(_XMLService
.isXML(value
)){
336 value
= {_is_xml
:true,xml
:_XMLService
.encode(value
)};
337 }else if(typeof value
=== "function"){
338 value
= null; // functions can't be saved!
339 }else if(value
&& typeof value
=== "object"){
340 // clone the object before saving to _storage tree
341 value
= json_decode(json_encode(value
));
343 _storage
[key
] = value
;
345 if(!isNaN(options
.TTL
)){
346 this.setTTL(key
, options
.TTL
);
347 // also handles saving
355 * Looks up a key in cache
357 * @param {String} key - Key to look up.
358 * @param {mixed} def - Default value to return, if key didn't exist.
359 * @returns the key value, default value or <null>
361 get: function(key
, def
){
364 if(_storage
[key
] && typeof _storage
[key
] === "object" &&
365 _storage
[key
]._is_xml
&&
366 _storage
[key
]._is_xml
){
367 return _XMLService
.decode(_storage
[key
].xml
);
369 return _storage
[key
];
372 return typeof(def
) === 'undefined' ? null : def
;
376 * Deletes a key from cache.
378 * @param {String} key - Key to delete.
379 * @returns true if key existed or false if it didn't
381 deleteKey: function(key
){
384 delete _storage
[key
];
385 // remove from TTL list
386 if(_storage
.__jstorage_meta
&&
387 typeof _storage
.__jstorage_meta
.TTL
=== "object" &&
388 key
in _storage
.__jstorage_meta
.TTL
){
389 delete _storage
.__jstorage_meta
.TTL
[key
];
398 * Sets a TTL for a key, or remove it if ttl value is 0 or below
400 * @param {String} key - key to set the TTL for
401 * @param {Number} ttl - TTL timeout in milliseconds
402 * @returns true if key existed or false if it didn't
404 setTTL: function(key
, ttl
){
405 var curtime
= +new Date();
407 ttl
= Number(ttl
) || 0;
410 if(!_storage
.__jstorage_meta
){
411 _storage
.__jstorage_meta
= {};
413 if(!_storage
.__jstorage_meta
.TTL
){
414 _storage
.__jstorage_meta
.TTL
= {};
417 // Set TTL value for the key
419 _storage
.__jstorage_meta
.TTL
[key
] = curtime
+ ttl
;
421 delete _storage
.__jstorage_meta
.TTL
[key
];
433 * Deletes everything in cache.
444 * Returns a read-only copy of _storage
448 storageObj: function(){
450 F
.prototype = _storage
;
455 * Returns an index of all used keys as an array
456 * ['key1', 'key2',..'keyN']
463 if(_storage
.hasOwnProperty(i
) && i
!== "__jstorage_meta"){
471 * How much space in bytes does the storage take?
475 storageSize: function(){
476 return _storage_size
;
480 * Which backend is currently in use?
484 currentBackend: function(){
489 * Test if storage is available
493 storageAvailable: function(){
498 * Reloads the data from browser storage
503 var new_storage_elm
, data
;
504 if(_storage_elm
&& _storage_elm
.addBehavior
){
505 new_storage_elm
= document
.createElement('link');
507 _storage_elm
.parentNode
.replaceChild(new_storage_elm
, _storage_elm
);
508 _storage_elm
= new_storage_elm
;
510 /* Use a DOM element to act as userData storage */
511 _storage_elm
.style
.behavior
= 'url(#default#userData)';
513 /* userData element needs to be inserted into the DOM! */
514 document
.getElementsByTagName('head')[0].appendChild(_storage_elm
);
516 _storage_elm
.load("jStorage");
519 data
= _storage_elm
.getAttribute("jStorage");
521 _storage_service
.jStorage
= data
;
522 _backend
= "userDataBehavior";
529 // Initialize jStorage
532 })(window
.$ || window
.jQuery
);