Merge "Revert "Revert "jQuery 1.8"""
[lhc/web/wiklou.git] / resources / jquery / jquery.jStorage.js
1 /*
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+
5 *
6 * Copyright (c) 2010 Andris Reinman, andris.reinman@gmail.com
7 * Project homepage: www.jstorage.info
8 *
9 * Taken from Github with slight modifications by Hoo man
10 * https://raw.github.com/andris9/jStorage/master/jstorage.js
11 *
12 * Licensed under MIT-style license:
13 *
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:
20 *
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
27 * SOFTWARE.
28 */
29
30 /**
31 * $.jStorage
32 *
33 * USAGE:
34 *
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!)
38 *
39 * Methods:
40 *
41 * -set(key, value[, options])
42 * $.jStorage.set(key, value) -> saves a value
43 *
44 * -get(key[, default])
45 * value = $.jStorage.get(key [, default]) ->
46 * retrieves value if key exists, or default if it doesn't
47 *
48 * -deleteKey(key)
49 * $.jStorage.deleteKey(key) -> removes a key from the storage
50 *
51 * -flush()
52 * $.jStorage.flush() -> clears the cache
53 *
54 * -storageObj()
55 * $.jStorage.storageObj() -> returns a read-ony copy of the actual storage
56 *
57 * -storageSize()
58 * $.jStorage.storageSize() -> returns the size of the storage in bytes
59 *
60 * -index()
61 * $.jStorage.index() -> returns the used keys as an array
62 *
63 * -storageAvailable()
64 * $.jStorage.storageAvailable() -> returns true if storage is available
65 *
66 * -reInit()
67 * $.jStorage.reInit() -> reloads the data from browser storage
68 *
69 * <value> can be any JSON-able value, including objects and arrays.
70 *
71 **/
72
73 (function($){
74 if(!$ || !($.toJSON || Object.toJSON || window.JSON)){
75 throw new Error("jQuery, MooTools or Prototype needs to be loaded before jStorage!");
76 }
77
78 var
79 /* This is the object, that holds the cached values */
80 _storage = {},
81
82 /* Actual browser storage (localStorage or globalStorage['domain']) */
83 _storage_service = {jStorage:"{}"},
84
85 /* DOM element for older IE versions, holds userData behavior */
86 _storage_elm = null,
87
88 /* How much space does the storage take */
89 _storage_size = 0,
90
91 /* function to encode objects to JSON strings */
92 json_encode = $.toJSON || Object.toJSON || (window.JSON && (JSON.encode || JSON.stringify)),
93
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();
97 },
98
99 /* which backend is currently used */
100 _backend = false,
101
102 /* Next check for TTL */
103 _ttl_timeout,
104
105 /**
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
109 * Eg. -
110 * $.jStorage.set("key", xmlNode); // IS OK
111 * $.jStorage.set("key", {xml: xmlNode}); // NOT OK
112 */
113 _XMLService = {
114
115 /**
116 * Validates a XML node to be XML
117 * based on jQuery.isXML function
118 */
119 isXML: function(elm){
120 var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
121 return documentElement ? documentElement.nodeName !== "HTML" : false;
122 },
123
124 /**
125 * Encodes a XML node to string
126 * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
127 */
128 encode: function(xmlNode) {
129 if(!this.isXML(xmlNode)){
130 return false;
131 }
132 try{ // Mozilla, Webkit, Opera
133 return new XMLSerializer().serializeToString(xmlNode);
134 }catch(E1) {
135 try { // IE
136 return xmlNode.xml;
137 }catch(E2){}
138 }
139 return false;
140 },
141
142 /**
143 * Decodes a XML node from string
144 * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
145 */
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);
152 return xml_doc;
153 }),
154 resultXML;
155 if(!dom_parser){
156 return false;
157 }
158 resultXML = dom_parser.call("DOMParser" in window && (new DOMParser()) || window, xmlString, 'text/xml');
159 return this.isXML(resultXML)?resultXML:false;
160 }
161 };
162
163 ////////////////////////// PRIVATE METHODS ////////////////////////
164
165 /**
166 * Initialization function. Detects if the browser supports DOM Storage
167 * or userData behavior and behaves accordingly.
168 * @returns undefined
169 */
170 function _init(){
171 /* Check if browser supports localStorage */
172 var localStorageReallyWorks = false;
173 if("localStorage" in window){
174 try {
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.
181 }
182 }
183 if(localStorageReallyWorks){
184 try {
185 if(window.localStorage) {
186 _storage_service = window.localStorage;
187 _backend = "localStorage";
188 }
189 } catch(E3) {/* Firefox fails when touching localStorage and cookies are disabled */}
190 }
191 /* Check if browser supports globalStorage */
192 else if("globalStorage" in window){
193 try {
194 if(window.globalStorage) {
195 _storage_service = window.globalStorage[window.location.hostname];
196 _backend = "globalStorage";
197 }
198 } catch(E4) {/* Firefox fails when touching localStorage and cookies are disabled */}
199 }
200 /* Check if browser supports userData behavior */
201 else {
202 _storage_elm = document.createElement('link');
203 if(_storage_elm.addBehavior){
204
205 /* Use a DOM element to act as userData storage */
206 _storage_elm.style.behavior = 'url(#default#userData)';
207
208 /* userData element needs to be inserted into the DOM! */
209 document.getElementsByTagName('head')[0].appendChild(_storage_elm);
210
211 _storage_elm.load("jStorage");
212 var data = "{}";
213 try{
214 data = _storage_elm.getAttribute("jStorage");
215 }catch(E5){}
216 _storage_service.jStorage = data;
217 _backend = "userDataBehavior";
218 }else{
219 _storage_elm = null;
220 return;
221 }
222 }
223
224 _load_storage();
225
226 // remove dead keys
227 _handleTTL();
228 }
229
230 /**
231 * Loads the data from the storage based on the supported mechanism
232 * @returns undefined
233 */
234 function _load_storage(){
235 /* if jStorage string is retrieved, then decode it */
236 if(_storage_service.jStorage){
237 try{
238 _storage = json_decode(String(_storage_service.jStorage));
239 }catch(E6){_storage_service.jStorage = "{}";}
240 }else{
241 _storage_service.jStorage = "{}";
242 }
243 _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;
244 }
245
246 /**
247 * This functions provides the "save" mechanism to store the jStorage object
248 * @returns undefined
249 */
250 function _save(){
251 try{
252 _storage_service.jStorage = json_encode(_storage);
253 // If userData is used as the storage engine, additional
254 if(_storage_elm) {
255 _storage_elm.setAttribute("jStorage",_storage_service.jStorage);
256 _storage_elm.save("jStorage");
257 }
258 _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;
259 }catch(E7){/* probably cache is full, nothing is saved this way*/}
260 }
261
262 /**
263 * Function checks if a key is set and is string or numberic
264 */
265 function _checkKey(key){
266 if(!key || (typeof key !== "string" && typeof key !== "number")){
267 throw new TypeError('Key name must be string or numeric');
268 }
269 if(key === "__jstorage_meta"){
270 throw new TypeError('Reserved key name');
271 }
272 return true;
273 }
274
275 /**
276 * Removes expired keys
277 */
278 function _handleTTL(){
279 var curtime, i, TTL, nextExpire = Infinity, changed = false;
280
281 clearTimeout(_ttl_timeout);
282
283 if(!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL !== "object"){
284 // nothing to do here
285 return;
286 }
287
288 curtime = +new Date();
289 TTL = _storage.__jstorage_meta.TTL;
290 for(i in TTL){
291 if(TTL.hasOwnProperty(i)){
292 if(TTL[i] <= curtime){
293 delete TTL[i];
294 delete _storage[i];
295 changed = true;
296 }else if(TTL[i] < nextExpire){
297 nextExpire = TTL[i];
298 }
299 }
300 }
301
302 // set next check
303 if(nextExpire != Infinity){
304 _ttl_timeout = setTimeout(_handleTTL, nextExpire - curtime);
305 }
306
307 // save changes
308 if(changed){
309 _save();
310 }
311 }
312
313 ////////////////////////// PUBLIC INTERFACE /////////////////////////
314
315 $.jStorage = {
316 /* Version number */
317 version: "0.1.7.0",
318
319 /**
320 * Sets a key's value.
321 *
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
329 */
330 set: function(key, value, options){
331 _checkKey(key);
332
333 options = options || {};
334
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));
342 }
343 _storage[key] = value;
344
345 if(!isNaN(options.TTL)){
346 this.setTTL(key, options.TTL);
347 // also handles saving
348 }else{
349 _save();
350 }
351 return value;
352 },
353
354 /**
355 * Looks up a key in cache
356 *
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>
360 */
361 get: function(key, def){
362 _checkKey(key);
363 if(key in _storage){
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);
368 }else{
369 return _storage[key];
370 }
371 }
372 return typeof(def) === 'undefined' ? null : def;
373 },
374
375 /**
376 * Deletes a key from cache.
377 *
378 * @param {String} key - Key to delete.
379 * @returns true if key existed or false if it didn't
380 */
381 deleteKey: function(key){
382 _checkKey(key);
383 if(key in _storage){
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];
390 }
391 _save();
392 return true;
393 }
394 return false;
395 },
396
397 /**
398 * Sets a TTL for a key, or remove it if ttl value is 0 or below
399 *
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
403 */
404 setTTL: function(key, ttl){
405 var curtime = +new Date();
406 _checkKey(key);
407 ttl = Number(ttl) || 0;
408 if(key in _storage){
409
410 if(!_storage.__jstorage_meta){
411 _storage.__jstorage_meta = {};
412 }
413 if(!_storage.__jstorage_meta.TTL){
414 _storage.__jstorage_meta.TTL = {};
415 }
416
417 // Set TTL value for the key
418 if(ttl>0){
419 _storage.__jstorage_meta.TTL[key] = curtime + ttl;
420 }else{
421 delete _storage.__jstorage_meta.TTL[key];
422 }
423
424 _save();
425
426 _handleTTL();
427 return true;
428 }
429 return false;
430 },
431
432 /**
433 * Deletes everything in cache.
434 *
435 * @return true
436 */
437 flush: function(){
438 _storage = {};
439 _save();
440 return true;
441 },
442
443 /**
444 * Returns a read-only copy of _storage
445 *
446 * @returns Object
447 */
448 storageObj: function(){
449 function F() {}
450 F.prototype = _storage;
451 return new F();
452 },
453
454 /**
455 * Returns an index of all used keys as an array
456 * ['key1', 'key2',..'keyN']
457 *
458 * @returns Array
459 */
460 index: function(){
461 var index = [], i;
462 for(i in _storage){
463 if(_storage.hasOwnProperty(i) && i !== "__jstorage_meta"){
464 index.push(i);
465 }
466 }
467 return index;
468 },
469
470 /**
471 * How much space in bytes does the storage take?
472 *
473 * @returns Number
474 */
475 storageSize: function(){
476 return _storage_size;
477 },
478
479 /**
480 * Which backend is currently in use?
481 *
482 * @returns String
483 */
484 currentBackend: function(){
485 return _backend;
486 },
487
488 /**
489 * Test if storage is available
490 *
491 * @returns Boolean
492 */
493 storageAvailable: function(){
494 return !!_backend;
495 },
496
497 /**
498 * Reloads the data from browser storage
499 *
500 * @returns undefined
501 */
502 reInit: function(){
503 var new_storage_elm, data;
504 if(_storage_elm && _storage_elm.addBehavior){
505 new_storage_elm = document.createElement('link');
506
507 _storage_elm.parentNode.replaceChild(new_storage_elm, _storage_elm);
508 _storage_elm = new_storage_elm;
509
510 /* Use a DOM element to act as userData storage */
511 _storage_elm.style.behavior = 'url(#default#userData)';
512
513 /* userData element needs to be inserted into the DOM! */
514 document.getElementsByTagName('head')[0].appendChild(_storage_elm);
515
516 _storage_elm.load("jStorage");
517 data = "{}";
518 try{
519 data = _storage_elm.getAttribute("jStorage");
520 }catch(E5){}
521 _storage_service.jStorage = data;
522 _backend = "userDataBehavior";
523 }
524
525 _load_storage();
526 }
527 };
528
529 // Initialize jStorage
530 _init();
531
532 })(window.$ || window.jQuery);