    /** * This class provides a common API to LocalStorage with backwards compatibility for IE. *  * The primary aspects of this API match the HTML5 standard API except that this class * provides a scoping mechanism to isolate property values by instance. This scope is * determined from the `id` property. Further, this class does not expose the number of * keys in the store as a `length` property as this cannot be maintained reliably without * undue cost. Instead there is a `getKeys` method that returns the cached array of keys * which is lazily populated on first call. *  * For example: *  *      var store = new Ext.util.LocalStorage({ *              id: 'foo' *          }); *  *      store.setItem('bar', 'stuff'); *       *      // Equivalent to: *      window.localStorage.setItem('foo-bar', 'stuff'); *  * In all cases, the `id` property is only used by the underlying storage and should not * be needed in item access calls or appear when enumerating keys. *  * To continue with the previous example: *  *      var keys = store.getKeys(); *      console.log(keys.length);   // logs 1 *      console.log(store.key(0));  // logs "bar" * * ## Sharing Instances *  * The management of the underlying storage can be broken if multiple instances of this * class are created with the same `id` simultaneously. To avoid creating multiple instances * with the same `id`, use the `get` method and it will lazily create and share a single * instance. When you are done with the shared instance, call `release`. *  *      var storage = Ext.util.LocalStorage.get('id'); *       *      ... *       *      storage.release(); // do not call `destroy` as others may be using this object * * **IMPORTANT:** Do not mix direction instantiation and `get` with the same `id`. *  * ## Legacy IE *  * Older IE browsers (specifically IE7 and below) do not support `localStorage` so this * class provides equivalent support using the IE proprietary persistence mechanism: the * [`userData` behavior](http://msdn.microsoft.com/en-us/library/ms531424(VS.85).aspx). In * this mode, the `id` serves as name passed to the `load` and `save` methods and as the * suffix on the DOM element added to the `head`. *  * In this mode, writes to the underlying storage are buffered and delayed for performance * reasons. This can be managed using the `flushDelay` config or by directly calling the * `save` method. * * @since 4.2.2 */Ext.define('Ext.util.LocalStorage', {    /**     * The unique identifier for this store. This config is required to scope this storage     * distinctly from others. Ultimately, this is used to set a prefix on all keys.     * @cfg {String} id      */    id: null,     /**     * This property is set to `true` when the instance's `destroy` method is called.     * @property {Boolean} destroyed      * @readonly     */    destroyed: false,     /**     * Determines if the keys collection is continuously maintained by this object. By     * default the keys array is lazily fetched from the underlying store and when keys     * are removed, the array is discarded. This heuristic tends to be safer than doing     * the linear removal and array rippling to remove keys from the array on each call     * to `removeItem`. If the cost of scanning `localStorage` for keys is high enough     * and if the keys are frequently needed, then this flag can be set to `false` to     * instruct this class to maintain the keys array once it has been determined.     * @cfg {Boolean} [lazyKeys=true]     */    lazyKeys: true,     /**     * The prefix to apply to all `localStorage` keys manages by this instance. This does     * not apply to the legacy IE mechanism but only to the HTML5 `localStorage` keys. If     * not provided, the `id` property initializes this value with `"id-"`.     * @cfg {String} [prefix]     */    prefix: '',     /**     * Specify this as `true` to use `sessionStorage` instead of the default `localStoreage`.     * This option is not supported in legacy IE browsers (IE 6 and 7) and is ignored.     * @cfg {Boolean} [session=false]     */    session: false,     /**     * The array of all key names. This will be `null` if the keys need to be redetermined     * by the `getKeys` method.     * @property {String[]} _keys     * @private     * @readonly     */    _keys: null,     /**     * The Storage instance used to store items. This is based on the `session` config.     * @property _store     * @private     * @readonly     */    _store: null,     /**     * The number of users that have requested this instance using the `get` method and     * have not yet called `release`.     * @property {Number} _users      * @private     * @readonly     */    _users: 0,     statics: {        cache: {},         /**         * Returns a shared instance of the desired local store given its `id`. When you         * are finished with the returned object call the `release` method:         *          *      var store = Ext.util.LocalStorage.get('foo');         *               *      // .. use store         *               *      store.release();         *          * **NOTE:** Do not mix this call with direct instantiation of the same `id`.         * @param {String/Object} id The `id` of the desired instance or a config object         * with an `id` property at a minimum.         * @return {Ext.util.LocalStorage} The desired instance, created if needed.         */        get: function (id) {            var me = this,                cache = me.cache,                config = {                    _users: 1 // allow constructor to recognize us as the caller                 },                instance;             if (Ext.isString(id)) {                config.id = id;            } else {                Ext.apply(config, id);            }             if (!(instance = cache[config.id])) {                instance = new me(config);            } else {                //<debug>                 if (instance === true) {                    Ext.raise('Creating a shared instance of private local store "' +                        me.id + '".');                }                //</debug>                  ++instance._users;            }             return instance;        },         /**         * This will be `true` if some form of local storage is supported or `false` if not.         * @property {Boolean} supported          * @readonly         */        supported: true    },     constructor: function (config) {        var me = this;         Ext.apply(me, config);         //<debug>         if (!me.hasOwnProperty('id')) {            Ext.raise("No id was provided to the local store.");        }        //</debug>          if (me._users) {            // When we are created by get() for shared use, the _users property is set to             // 1... so this means we are being created for shared use: add this instance             // to the cache.             Ext.util.LocalStorage.cache[me.id] = me;        }        //<debug>         else {            // else we are being created directly so check that this id is not also in the             // cache ... that would be "extraordinaly bad".              if (Ext.util.LocalStorage.cache[me.id]) {                Ext.raise('Cannot create duplicate instance of local store "' +                    me.id + '". Use Ext.util.LocalStorage.get() to share instances.');            }             // We put true in the cache to be detectable by get() for diagnostic reasons             // but no "this" to avoid leaking the store:             Ext.util.LocalStorage.cache[me.id] = true;        }        //</debug>          me.init();    },     /**     * Initializes this instance.     * @private     */    init: function () {        var me = this,            id = me.id;         if (!me.prefix && id) {            me.prefix = id + '-';        }        me._store = (me.session ? window.sessionStorage : window.localStorage);    },     /**     * Destroys this instance and for legacy IE, ensures data is flushed to persistent     * storage. This method should not be called directly on instances returned by the     * `get` method. Call `release` instead for such instances.     *      * *NOTE:* For non-legacy IE browsers, there is no harm in failing to call this     * method. In legacy IE, however, failing to call this method can result in memory     * leaks.     */    destroy: function () {        var me = this;         //<debug>         if (me._users) {            Ext.log.warn('LocalStorage(id=' + me.id + ') destroyed while in use');        }        //</debug>          delete Ext.util.LocalStorage.cache[me.id];        me._store = me._keys = null;        me.callParent();    },     /**     * Returns the keys for this storage.     * @return {String[]} The keys for this storage. This array should be considered as     * readonly.     */    getKeys: function () {        var me = this,            store = me._store,            prefix = me.prefix,            keys = me._keys,            n = prefix.length,            i, key;         if (!keys) {            me._keys = keys = [];             for (i = store.length; i--; ) {                key = store.key(i);                if (key.length > n) {                    if (prefix === key.substring(0, n)) {                        keys.push(key.substring(n));                    }                }            }        }         return keys;    },     /**     * Call this method when finished with an instance returned by `get` instead of calling     * `destroy`. When the last shared use of this instance calls `release`, the `destroy`     * method is called automatically.     *      * *NOTE:* Failing to call this method will result in memory leaks.     */    release: function () {        if (! --this._users) {            this.destroy();        }    },     /**     * @method     * @static     * @private     */    save: Ext.emptyFn,     /**     * Removes all of the keys of this storage.     * **NOTE:** This method conforms to the standard HTML5 Storage interface.     */    clear: function () {        var me = this,            store = me._store,            prefix = me.prefix,            keys = me._keys || me.getKeys(),            i;         for (i = keys.length; i--; ) {            store.removeItem(prefix + keys[i]);        }         keys.length = 0;    },     /**     * Returns the specified key given its `index`. These keys have the scoping prefix     * removed so they match what was passed to `setItem`.     * **NOTE:** This method conforms to the standard HTML5 Storage interface.     * @param {Number} index The index of the desired key.     * @return {String} The key.     */    key: function (index) {        var keys = this._keys || this.getKeys();         return (0 <= index && index < keys.length) ? keys[index] : null;    },     /**     * Returns the value associated with the given `key`.     * **NOTE:** This method conforms to the standard HTML5 Storage interface.     * @param {String} key The key.     * @return {String} The value associated with the given `key`.     */    getItem: function (key) {        var k = this.prefix + key;         return this._store.getItem(k);    },     /**     * Removes the value associated with the given `key`.     * **NOTE:** This method conforms to the standard HTML5 Storage interface.     * @param {String} key The key.     */    removeItem: function (key) {        var me = this,            k = me.prefix + key,            store = me._store,            keys = me._keys,            length = store.length;         store.removeItem(k);         if (keys && length !== store.length) {            if (me.lazyKeys) {                me._keys = null;            } else {                Ext.Array.remove(keys, key);            }        }    },     /**     * Sets the value associated with the given `key`.     * **NOTE:** This method conforms to the standard HTML5 Storage interface.     * @param {String} key The key.     * @param {String} value The new associated value for `key`.     */    setItem: function (key, value) {        var me = this,            k = me.prefix + key,            store = me._store,            length = store.length,            keys = me._keys;         store.setItem(k, value);         if (keys && length !== store.length) {            // Good news here - maintaining the keys collection is easy in this case.             keys.push(key);        }    }}, function () {    var LocalStorage = this;     if ('localStorage' in window) {        return;    }    if (!Ext.isIE) {        LocalStorage.supported = false;        //<debug>         LocalStorage.prototype.init = function () {            Ext.raise("Local storage is not supported on this browser");        };        //</debug>         return;    }     // The legacy IE polyfill has to store values as attributes which have strict rules on     // valid names. Rather than handle that per-key, we just JSON enocde an object and use     // just the "xdata" attribute.     //     LocalStorage.override({        /**         * The parsed data object. This is JSON encoded and saved in storage as the `xdata`         * attribute.         * @property {Object} data          * @private         * @readonly         */        data: null,         /**         * The IE `userData` enabled element. This property is used to support legacy IE         * mode.         * @property {HTMLElement} el          * @private         * @readonly         */         /**         * The number of milliseconds to delay writing changes to the underlying store.         * This applies only to legacy IE mode and helps batch multiple writes into one         * flush to storage.         * @cfg {Number} [flushDelay=1]         */        flushDelay: 1,         init: function () {            var me = this,                data = me.data,                el;             me.el = el = document.createElement('div');             el.id = (me.id || (me.id = 'extjs-localstore'));            el.addBehavior('#default#userdata');             // Add to <head> to ensure the div is invisible             Ext.getHead().dom.appendChild(el);             el.load(me.id);            data = el.getAttribute('xdata');             me.data = data = (data ? Ext.decode(data) : {});             me._flushFn = function () {                me._timer = null;                me.save(0);            };        },         destroy: function () {            var me = this,                el = me.el;             if (el) {                // If we have a save pending, flush that now:                 if (me._timer) {                    me.save();                }                 el.parentNode.removeChild(el);                me.data = me.el = null;                 me.callParent();            }        },         getKeys: function () {            var me = this,                keys = me._keys;             if (!keys) {                me._keys = keys = Ext.Object.getKeys(me.data);            }             return keys;        },         /**         * This method ensures the content of the store is saved to the underlying storage.         * This applies only to legacy IE. This is not normally called by user code but can         * be called to ensure storage is saved.         * @param {Number} [delay=0]         */        save: function (delay) {            var me = this;             if (!delay) {                if (me._timer) {                    clearTimeout(me._timer);                    me._timer = null;                }                 me.el.setAttribute('xdata', Ext.encode(me.data));                me.el.save(me.id);            } else if (!me._timer) {                me._timer = Ext.defer(me._flushFn, delay);            }        },         clear: function () {            var me = this;             me.data = {};            me._keys = null;            me.save(me.flushDelay);        },         getItem: function (key) {            var data = this.data;             return (key in data) ? data[key] : null;        },         removeItem: function (key) {            var me = this,                keys = me._keys,                data = me.data;             if (key in data) {                delete data[key];                 if (keys) {                    if (me.lazyKeys) {                        me._keys = null;                    } else {                        Ext.Array.remove(keys, key);                    }                }                 me.save(me.flushDelay);            }        },         setItem: function (key, value) {            var me = this,                data = me.data,                keys = me._keys;             if (keys && !(key in data)) {                keys.push(key);            }             data[key] = value;            me.save(me.flushDelay);        }    });});