diff --git a/jquery.store.js b/jquery.store.js index 80e6ab4..dd60515 100644 --- a/jquery.store.js +++ b/jquery.store.js @@ -31,404 +31,403 @@ (function($,undefined){ -/********************************************************************************** - * $.store base and convinience accessor - **********************************************************************************/ - -$.store = function( driver, serializers ) -{ - var that = this; - - if( typeof driver == 'string' ) - { - if( $.store.drivers[ driver ] ) - this.driver = $.store.drivers[ driver ]; - else - throw new Error( 'Unknown driver '+ driver ); - } - else if( typeof driver == 'object' ) - { - var invalidAPI = !$.isFunction( driver.init ) - || !$.isFunction( driver.get ) - || !$.isFunction( driver.set ) - || !$.isFunction( driver.del ) - || !$.isFunction( driver.flush ); - - if( invalidAPI ) - throw new Error( 'The specified driver does not fulfill the API requirements' ); - - this.driver = driver; - } - else - { - // detect and initialize storage driver - $.each( $.store.drivers, function() - { - // skip unavailable drivers - if( !$.isFunction( this.available ) || !this.available() ) - return true; // continue; - - that.driver = this; - if( that.driver.init() === false ) - { - that.driver = null; - return true; // continue; - } - - return false; // break; - }); - } - - // use default serializers if not told otherwise - if( !serializers ) - serializers = $.store.serializers; - - // intialize serializers - this.serializers = {}; - $.each( serializers, function( key, serializer ) - { - // skip invalid processors - if( !$.isFunction( this.init ) ) - return true; // continue; - - that.serializers[ key ] = this; - that.serializers[ key ].init( that.encoders, that.decoders ); - }); -}; + /********************************************************************************** + * $.store base and convinience accessor + **********************************************************************************/ + $.store = function( driver, serializers ) + { + var that = this; -/********************************************************************************** - * $.store API - **********************************************************************************/ - -$.extend( $.store.prototype, { - get: function( key ) - { - var value = this.driver.get( key ); - return this.driver.encodes ? value : this.unserialize( value ); - }, - set: function( key, value ) - { - this.driver.set( key, this.driver.encodes ? value : this.serialize( value ) ); - }, - del: function( key ) - { - this.driver.del( key ); - }, - flush: function() - { - this.driver.flush(); - }, - driver : undefined, - encoders : [], - decoders : [], - serialize: function( value ) - { - var that = this; - - $.each( this.encoders, function() - { - var serializer = that.serializers[ this + "" ]; - if( !serializer || !serializer.encode ) - return true; // continue; - try - { - value = serializer.encode( value ); - } - catch( e ){} - }); - - return value; - }, - unserialize: function( value ) - { - var that = this; - if( !value ) - return value; - - $.each( this.decoders, function() - { - var serializer = that.serializers[ this + "" ]; - if( !serializer || !serializer.decode ) - return true; // continue; - - value = serializer.decode( value ); - }); - - return value; - } -}); + if( typeof driver == 'string' ) + { + if( $.store.drivers[ driver ] ) + this.driver = $.store.drivers[ driver ]; + else + throw new Error( 'Unknown driver '+ driver ); + } + else if( typeof driver == 'object' ) + { + var invalidAPI = !$.isFunction( driver.init ) + || !$.isFunction( driver.get ) + || !$.isFunction( driver.set ) + || !$.isFunction( driver.del ) + || !$.isFunction( driver.flush ); + if( invalidAPI ) + throw new Error( 'The specified driver does not fulfill the API requirements' ); -/********************************************************************************** - * $.store drivers - **********************************************************************************/ - -$.store.drivers = { - // Firefox 3.5, Safari 4.0, Chrome 5, Opera 10.5, IE8 - 'localStorage': { - // see https://developer.mozilla.org/en/dom/storage#localStorage - ident: "$.store.drivers.localStorage", - scope: 'browser', - available: function() - { - try - { - // Firefox won't allow localStorage if cookies are disabled - if (!!window.localStorage) { - // Safari's "Private" mode throws a QUOTA_EXCEEDED_ERR on setItem - window.localStorage.setItem("jQuery Store Availability test", true); - window.localStorage.removeItem("jQuery Store Availability test"); - return true; - }; - return false; - } - catch(e) - { - return false; - } - }, - init: $.noop, - get: function( key ) - { - return window.localStorage.getItem( key ); - }, - set: function( key, value ) - { - window.localStorage.setItem( key, value ); - }, - del: function( key ) - { - window.localStorage.removeItem( key ); - }, - flush: function() - { - window.localStorage.clear(); - } - }, - - // IE6, IE7 - 'userData': { - // see http://msdn.microsoft.com/en-us/library/ms531424.aspx - ident: "$.store.drivers.userData", - element: null, - nodeName: 'userdatadriver', - scope: 'browser', - initialized: false, - available: function() - { - try - { - return !!( document.documentElement && document.documentElement.addBehavior ); - } - catch(e) - { - return false; - } - }, - init: function() - { - // $.store can only utilize one userData store at a time, thus avoid duplicate initialization - if( this.initialized ) - return; - - try - { - // Create a non-existing element and append it to the root element (html) - this.element = document.createElement( this.nodeName ); - document.documentElement.insertBefore( this.element, document.getElementsByTagName('title')[0] ); - // Apply userData behavior - this.element.addBehavior( "#default#userData" ); - this.initialized = true; - } - catch( e ) - { - return false; - } - }, - get: function( key ) - { - this.element.load( this.nodeName ); - return this.element.getAttribute( key ); - }, - set: function( key, value ) - { - this.element.setAttribute( key, value ); - this.element.save( this.nodeName ); - }, - del: function( key ) - { - this.element.removeAttribute( key ); - this.element.save( this.nodeName ); - - }, - flush: function() - { - // flush by expiration - var attrs = this.element.xmlDocument.firstChild.attributes; - for (var i = attrs.length - 1; i >= 0; i--) { - this.element.removeAttribute( attrs[i].nodeName ); - } - this.element.save( this.nodeName ); - } - }, - - // most other browsers - 'windowName': { - ident: "$.store.drivers.windowName", - scope: 'window', - cache: {}, - encodes: true, - available: function() - { - return true; - }, - init: function() - { - this.load(); - }, - save: function() - { - window.name = $.store.serializers.json.encode( this.cache ); - }, - load: function() - { - try - { - this.cache = $.store.serializers.json.decode( window.name + "" ); - if( typeof this.cache != "object" ) - this.cache = {}; - } - catch(e) - { - this.cache = {}; - window.name = "{}"; - } - }, - get: function( key ) - { - return this.cache[ key ]; - }, - set: function( key, value ) - { - this.cache[ key ] = value; - this.save(); - }, - del: function( key ) - { - try - { - delete this.cache[ key ]; - } - catch(e) - { - this.cache[ key ] = undefined; - } - - this.save(); - }, - flush: function() - { - window.name = "{}"; - } - } -}; + this.driver = driver; + } + else + { + // detect and initialize storage driver + $.each( $.store.drivers, function() + { + // skip unavailable drivers + if( !$.isFunction( this.available ) || !this.available() ) + return true; // continue; -/********************************************************************************** - * $.store serializers - **********************************************************************************/ - -$.store.serializers = { - - 'json': { - ident: "$.store.serializers.json", - init: function( encoders, decoders ) - { - encoders.push( "json" ); - decoders.push( "json" ); - }, - encode: JSON.stringify, - decode: JSON.parse - }, - - // TODO: html serializer - // 'html' : {}, - - 'xml': { - ident: "$.store.serializers.xml", - init: function( encoders, decoders ) - { - encoders.unshift( "xml" ); - decoders.push( "xml" ); - }, - - // wouldn't be necessary if jQuery exposed this function - isXML: function( value ) - { - var documentElement = ( value ? value.ownerDocument || value : 0 ).documentElement; - return documentElement ? documentElement.nodeName.toLowerCase() !== "html" : false; - }, - - // encodes a XML node to string (taken from $.jStorage, MIT License) - encode: function( value ) - { - if( !value || value._serialized || !this.isXML( value ) ) - return value; - - var _value = { _serialized: this.ident, value: value }; - - try - { - // Mozilla, Webkit, Opera - _value.value = new XMLSerializer().serializeToString( value ); - return _value; - } - catch(E1) - { - try - { - // Internet Explorer - _value.value = value.xml; - return _value; - } - catch(E2){} - } - - return value; - }, - - // decodes a XML node from string (taken from $.jStorage, MIT License) - decode: function( value ) - { - if( !value || !value._serialized || value._serialized != this.ident ) - return value; - - var dom_parser = ( "DOMParser" in window && (new DOMParser()).parseFromString ); - if( !dom_parser && window.ActiveXObject ) - { - dom_parser = function( _xmlString ) - { - var xml_doc = new ActiveXObject( 'Microsoft.XMLDOM' ); - xml_doc.async = 'false'; - xml_doc.loadXML( _xmlString ); - return xml_doc; - } - } - - if( !dom_parser ) - { - return undefined; - } - - value.value = dom_parser.call( - "DOMParser" in window && (new DOMParser()) || window, - value.value, - 'text/xml' - ); - - return this.isXML( value.value ) ? value.value : undefined; - } - } -}; + that.driver = this; + if( that.driver.init() === false ) + { + that.driver = null; + return true; // continue; + } + + return false; // break; + }); + } + + // use default serializers if not told otherwise + if( !serializers ) + serializers = $.store.serializers; + + // intialize serializers + this.serializers = {}; + this.encoders = []; + this.decoders = []; + $.each( serializers, function( key, serializer ) + { + // skip invalid processors + if( !$.isFunction( this.init ) ) + return true; // continue; + + that.serializers[ key ] = this; + that.serializers[ key ].init( that.encoders, that.decoders ); + }); + }; + + + /********************************************************************************** + * $.store API + **********************************************************************************/ + + $.extend( $.store.prototype, { + get: function( key ) + { + var value = this.driver.get( key ); + return this.driver.encodes ? value : this.unserialize( value ); + }, + set: function( key, value ) + { + this.driver.set( key, this.driver.encodes ? value : this.serialize( value ) ); + }, + del: function( key ) + { + this.driver.del( key ); + }, + flush: function() + { + this.driver.flush(); + }, + serialize: function( value ) + { + var that = this; + + $.each( this.encoders, function() + { + var serializer = that.serializers[ this + "" ]; + if( !serializer || !serializer.encode ) + return true; // continue; + try + { + value = serializer.encode( value ); + } + catch( e ){} + }); + + return value; + }, + unserialize: function( value ) + { + var that = this; + if( !value ) + return value; + + $.each( this.decoders, function() + { + var serializer = that.serializers[ this + "" ]; + if( !serializer || !serializer.decode ) + return true; // continue; + + value = serializer.decode( value ); + }); + + return value; + } + }); + + + /********************************************************************************** + * $.store drivers + **********************************************************************************/ + + $.store.drivers = { + // Firefox 3.5, Safari 4.0, Chrome 5, Opera 10.5, IE8 + 'localStorage': { + // see https://developer.mozilla.org/en/dom/storage#localStorage + ident: "$.store.drivers.localStorage", + scope: 'browser', + available: function() + { + try + { + // Firefox won't allow localStorage if cookies are disabled + if (!!window.localStorage) { + // Safari's "Private" mode throws a QUOTA_EXCEEDED_ERR on setItem + window.localStorage.setItem("jQuery Store Availability test", true); + window.localStorage.removeItem("jQuery Store Availability test"); + return true; + }; + return false; + } + catch(e) + { + return false; + } + }, + init: $.noop, + get: function( key ) + { + return window.localStorage.getItem( key ); + }, + set: function( key, value ) + { + window.localStorage.setItem( key, value ); + }, + del: function( key ) + { + window.localStorage.removeItem( key ); + }, + flush: function() + { + window.localStorage.clear(); + } + }, + + // IE6, IE7 + 'userData': { + // see http://msdn.microsoft.com/en-us/library/ms531424.aspx + ident: "$.store.drivers.userData", + element: null, + nodeName: 'userdatadriver', + scope: 'browser', + initialized: false, + available: function() + { + try + { + return !!( document.documentElement && document.documentElement.addBehavior ); + } + catch(e) + { + return false; + } + }, + init: function() + { + // $.store can only utilize one userData store at a time, thus avoid duplicate initialization + if( this.initialized ) + return; + + try + { + // Create a non-existing element and append it to the root element (html) + this.element = document.createElement( this.nodeName ); + document.documentElement.insertBefore( this.element, document.getElementsByTagName('title')[0] ); + // Apply userData behavior + this.element.addBehavior( "#default#userData" ); + this.initialized = true; + } + catch( e ) + { + return false; + } + }, + get: function( key ) + { + this.element.load( this.nodeName ); + return this.element.getAttribute( key ); + }, + set: function( key, value ) + { + this.element.setAttribute( key, value ); + this.element.save( this.nodeName ); + }, + del: function( key ) + { + this.element.removeAttribute( key ); + this.element.save( this.nodeName ); + + }, + flush: function() + { + // flush by expiration + var attrs = this.element.xmlDocument.firstChild.attributes; + for (var i = attrs.length - 1; i >= 0; i--) { + this.element.removeAttribute( attrs[i].nodeName ); + } + this.element.save( this.nodeName ); + } + }, + + // most other browsers + 'windowName': { + ident: "$.store.drivers.windowName", + scope: 'window', + cache: {}, + encodes: true, + available: function() + { + return true; + }, + init: function() + { + this.load(); + }, + save: function() + { + window.name = $.store.serializers.json.encode( this.cache ); + }, + load: function() + { + try + { + this.cache = $.store.serializers.json.decode( window.name + "" ); + if( typeof this.cache != "object" ) + this.cache = {}; + } + catch(e) + { + this.cache = {}; + window.name = "{}"; + } + }, + get: function( key ) + { + return this.cache[ key ]; + }, + set: function( key, value ) + { + this.cache[ key ] = value; + this.save(); + }, + del: function( key ) + { + try + { + delete this.cache[ key ]; + } + catch(e) + { + this.cache[ key ] = undefined; + } + + this.save(); + }, + flush: function() + { + window.name = "{}"; + } + } + }; + + /********************************************************************************** + * $.store serializers + **********************************************************************************/ + + $.store.serializers = { + + 'json': { + ident: "$.store.serializers.json", + init: function( encoders, decoders ) + { + encoders.push( "json" ); + decoders.push( "json" ); + }, + encode: JSON.stringify, + decode: JSON.parse + }, + + // TODO: html serializer + // 'html' : {}, + + 'xml': { + ident: "$.store.serializers.xml", + init: function( encoders, decoders ) + { + encoders.unshift( "xml" ); + decoders.push( "xml" ); + }, + + // wouldn't be necessary if jQuery exposed this function + isXML: function( value ) + { + var documentElement = ( value ? value.ownerDocument || value : 0 ).documentElement; + return documentElement ? documentElement.nodeName.toLowerCase() !== "html" : false; + }, + + // encodes a XML node to string (taken from $.jStorage, MIT License) + encode: function( value ) + { + if( !value || value._serialized || !this.isXML( value ) ) + return value; + + var _value = { _serialized: this.ident, value: value }; + + try + { + // Mozilla, Webkit, Opera + _value.value = new XMLSerializer().serializeToString( value ); + return _value; + } + catch(E1) + { + try + { + // Internet Explorer + _value.value = value.xml; + return _value; + } + catch(E2){} + } + + return value; + }, + + // decodes a XML node from string (taken from $.jStorage, MIT License) + decode: function( value ) + { + if( !value || !value._serialized || value._serialized != this.ident ) + return value; + + var dom_parser = ( "DOMParser" in window && (new DOMParser()).parseFromString ); + if( !dom_parser && window.ActiveXObject ) + { + dom_parser = function( _xmlString ) + { + var xml_doc = new ActiveXObject( 'Microsoft.XMLDOM' ); + xml_doc.async = 'false'; + xml_doc.loadXML( _xmlString ); + return xml_doc; + } + } + + if( !dom_parser ) + { + return undefined; + } + + value.value = dom_parser.call( + "DOMParser" in window && (new DOMParser()) || window, + value.value, + 'text/xml' + ); + + return this.isXML( value.value ) ? value.value : undefined; + } + } + }; })(jQuery);