Simplify HTMLTitleTextField::validate
[lhc/web/wiklou.git] / resources / lib / 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 * Author: Andris Reinman, andris.reinman@gmail.com
7 * Project homepage: www.jstorage.info
8 *
9 * Licensed under Unlicense:
10 *
11 * This is free and unencumbered software released into the public domain.
12 *
13 * Anyone is free to copy, modify, publish, use, compile, sell, or
14 * distribute this software, either in source code form or as a compiled
15 * binary, for any purpose, commercial or non-commercial, and by any
16 * means.
17 *
18 * In jurisdictions that recognize copyright laws, the author or authors
19 * of this software dedicate any and all copyright interest in the
20 * software to the public domain. We make this dedication for the benefit
21 * of the public at large and to the detriment of our heirs and
22 * successors. We intend this dedication to be an overt act of
23 * relinquishment in perpetuity of all present and future rights to this
24 * software under copyright law.
25 *
26 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
27 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
29 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
30 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
31 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
32 * OTHER DEALINGS IN THE SOFTWARE.
33 *
34 * For more information, please refer to <http://unlicense.org/>
35 */
36
37 /* global ActiveXObject: false */
38 /* jshint browser: true */
39
40 (function() {
41 'use strict';
42
43 var
44 /* jStorage version */
45 JSTORAGE_VERSION = '0.4.12',
46
47 /* detect a dollar object or create one if not found */
48 $ = window.jQuery || window.$ || (window.$ = {}),
49
50 /* check for a JSON handling support */
51 JSON = {
52 parse: window.JSON && (window.JSON.parse || window.JSON.decode) ||
53 String.prototype.evalJSON && function(str) {
54 return String(str).evalJSON();
55 } ||
56 $.parseJSON ||
57 $.evalJSON,
58 stringify: Object.toJSON ||
59 window.JSON && (window.JSON.stringify || window.JSON.encode) ||
60 $.toJSON
61 };
62
63 // Break if no JSON support was found
64 if (typeof JSON.parse !== 'function' || typeof JSON.stringify !== 'function') {
65 throw new Error('No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page');
66 }
67
68 var
69 /* This is the object, that holds the cached values */
70 _storage = {
71 __jstorage_meta: {
72 CRC32: {}
73 }
74 },
75
76 /* Actual browser storage (localStorage or globalStorage['domain']) */
77 _storage_service = {
78 jStorage: '{}'
79 },
80
81 /* DOM element for older IE versions, holds userData behavior */
82 _storage_elm = null,
83
84 /* How much space does the storage take */
85 _storage_size = 0,
86
87 /* which backend is currently used */
88 _backend = false,
89
90 /* onchange observers */
91 _observers = {},
92
93 /* timeout to wait after onchange event */
94 _observer_timeout = false,
95
96 /* last update time */
97 _observer_update = 0,
98
99 /* pubsub observers */
100 _pubsub_observers = {},
101
102 /* skip published items older than current timestamp */
103 _pubsub_last = +new Date(),
104
105 /* Next check for TTL */
106 _ttl_timeout,
107
108 /**
109 * XML encoding and decoding as XML nodes can't be JSON'ized
110 * XML nodes are encoded and decoded if the node is the value to be saved
111 * but not if it's as a property of another object
112 * Eg. -
113 * $.jStorage.set('key', xmlNode); // IS OK
114 * $.jStorage.set('key', {xml: xmlNode}); // NOT OK
115 */
116 _XMLService = {
117
118 /**
119 * Validates a XML node to be XML
120 * based on jQuery.isXML function
121 */
122 isXML: function(elm) {
123 var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
124 return documentElement ? documentElement.nodeName !== 'HTML' : false;
125 },
126
127 /**
128 * Encodes a XML node to string
129 * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
130 */
131 encode: function(xmlNode) {
132 if (!this.isXML(xmlNode)) {
133 return false;
134 }
135 try { // Mozilla, Webkit, Opera
136 return new XMLSerializer().serializeToString(xmlNode);
137 } catch (E1) {
138 try { // IE
139 return xmlNode.xml;
140 } catch (E2) {}
141 }
142 return false;
143 },
144
145 /**
146 * Decodes a XML node from string
147 * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
148 */
149 decode: function(xmlString) {
150 var dom_parser = ('DOMParser' in window && (new DOMParser()).parseFromString) ||
151 (window.ActiveXObject && function(_xmlString) {
152 var xml_doc = new ActiveXObject('Microsoft.XMLDOM');
153 xml_doc.async = 'false';
154 xml_doc.loadXML(_xmlString);
155 return xml_doc;
156 }),
157 resultXML;
158 if (!dom_parser) {
159 return false;
160 }
161 resultXML = dom_parser.call('DOMParser' in window && (new DOMParser()) || window, xmlString, 'text/xml');
162 return this.isXML(resultXML) ? resultXML : false;
163 }
164 };
165
166
167 ////////////////////////// PRIVATE METHODS ////////////////////////
168
169 /**
170 * Initialization function. Detects if the browser supports DOM Storage
171 * or userData behavior and behaves accordingly.
172 */
173 function _init() {
174 /* Check if browser supports localStorage */
175 var localStorageReallyWorks = false;
176 if ('localStorage' in window) {
177 try {
178 window.localStorage.setItem('_tmptest', 'tmpval');
179 localStorageReallyWorks = true;
180 window.localStorage.removeItem('_tmptest');
181 } catch (BogusQuotaExceededErrorOnIos5) {
182 // Thanks be to iOS5 Private Browsing mode which throws
183 // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
184 }
185 }
186
187 if (localStorageReallyWorks) {
188 try {
189 if (window.localStorage) {
190 _storage_service = window.localStorage;
191 _backend = 'localStorage';
192 _observer_update = _storage_service.jStorage_update;
193 }
194 } catch (E3) { /* Firefox fails when touching localStorage and cookies are disabled */ }
195 }
196 /* Check if browser supports globalStorage */
197 else if ('globalStorage' in window) {
198 try {
199 if (window.globalStorage) {
200 if (window.location.hostname == 'localhost') {
201 _storage_service = window.globalStorage['localhost.localdomain'];
202 } else {
203 _storage_service = window.globalStorage[window.location.hostname];
204 }
205 _backend = 'globalStorage';
206 _observer_update = _storage_service.jStorage_update;
207 }
208 } catch (E4) { /* Firefox fails when touching localStorage and cookies are disabled */ }
209 }
210 /* Check if browser supports userData behavior */
211 else {
212 _storage_elm = document.createElement('link');
213 if (_storage_elm.addBehavior) {
214
215 /* Use a DOM element to act as userData storage */
216 _storage_elm.style.behavior = 'url(#default#userData)';
217
218 /* userData element needs to be inserted into the DOM! */
219 document.getElementsByTagName('head')[0].appendChild(_storage_elm);
220
221 try {
222 _storage_elm.load('jStorage');
223 } catch (E) {
224 // try to reset cache
225 _storage_elm.setAttribute('jStorage', '{}');
226 _storage_elm.save('jStorage');
227 _storage_elm.load('jStorage');
228 }
229
230 var data = '{}';
231 try {
232 data = _storage_elm.getAttribute('jStorage');
233 } catch (E5) {}
234
235 try {
236 _observer_update = _storage_elm.getAttribute('jStorage_update');
237 } catch (E6) {}
238
239 _storage_service.jStorage = data;
240 _backend = 'userDataBehavior';
241 } else {
242 _storage_elm = null;
243 return;
244 }
245 }
246
247 // Load data from storage
248 _load_storage();
249
250 // remove dead keys
251 _handleTTL();
252
253 // start listening for changes
254 _setupObserver();
255
256 // initialize publish-subscribe service
257 _handlePubSub();
258
259 // handle cached navigation
260 if ('addEventListener' in window) {
261 window.addEventListener('pageshow', function(event) {
262 if (event.persisted) {
263 _storageObserver();
264 }
265 }, false);
266 }
267 }
268
269 /**
270 * Reload data from storage when needed
271 */
272 function _reloadData() {
273 var data = '{}';
274
275 if (_backend == 'userDataBehavior') {
276 _storage_elm.load('jStorage');
277
278 try {
279 data = _storage_elm.getAttribute('jStorage');
280 } catch (E5) {}
281
282 try {
283 _observer_update = _storage_elm.getAttribute('jStorage_update');
284 } catch (E6) {}
285
286 _storage_service.jStorage = data;
287 }
288
289 _load_storage();
290
291 // remove dead keys
292 _handleTTL();
293
294 _handlePubSub();
295 }
296
297 /**
298 * Sets up a storage change observer
299 */
300 function _setupObserver() {
301 if (_backend == 'localStorage' || _backend == 'globalStorage') {
302 if ('addEventListener' in window) {
303 window.addEventListener('storage', _storageObserver, false);
304 } else {
305 document.attachEvent('onstorage', _storageObserver);
306 }
307 } else if (_backend == 'userDataBehavior') {
308 setInterval(_storageObserver, 1000);
309 }
310 }
311
312 /**
313 * Fired on any kind of data change, needs to check if anything has
314 * really been changed
315 */
316 function _storageObserver() {
317 var updateTime;
318 // cumulate change notifications with timeout
319 clearTimeout(_observer_timeout);
320 _observer_timeout = setTimeout(function() {
321
322 if (_backend == 'localStorage' || _backend == 'globalStorage') {
323 updateTime = _storage_service.jStorage_update;
324 } else if (_backend == 'userDataBehavior') {
325 _storage_elm.load('jStorage');
326 try {
327 updateTime = _storage_elm.getAttribute('jStorage_update');
328 } catch (E5) {}
329 }
330
331 if (updateTime && updateTime != _observer_update) {
332 _observer_update = updateTime;
333 _checkUpdatedKeys();
334 }
335
336 }, 25);
337 }
338
339 /**
340 * Reloads the data and checks if any keys are changed
341 */
342 function _checkUpdatedKeys() {
343 var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)),
344 newCrc32List;
345
346 _reloadData();
347 newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32));
348
349 var key,
350 updated = [],
351 removed = [];
352
353 for (key in oldCrc32List) {
354 if (oldCrc32List.hasOwnProperty(key)) {
355 if (!newCrc32List[key]) {
356 removed.push(key);
357 continue;
358 }
359 if (oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0, 2) == '2.') {
360 updated.push(key);
361 }
362 }
363 }
364
365 for (key in newCrc32List) {
366 if (newCrc32List.hasOwnProperty(key)) {
367 if (!oldCrc32List[key]) {
368 updated.push(key);
369 }
370 }
371 }
372
373 _fireObservers(updated, 'updated');
374 _fireObservers(removed, 'deleted');
375 }
376
377 /**
378 * Fires observers for updated keys
379 *
380 * @param {Array|String} keys Array of key names or a key
381 * @param {String} action What happened with the value (updated, deleted, flushed)
382 */
383 function _fireObservers(keys, action) {
384 keys = [].concat(keys || []);
385
386 var i, j, len, jlen;
387
388 if (action == 'flushed') {
389 keys = [];
390 for (var key in _observers) {
391 if (_observers.hasOwnProperty(key)) {
392 keys.push(key);
393 }
394 }
395 action = 'deleted';
396 }
397 for (i = 0, len = keys.length; i < len; i++) {
398 if (_observers[keys[i]]) {
399 for (j = 0, jlen = _observers[keys[i]].length; j < jlen; j++) {
400 _observers[keys[i]][j](keys[i], action);
401 }
402 }
403 if (_observers['*']) {
404 for (j = 0, jlen = _observers['*'].length; j < jlen; j++) {
405 _observers['*'][j](keys[i], action);
406 }
407 }
408 }
409 }
410
411 /**
412 * Publishes key change to listeners
413 */
414 function _publishChange() {
415 var updateTime = (+new Date()).toString();
416
417 if (_backend == 'localStorage' || _backend == 'globalStorage') {
418 try {
419 _storage_service.jStorage_update = updateTime;
420 } catch (E8) {
421 // safari private mode has been enabled after the jStorage initialization
422 _backend = false;
423 }
424 } else if (_backend == 'userDataBehavior') {
425 _storage_elm.setAttribute('jStorage_update', updateTime);
426 _storage_elm.save('jStorage');
427 }
428
429 _storageObserver();
430 }
431
432 /**
433 * Loads the data from the storage based on the supported mechanism
434 */
435 function _load_storage() {
436 /* if jStorage string is retrieved, then decode it */
437 if (_storage_service.jStorage) {
438 try {
439 _storage = JSON.parse(String(_storage_service.jStorage));
440 } catch (E6) {
441 _storage_service.jStorage = '{}';
442 }
443 } else {
444 _storage_service.jStorage = '{}';
445 }
446 _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;
447
448 if (!_storage.__jstorage_meta) {
449 _storage.__jstorage_meta = {};
450 }
451 if (!_storage.__jstorage_meta.CRC32) {
452 _storage.__jstorage_meta.CRC32 = {};
453 }
454 }
455
456 /**
457 * This functions provides the 'save' mechanism to store the jStorage object
458 */
459 function _save() {
460 _dropOldEvents(); // remove expired events
461 try {
462 _storage_service.jStorage = JSON.stringify(_storage);
463 // If userData is used as the storage engine, additional
464 if (_storage_elm) {
465 _storage_elm.setAttribute('jStorage', _storage_service.jStorage);
466 _storage_elm.save('jStorage');
467 }
468 _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;
469 } catch (E7) { /* probably cache is full, nothing is saved this way*/ }
470 }
471
472 /**
473 * Function checks if a key is set and is string or numberic
474 *
475 * @param {String} key Key name
476 */
477 function _checkKey(key) {
478 if (typeof key != 'string' && typeof key != 'number') {
479 throw new TypeError('Key name must be string or numeric');
480 }
481 if (key == '__jstorage_meta') {
482 throw new TypeError('Reserved key name');
483 }
484 return true;
485 }
486
487 /**
488 * Removes expired keys
489 */
490 function _handleTTL() {
491 var curtime, i, TTL, CRC32, nextExpire = Infinity,
492 changed = false,
493 deleted = [];
494
495 clearTimeout(_ttl_timeout);
496
497 if (!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != 'object') {
498 // nothing to do here
499 return;
500 }
501
502 curtime = +new Date();
503 TTL = _storage.__jstorage_meta.TTL;
504
505 CRC32 = _storage.__jstorage_meta.CRC32;
506 for (i in TTL) {
507 if (TTL.hasOwnProperty(i)) {
508 if (TTL[i] <= curtime) {
509 delete TTL[i];
510 delete CRC32[i];
511 delete _storage[i];
512 changed = true;
513 deleted.push(i);
514 } else if (TTL[i] < nextExpire) {
515 nextExpire = TTL[i];
516 }
517 }
518 }
519
520 // set next check
521 if (nextExpire != Infinity) {
522 _ttl_timeout = setTimeout(_handleTTL, Math.min(nextExpire - curtime, 0x7FFFFFFF));
523 }
524
525 // save changes
526 if (changed) {
527 _save();
528 _publishChange();
529 _fireObservers(deleted, 'deleted');
530 }
531 }
532
533 /**
534 * Checks if there's any events on hold to be fired to listeners
535 */
536 function _handlePubSub() {
537 var i, len;
538 if (!_storage.__jstorage_meta.PubSub) {
539 return;
540 }
541 var pubelm,
542 _pubsubCurrent = _pubsub_last,
543 needFired = [];
544
545 for (i = len = _storage.__jstorage_meta.PubSub.length - 1; i >= 0; i--) {
546 pubelm = _storage.__jstorage_meta.PubSub[i];
547 if (pubelm[0] > _pubsub_last) {
548 _pubsubCurrent = pubelm[0];
549 needFired.unshift(pubelm);
550 }
551 }
552
553 for (i = needFired.length - 1; i >= 0; i--) {
554 _fireSubscribers(needFired[i][1], needFired[i][2]);
555 }
556
557 _pubsub_last = _pubsubCurrent;
558 }
559
560 /**
561 * Fires all subscriber listeners for a pubsub channel
562 *
563 * @param {String} channel Channel name
564 * @param {Mixed} payload Payload data to deliver
565 */
566 function _fireSubscribers(channel, payload) {
567 if (_pubsub_observers[channel]) {
568 for (var i = 0, len = _pubsub_observers[channel].length; i < len; i++) {
569 // send immutable data that can't be modified by listeners
570 try {
571 _pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload)));
572 } catch (E) {}
573 }
574 }
575 }
576
577 /**
578 * Remove old events from the publish stream (at least 2sec old)
579 */
580 function _dropOldEvents() {
581 if (!_storage.__jstorage_meta.PubSub) {
582 return;
583 }
584
585 var retire = +new Date() - 2000;
586
587 for (var i = 0, len = _storage.__jstorage_meta.PubSub.length; i < len; i++) {
588 if (_storage.__jstorage_meta.PubSub[i][0] <= retire) {
589 // deleteCount is needed for IE6
590 _storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i);
591 break;
592 }
593 }
594
595 if (!_storage.__jstorage_meta.PubSub.length) {
596 delete _storage.__jstorage_meta.PubSub;
597 }
598
599 }
600
601 /**
602 * Publish payload to a channel
603 *
604 * @param {String} channel Channel name
605 * @param {Mixed} payload Payload to send to the subscribers
606 */
607 function _publish(channel, payload) {
608 if (!_storage.__jstorage_meta) {
609 _storage.__jstorage_meta = {};
610 }
611 if (!_storage.__jstorage_meta.PubSub) {
612 _storage.__jstorage_meta.PubSub = [];
613 }
614
615 _storage.__jstorage_meta.PubSub.unshift([+new Date(), channel, payload]);
616
617 _save();
618 _publishChange();
619 }
620
621
622 /**
623 * JS Implementation of MurmurHash2
624 *
625 * SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed)
626 *
627 * @author <a href='mailto:gary.court@gmail.com'>Gary Court</a>
628 * @see http://github.com/garycourt/murmurhash-js
629 * @author <a href='mailto:aappleby@gmail.com'>Austin Appleby</a>
630 * @see http://sites.google.com/site/murmurhash/
631 *
632 * @param {string} str ASCII only
633 * @param {number} seed Positive integer only
634 * @return {number} 32-bit positive integer hash
635 */
636
637 function murmurhash2_32_gc(str, seed) {
638 var
639 l = str.length,
640 h = seed ^ l,
641 i = 0,
642 k;
643
644 while (l >= 4) {
645 k =
646 ((str.charCodeAt(i) & 0xff)) |
647 ((str.charCodeAt(++i) & 0xff) << 8) |
648 ((str.charCodeAt(++i) & 0xff) << 16) |
649 ((str.charCodeAt(++i) & 0xff) << 24);
650
651 k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
652 k ^= k >>> 24;
653 k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
654
655 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;
656
657 l -= 4;
658 ++i;
659 }
660
661 switch (l) {
662 case 3:
663 h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
664 /* falls through */
665 case 2:
666 h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
667 /* falls through */
668 case 1:
669 h ^= (str.charCodeAt(i) & 0xff);
670 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
671 }
672
673 h ^= h >>> 13;
674 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
675 h ^= h >>> 15;
676
677 return h >>> 0;
678 }
679
680 ////////////////////////// PUBLIC INTERFACE /////////////////////////
681
682 $.jStorage = {
683 /* Version number */
684 version: JSTORAGE_VERSION,
685
686 /**
687 * Sets a key's value.
688 *
689 * @param {String} key Key to set. If this value is not set or not
690 * a string an exception is raised.
691 * @param {Mixed} value Value to set. This can be any value that is JSON
692 * compatible (Numbers, Strings, Objects etc.).
693 * @param {Object} [options] - possible options to use
694 * @param {Number} [options.TTL] - optional TTL value, in milliseconds
695 * @return {Mixed} the used value
696 */
697 set: function(key, value, options) {
698 _checkKey(key);
699
700 options = options || {};
701
702 // undefined values are deleted automatically
703 if (typeof value == 'undefined') {
704 this.deleteKey(key);
705 return value;
706 }
707
708 if (_XMLService.isXML(value)) {
709 value = {
710 _is_xml: true,
711 xml: _XMLService.encode(value)
712 };
713 } else if (typeof value == 'function') {
714 return undefined; // functions can't be saved!
715 } else if (value && typeof value == 'object') {
716 // clone the object before saving to _storage tree
717 value = JSON.parse(JSON.stringify(value));
718 }
719
720 _storage[key] = value;
721
722 _storage.__jstorage_meta.CRC32[key] = '2.' + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c);
723
724 this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange
725
726 _fireObservers(key, 'updated');
727 return value;
728 },
729
730 /**
731 * Looks up a key in cache
732 *
733 * @param {String} key - Key to look up.
734 * @param {mixed} def - Default value to return, if key didn't exist.
735 * @return {Mixed} the key value, default value or null
736 */
737 get: function(key, def) {
738 _checkKey(key);
739 if (key in _storage) {
740 if (_storage[key] && typeof _storage[key] == 'object' && _storage[key]._is_xml) {
741 return _XMLService.decode(_storage[key].xml);
742 } else {
743 return _storage[key];
744 }
745 }
746 return typeof(def) == 'undefined' ? null : def;
747 },
748
749 /**
750 * Deletes a key from cache.
751 *
752 * @param {String} key - Key to delete.
753 * @return {Boolean} true if key existed or false if it didn't
754 */
755 deleteKey: function(key) {
756 _checkKey(key);
757 if (key in _storage) {
758 delete _storage[key];
759 // remove from TTL list
760 if (typeof _storage.__jstorage_meta.TTL == 'object' &&
761 key in _storage.__jstorage_meta.TTL) {
762 delete _storage.__jstorage_meta.TTL[key];
763 }
764
765 delete _storage.__jstorage_meta.CRC32[key];
766
767 _save();
768 _publishChange();
769 _fireObservers(key, 'deleted');
770 return true;
771 }
772 return false;
773 },
774
775 /**
776 * Sets a TTL for a key, or remove it if ttl value is 0 or below
777 *
778 * @param {String} key - key to set the TTL for
779 * @param {Number} ttl - TTL timeout in milliseconds
780 * @return {Boolean} true if key existed or false if it didn't
781 */
782 setTTL: function(key, ttl) {
783 var curtime = +new Date();
784 _checkKey(key);
785 ttl = Number(ttl) || 0;
786 if (key in _storage) {
787
788 if (!_storage.__jstorage_meta.TTL) {
789 _storage.__jstorage_meta.TTL = {};
790 }
791
792 // Set TTL value for the key
793 if (ttl > 0) {
794 _storage.__jstorage_meta.TTL[key] = curtime + ttl;
795 } else {
796 delete _storage.__jstorage_meta.TTL[key];
797 }
798
799 _save();
800
801 _handleTTL();
802
803 _publishChange();
804 return true;
805 }
806 return false;
807 },
808
809 /**
810 * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set
811 *
812 * @param {String} key Key to check
813 * @return {Number} Remaining TTL in milliseconds
814 */
815 getTTL: function(key) {
816 var curtime = +new Date(),
817 ttl;
818 _checkKey(key);
819 if (key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]) {
820 ttl = _storage.__jstorage_meta.TTL[key] - curtime;
821 return ttl || 0;
822 }
823 return 0;
824 },
825
826 /**
827 * Deletes everything in cache.
828 *
829 * @return {Boolean} Always true
830 */
831 flush: function() {
832 _storage = {
833 __jstorage_meta: {
834 CRC32: {}
835 }
836 };
837 _save();
838 _publishChange();
839 _fireObservers(null, 'flushed');
840 return true;
841 },
842
843 /**
844 * Returns a read-only copy of _storage
845 *
846 * @return {Object} Read-only copy of _storage
847 */
848 storageObj: function() {
849 function F() {}
850 F.prototype = _storage;
851 return new F();
852 },
853
854 /**
855 * Returns an index of all used keys as an array
856 * ['key1', 'key2',..'keyN']
857 *
858 * @return {Array} Used keys
859 */
860 index: function() {
861 var index = [],
862 i;
863 for (i in _storage) {
864 if (_storage.hasOwnProperty(i) && i != '__jstorage_meta') {
865 index.push(i);
866 }
867 }
868 return index;
869 },
870
871 /**
872 * How much space in bytes does the storage take?
873 *
874 * @return {Number} Storage size in chars (not the same as in bytes,
875 * since some chars may take several bytes)
876 */
877 storageSize: function() {
878 return _storage_size;
879 },
880
881 /**
882 * Which backend is currently in use?
883 *
884 * @return {String} Backend name
885 */
886 currentBackend: function() {
887 return _backend;
888 },
889
890 /**
891 * Test if storage is available
892 *
893 * @return {Boolean} True if storage can be used
894 */
895 storageAvailable: function() {
896 return !!_backend;
897 },
898
899 /**
900 * Register change listeners
901 *
902 * @param {String} key Key name
903 * @param {Function} callback Function to run when the key changes
904 */
905 listenKeyChange: function(key, callback) {
906 _checkKey(key);
907 if (!_observers[key]) {
908 _observers[key] = [];
909 }
910 _observers[key].push(callback);
911 },
912
913 /**
914 * Remove change listeners
915 *
916 * @param {String} key Key name to unregister listeners against
917 * @param {Function} [callback] If set, unregister the callback, if not - unregister all
918 */
919 stopListening: function(key, callback) {
920 _checkKey(key);
921
922 if (!_observers[key]) {
923 return;
924 }
925
926 if (!callback) {
927 delete _observers[key];
928 return;
929 }
930
931 for (var i = _observers[key].length - 1; i >= 0; i--) {
932 if (_observers[key][i] == callback) {
933 _observers[key].splice(i, 1);
934 }
935 }
936 },
937
938 /**
939 * Subscribe to a Publish/Subscribe event stream
940 *
941 * @param {String} channel Channel name
942 * @param {Function} callback Function to run when the something is published to the channel
943 */
944 subscribe: function(channel, callback) {
945 channel = (channel || '').toString();
946 if (!channel) {
947 throw new TypeError('Channel not defined');
948 }
949 if (!_pubsub_observers[channel]) {
950 _pubsub_observers[channel] = [];
951 }
952 _pubsub_observers[channel].push(callback);
953 },
954
955 /**
956 * Publish data to an event stream
957 *
958 * @param {String} channel Channel name
959 * @param {Mixed} payload Payload to deliver
960 */
961 publish: function(channel, payload) {
962 channel = (channel || '').toString();
963 if (!channel) {
964 throw new TypeError('Channel not defined');
965 }
966
967 _publish(channel, payload);
968 },
969
970 /**
971 * Reloads the data from browser storage
972 */
973 reInit: function() {
974 _reloadData();
975 },
976
977 /**
978 * Removes reference from global objects and saves it as jStorage
979 *
980 * @param {Boolean} option if needed to save object as simple 'jStorage' in windows context
981 */
982 noConflict: function(saveInGlobal) {
983 delete window.$.jStorage;
984
985 if (saveInGlobal) {
986 window.jStorage = this;
987 }
988
989 return this;
990 }
991 };
992
993 // Initialize jStorage
994 _init();
995
996 })();