/**
* @class Ext.state.Provider
* <p>Abstract base class for state provider implementations. The provider is responsible
* for setting values and extracting values to/from the underlying storage source. The
* storage source can vary and the details should be implemented in a subclass. For example
* a provider could use a server side database or the browser localstorage where supported.</p>
*
* <p>This class provides methods for encoding and decoding <b>typed</b> variables including
* dates and defines the Provider interface. By default these methods put the value and the
* type information into a delimited string that can be stored. These should be overridden in
* a subclass if you want to change the format of the encoded value and subsequent decoding.</p>
*/
Ext.define('Ext.state.Provider', {
mixins: {
observable: 'Ext.util.Observable'
},
/**
* @cfg {String} prefix A string to prefix to items stored in the underlying state store.
* Defaults to <tt>'ext-'</tt>
*/
prefix: 'ext-',
constructor : function(config){
config = config || {};
var me = this;
Ext.apply(me, config);
/**
* @event statechange
* Fires when a state change occurs.
* @param {Provider} this This state provider
* @param {String} key The state key which was changed
* @param {String} value The encoded value for the state
*/
me.addEvents("statechange");
me.state = {};
me.mixins.observable.constructor.call(me);
},
/**
* Returns the current value for a key
* @param {String} name The key name
* @param {Mixed} defaultValue A default value to return if the key's value is not found
* @return {Mixed} The state data
*/
get : function(name, defaultValue){
return typeof this.state[name] == "undefined" ?
defaultValue : this.state[name];
},
/**
* Clears a value from the state
* @param {String} name The key name
*/
clear : function(name){
var me = this;
delete me.state[name];
me.fireEvent("statechange", me, name, null);
},
/**
* Sets the value for a key
* @param {String} name The key name
* @param {Mixed} value The value to set
*/
set : function(name, value){
var me = this;
me.state[name] = value;
me.fireEvent("statechange", me, name, value);
},
/**
* Decodes a string previously encoded with {@link #encodeValue}.
* @param {String} value The value to decode
* @return {Mixed} The decoded value
*/
decodeValue : function(value){
// a -> Array
// n -> Number
// d -> Date
// b -> Boolean
// s -> String
// o -> Object
// -> Empty (null)
var me = this,
re = /^(a|n|d|b|s|o|e)\:(.*)$/,
matches = re.exec(unescape(value)),
all,
type,
value,
keyValue;
if(!matches || !matches[1]){
return; // non state
}
type = matches[1];
value = matches[2];
switch (type) {
case 'e':
return null;
case 'n':
return parseFloat(value);
case 'd':
return new Date(Date.parse(value));
case 'b':
return (value == '1');
case 'a':
all = [];
if(value != ''){
Ext.each(value.split('^'), function(val){
all.push(me.decodeValue(val));
}, me);
}
return all;
case 'o':
all = {};
if(value != ''){
Ext.each(value.split('^'), function(val){
keyValue = val.split('=');
all[keyValue[0]] = me.decodeValue(keyValue[1]);
}, me);
}
return all;
default:
return value;
}
},
/**
* Encodes a value including type information. Decode with {@link #decodeValue}.
* @param {Mixed} value The value to encode
* @return {String} The encoded value
*/
encodeValue : function(value){
var flat = '',
i = 0,
enc,
len,
key;
if (value == null) {
return 'e:1';
} else if(typeof value == 'number') {
enc = 'n:' + value;
} else if(typeof value == 'boolean') {
enc = 'b:' + (value ? '1' : '0');
} else if(Ext.isDate(value)) {
enc = 'd:' + value.toGMTString();
} else if(Ext.isArray(value)) {
for (len = value.length; i < len; i++) {
flat += this.encodeValue(value[i]);
if (i != len - 1) {
flat += '^';
}
}
enc = 'a:' + flat;
} else if (typeof value == 'object') {
for (key in value) {
if (typeof value[key] != 'function' && value[key] !== undefined) {
flat += key + '=' + this.encodeValue(value[key]) + '^';
}
}
enc = 'o:' + flat.substring(0, flat.length-1);
} else {
enc = 's:' + value;
}
return escape(enc);
}
});