/* * ----------------------------- JSTORAGE ------------------------------------- * Simple local storage wrapper to save data on the browser side, supporting * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+ * * Copyright (c) 2010 Andris Reinman, andris.reinman@gmail.com * Project homepage: www.jstorage.info * * Taken from Github with slight modifications by Hoo man * https://raw.github.com/andris9/jStorage/master/jstorage.js * * Licensed under MIT-style license: * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** * $.jStorage * * USAGE: * * jStorage requires Prototype, MooTools or jQuery! If jQuery is used, then * jQuery-JSON (http://code.google.com/p/jquery-json/) is also needed. * (jQuery-JSON needs to be loaded BEFORE jStorage!) * * Methods: * * -set(key, value[, options]) * $.jStorage.set(key, value) -> saves a value * * -get(key[, default]) * value = $.jStorage.get(key [, default]) -> * retrieves value if key exists, or default if it doesn't * * -deleteKey(key) * $.jStorage.deleteKey(key) -> removes a key from the storage * * -flush() * $.jStorage.flush() -> clears the cache * * -storageObj() * $.jStorage.storageObj() -> returns a read-ony copy of the actual storage * * -storageSize() * $.jStorage.storageSize() -> returns the size of the storage in bytes * * -index() * $.jStorage.index() -> returns the used keys as an array * * -storageAvailable() * $.jStorage.storageAvailable() -> returns true if storage is available * * -reInit() * $.jStorage.reInit() -> reloads the data from browser storage * * can be any JSON-able value, including objects and arrays. * **/ (function($){ if(!$ || !($.toJSON || Object.toJSON || window.JSON)){ throw new Error("jQuery, MooTools or Prototype needs to be loaded before jStorage!"); } var /* This is the object, that holds the cached values */ _storage = {}, /* Actual browser storage (localStorage or globalStorage['domain']) */ _storage_service = {jStorage:"{}"}, /* DOM element for older IE versions, holds userData behavior */ _storage_elm = null, /* How much space does the storage take */ _storage_size = 0, /* function to encode objects to JSON strings */ json_encode = $.toJSON || Object.toJSON || (window.JSON && (JSON.encode || JSON.stringify)), /* function to decode objects from JSON strings */ json_decode = $.evalJSON || (window.JSON && (JSON.decode || JSON.parse)) || function(str){ return String(str).evalJSON(); }, /* which backend is currently used */ _backend = false, /* Next check for TTL */ _ttl_timeout, /** * XML encoding and decoding as XML nodes can't be JSON'ized * XML nodes are encoded and decoded if the node is the value to be saved * but not if it's as a property of another object * Eg. - * $.jStorage.set("key", xmlNode); // IS OK * $.jStorage.set("key", {xml: xmlNode}); // NOT OK */ _XMLService = { /** * Validates a XML node to be XML * based on jQuery.isXML function */ isXML: function(elm){ var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement; return documentElement ? documentElement.nodeName !== "HTML" : false; }, /** * Encodes a XML node to string * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/ */ encode: function(xmlNode) { if(!this.isXML(xmlNode)){ return false; } try{ // Mozilla, Webkit, Opera return new XMLSerializer().serializeToString(xmlNode); }catch(E1) { try { // IE return xmlNode.xml; }catch(E2){} } return false; }, /** * Decodes a XML node from string * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/ */ decode: function(xmlString){ var dom_parser = ("DOMParser" in window && (new DOMParser()).parseFromString) || (window.ActiveXObject && function(_xmlString) { var xml_doc = new ActiveXObject('Microsoft.XMLDOM'); xml_doc.async = 'false'; xml_doc.loadXML(_xmlString); return xml_doc; }), resultXML; if(!dom_parser){ return false; } resultXML = dom_parser.call("DOMParser" in window && (new DOMParser()) || window, xmlString, 'text/xml'); return this.isXML(resultXML)?resultXML:false; } }; ////////////////////////// PRIVATE METHODS //////////////////////// /** * Initialization function. Detects if the browser supports DOM Storage * or userData behavior and behaves accordingly. * @returns undefined */ function _init(){ /* Check if browser supports localStorage */ var localStorageReallyWorks = false; if("localStorage" in window){ try { window.localStorage.setItem('_tmptest', 'tmpval'); localStorageReallyWorks = true; window.localStorage.removeItem('_tmptest'); } catch(BogusQuotaExceededErrorOnIos5) { // Thanks be to iOS5 Private Browsing mode which throws // QUOTA_EXCEEDED_ERRROR DOM Exception 22. } } if(localStorageReallyWorks){ try { if(window.localStorage) { _storage_service = window.localStorage; _backend = "localStorage"; } } catch(E3) {/* Firefox fails when touching localStorage and cookies are disabled */} } /* Check if browser supports globalStorage */ else if("globalStorage" in window){ try { if(window.globalStorage) { _storage_service = window.globalStorage[window.location.hostname]; _backend = "globalStorage"; } } catch(E4) {/* Firefox fails when touching localStorage and cookies are disabled */} } /* Check if browser supports userData behavior */ else { _storage_elm = document.createElement('link'); if(_storage_elm.addBehavior){ /* Use a DOM element to act as userData storage */ _storage_elm.style.behavior = 'url(#default#userData)'; /* userData element needs to be inserted into the DOM! */ document.getElementsByTagName('head')[0].appendChild(_storage_elm); _storage_elm.load("jStorage"); var data = "{}"; try{ data = _storage_elm.getAttribute("jStorage"); }catch(E5){} _storage_service.jStorage = data; _backend = "userDataBehavior"; }else{ _storage_elm = null; return; } } _load_storage(); // remove dead keys _handleTTL(); } /** * Loads the data from the storage based on the supported mechanism * @returns undefined */ function _load_storage(){ /* if jStorage string is retrieved, then decode it */ if(_storage_service.jStorage){ try{ _storage = json_decode(String(_storage_service.jStorage)); }catch(E6){_storage_service.jStorage = "{}";} }else{ _storage_service.jStorage = "{}"; } _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0; } /** * This functions provides the "save" mechanism to store the jStorage object * @returns undefined */ function _save(){ try{ _storage_service.jStorage = json_encode(_storage); // If userData is used as the storage engine, additional if(_storage_elm) { _storage_elm.setAttribute("jStorage",_storage_service.jStorage); _storage_elm.save("jStorage"); } _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0; }catch(E7){/* probably cache is full, nothing is saved this way*/} } /** * Function checks if a key is set and is string or numberic */ function _checkKey(key){ if(!key || (typeof key !== "string" && typeof key !== "number")){ throw new TypeError('Key name must be string or numeric'); } if(key === "__jstorage_meta"){ throw new TypeError('Reserved key name'); } return true; } /** * Removes expired keys */ function _handleTTL(){ var curtime, i, TTL, nextExpire = Infinity, changed = false; clearTimeout(_ttl_timeout); if(!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL !== "object"){ // nothing to do here return; } curtime = +new Date(); TTL = _storage.__jstorage_meta.TTL; for(i in TTL){ if(TTL.hasOwnProperty(i)){ if(TTL[i] <= curtime){ delete TTL[i]; delete _storage[i]; changed = true; }else if(TTL[i] < nextExpire){ nextExpire = TTL[i]; } } } // set next check if(nextExpire != Infinity){ _ttl_timeout = setTimeout(_handleTTL, nextExpire - curtime); } // save changes if(changed){ _save(); } } ////////////////////////// PUBLIC INTERFACE ///////////////////////// $.jStorage = { /* Version number */ version: "0.1.7.0", /** * Sets a key's value. * * @param {String} key - Key to set. If this value is not set or not * a string an exception is raised. * @param {Mixed} value - Value to set. This can be any value that is JSON * compatible (Numbers, Strings, Objects etc.). * @param {Object} [options] - possible options to use * @param {Number} [options.TTL] - optional TTL value * @returns the used value */ set: function(key, value, options){ _checkKey(key); options = options || {}; if(_XMLService.isXML(value)){ value = {_is_xml:true,xml:_XMLService.encode(value)}; }else if(typeof value === "function"){ value = null; // functions can't be saved! }else if(value && typeof value === "object"){ // clone the object before saving to _storage tree value = json_decode(json_encode(value)); } _storage[key] = value; if(!isNaN(options.TTL)){ this.setTTL(key, options.TTL); // also handles saving }else{ _save(); } return value; }, /** * Looks up a key in cache * * @param {String} key - Key to look up. * @param {mixed} def - Default value to return, if key didn't exist. * @returns the key value, default value or */ get: function(key, def){ _checkKey(key); if(key in _storage){ if(_storage[key] && typeof _storage[key] === "object" && _storage[key]._is_xml && _storage[key]._is_xml){ return _XMLService.decode(_storage[key].xml); }else{ return _storage[key]; } } return typeof(def) === 'undefined' ? null : def; }, /** * Deletes a key from cache. * * @param {String} key - Key to delete. * @returns true if key existed or false if it didn't */ deleteKey: function(key){ _checkKey(key); if(key in _storage){ delete _storage[key]; // remove from TTL list if(_storage.__jstorage_meta && typeof _storage.__jstorage_meta.TTL === "object" && key in _storage.__jstorage_meta.TTL){ delete _storage.__jstorage_meta.TTL[key]; } _save(); return true; } return false; }, /** * Sets a TTL for a key, or remove it if ttl value is 0 or below * * @param {String} key - key to set the TTL for * @param {Number} ttl - TTL timeout in milliseconds * @returns true if key existed or false if it didn't */ setTTL: function(key, ttl){ var curtime = +new Date(); _checkKey(key); ttl = Number(ttl) || 0; if(key in _storage){ if(!_storage.__jstorage_meta){ _storage.__jstorage_meta = {}; } if(!_storage.__jstorage_meta.TTL){ _storage.__jstorage_meta.TTL = {}; } // Set TTL value for the key if(ttl>0){ _storage.__jstorage_meta.TTL[key] = curtime + ttl; }else{ delete _storage.__jstorage_meta.TTL[key]; } _save(); _handleTTL(); return true; } return false; }, /** * Deletes everything in cache. * * @return true */ flush: function(){ _storage = {}; _save(); return true; }, /** * Returns a read-only copy of _storage * * @returns Object */ storageObj: function(){ function F() {} F.prototype = _storage; return new F(); }, /** * Returns an index of all used keys as an array * ['key1', 'key2',..'keyN'] * * @returns Array */ index: function(){ var index = [], i; for(i in _storage){ if(_storage.hasOwnProperty(i) && i !== "__jstorage_meta"){ index.push(i); } } return index; }, /** * How much space in bytes does the storage take? * * @returns Number */ storageSize: function(){ return _storage_size; }, /** * Which backend is currently in use? * * @returns String */ currentBackend: function(){ return _backend; }, /** * Test if storage is available * * @returns Boolean */ storageAvailable: function(){ return !!_backend; }, /** * Reloads the data from browser storage * * @returns undefined */ reInit: function(){ var new_storage_elm, data; if(_storage_elm && _storage_elm.addBehavior){ new_storage_elm = document.createElement('link'); _storage_elm.parentNode.replaceChild(new_storage_elm, _storage_elm); _storage_elm = new_storage_elm; /* Use a DOM element to act as userData storage */ _storage_elm.style.behavior = 'url(#default#userData)'; /* userData element needs to be inserted into the DOM! */ document.getElementsByTagName('head')[0].appendChild(_storage_elm); _storage_elm.load("jStorage"); data = "{}"; try{ data = _storage_elm.getAttribute("jStorage"); }catch(E5){} _storage_service.jStorage = data; _backend = "userDataBehavior"; } _load_storage(); } }; // Initialize jStorage _init(); })(window.$ || window.jQuery);