| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 | <!DOCTYPE html><html><head>  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  <title>The source code</title>  <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />  <script type="text/javascript" src="../resources/prettify/prettify.js"></script>  <style type="text/css">    .highlight { display: block; background-color: #ddd; }  </style>  <script type="text/javascript">    function highlight() {      document.getElementById(location.hash.replace(/#/, "")).className = "highlight";    }  </script></head><body onload="prettyPrint(); highlight();">  <pre class="prettyprint lang-js"><span id='Ext-data-proxy-WebStorage'>/**</span> * @author Ed Spencer * * WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage LocalStorage} and {@link * Ext.data.proxy.SessionStorage SessionStorage} proxies. It uses the new HTML5 key/value client-side storage objects to * save {@link Ext.data.Model model instances} for offline use. * @private */Ext.define('Ext.data.proxy.WebStorage', {    extend: 'Ext.data.proxy.Client',    alternateClassName: 'Ext.data.WebStorageProxy',    requires: [        'Ext.data.SequentialIdGenerator'    ],<span id='Ext-data-proxy-WebStorage-cfg-id'>    /**</span>     * @cfg {String} id     * The unique ID used as the key in which all record data are stored in the local storage object.     */    id: undefined,<span id='Ext-data-proxy-WebStorage-method-constructor'>    /**</span>     * Creates the proxy, throws an error if local storage is not supported in the current browser.     * @param {Object} config (optional) Config object.     */    constructor: function(config) {        this.callParent(arguments);<span id='Ext-data-proxy-WebStorage-property-cache'>        /**</span>         * @property {Object} cache         * Cached map of records already retrieved by this Proxy. Ensures that the same instance is always retrieved.         */        this.cache = {};        //<debug>        if (this.getStorageObject() === undefined) {            Ext.Error.raise("Local Storage is not supported in this browser, please use another type of data proxy");        }        //</debug>        //if an id is not given, try to use the store's id instead        this.id = this.id || (this.store ? this.store.storeId : undefined);        //<debug>        if (this.id === undefined) {            Ext.Error.raise("No unique id was provided to the local storage proxy. See Ext.data.proxy.LocalStorage documentation for details");        }        //</debug>        this.initialize();    },    //inherit docs    create: function(operation, callback, scope) {        var me = this,            records = operation.records,            length = records.length,            ids = me.getIds(),            id, record, i;        operation.setStarted();        if(me.isHierarchical === undefined) {            // if the storage object does not yet contain any data, this is the first point at which we can determine whether or not this proxy deals with hierarchical data.            // it cannot be determined during initialization because the Model is not decorated with NodeInterface until it is used in a TreeStore            me.isHierarchical = !!records[0].isNode;            if(me.isHierarchical) {                me.getStorageObject().setItem(me.getTreeKey(), true);            }        }        for (i = 0; i < length; i++) {            record = records[i];            if (record.phantom) {                record.phantom = false;                id = me.getNextId();            } else {                id = record.getId();            }            me.setRecord(record, id);            record.commit();            ids.push(id);        }        me.setIds(ids);        operation.setCompleted();        operation.setSuccessful();        if (typeof callback == 'function') {            callback.call(scope || me, operation);        }    },    //inherit docs    read: function(operation, callback, scope) {        //TODO: respect sorters, filters, start and limit options on the Operation        var me = this,            records = [],            i = 0,            success = true,            Model = me.model,            ids, length, record, data, id;        operation.setStarted();        if(me.isHierarchical) {            records = me.getTreeData();        } else {            ids = me.getIds();            length = ids.length;            id = operation.id;            //read a single record            if (id) {                data = me.getRecord(id);                if (data !== null) {                    record = new Model(data, id, data);                }                if (record) {                    records.push(record);                } else {                    success = false;                }            } else {                for (; i < length; i++) {                    id = ids[i];                    data = me.getRecord(id);                    records.push(new Model(data, id, data));                }            }        }        if(success) {            operation.setSuccessful();        }        operation.setCompleted();        operation.resultSet = Ext.create('Ext.data.ResultSet', {            records: records,            total  : records.length,            loaded : true        });        if (typeof callback == 'function') {            callback.call(scope || me, operation);        }    },    //inherit docs    update: function(operation, callback, scope) {        var records = operation.records,            length  = records.length,            ids     = this.getIds(),            record, id, i;        operation.setStarted();        for (i = 0; i < length; i++) {            record = records[i];            this.setRecord(record);            record.commit();            //we need to update the set of ids here because it's possible that a non-phantom record was added            //to this proxy - in which case the record's id would never have been added via the normal 'create' call            id = record.getId();            if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {                ids.push(id);            }        }        this.setIds(ids);        operation.setCompleted();        operation.setSuccessful();        if (typeof callback == 'function') {            callback.call(scope || this, operation);        }    },    //inherit    destroy: function(operation, callback, scope) {        var me = this,            records = operation.records,            ids = me.getIds(),            idLength = ids.length,            newIds = [],            removedHash = {},            i = records.length,            id;        operation.setStarted();        for (; i--;) {            Ext.apply(removedHash, me.removeRecord(records[i]));        }        for(i = 0; i < idLength; i++) {            id = ids[i];            if(!removedHash[id]) {                newIds.push(id);            }        }        me.setIds(newIds);        operation.setCompleted();        operation.setSuccessful();        if (typeof callback == 'function') {            callback.call(scope || me, operation);        }    },<span id='Ext-data-proxy-WebStorage-method-getRecord'>    /**</span>     * @private     * Fetches record data from the Proxy by ID.     * @param {String} id The record's unique ID     * @return {Object} The record data     */    getRecord: function(id) {        var me = this,            cache = me.cache,            data = !cache[id] ? Ext.decode(me.getStorageObject().getItem(me.getRecordKey(id))) : cache[id];        if(!data) {            return null;        }        cache[id] = data;        data[me.model.prototype.idProperty] = id;        return data;    },<span id='Ext-data-proxy-WebStorage-method-setRecord'>    /**</span>     * Saves the given record in the Proxy.     * @param {Ext.data.Model} record The model instance     * @param {String} [id] The id to save the record under (defaults to the value of the record's getId() function)     */    setRecord: function(record, id) {        if (id) {            record.setId(id);        } else {            id = record.getId();        }        var me = this,            rawData = record.data,            data    = {},            model   = me.model,            fields  = model.prototype.fields.items,            length  = fields.length,            i = 0,            field, name, obj, key;        for (; i < length; i++) {            field = fields[i];            name  = field.name;            if(field.persist) {                data[name] = rawData[name];            }        }        // no need to store the id in the data, since it is already stored in the record key        delete data[me.model.prototype.idProperty];        // if the record is a tree node and it's a direct child of the root node, do not store the parentId        if(record.isNode && record.get('depth') === 1) {            delete data.parentId;        }        obj = me.getStorageObject();        key = me.getRecordKey(id);        //keep the cache up to date        me.cache[id] = data;        //iPad bug requires that we remove the item before setting it        obj.removeItem(key);        obj.setItem(key, Ext.encode(data));    },<span id='Ext-data-proxy-WebStorage-method-removeRecord'>    /**</span>     * @private     * Physically removes a given record from the local storage and recursively removes children if the record is a tree node. Used internally by {@link #destroy}.     * @param {Ext.data.Model} record The record to remove     * @return {Object} a hash with the ids of the records that were removed as keys and the records that were removed as values     */    removeRecord: function(record) {        var me = this,            id = record.getId(),            records = {},            i, childNodes;        records[id] = record;        me.getStorageObject().removeItem(me.getRecordKey(id));        delete me.cache[id];        if(record.childNodes) {            childNodes = record.childNodes;            for(i = childNodes.length; i--;) {                Ext.apply(records, me.removeRecord(childNodes[i]));            }        }        return records;    },<span id='Ext-data-proxy-WebStorage-method-getRecordKey'>    /**</span>     * @private     * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when     * storing data in the local storage object and should prevent naming collisions.     * @param {String/Number/Ext.data.Model} id The record id, or a Model instance     * @return {String} The unique key for this record     */    getRecordKey: function(id) {        if (id.isModel) {            id = id.getId();        }        return Ext.String.format("{0}-{1}", this.id, id);    },<span id='Ext-data-proxy-WebStorage-method-getRecordCounterKey'>    /**</span>     * @private     * Returns the unique key used to store the current record counter for this proxy. This is used internally when     * realizing models (creating them when they used to be phantoms), in order to give each model instance a unique id.     * @return {String} The counter key     */    getRecordCounterKey: function() {        return Ext.String.format("{0}-counter", this.id);    },<span id='Ext-data-proxy-WebStorage-method-getTreeKey'>    /**</span>     * @private     * Returns the unique key used to store the tree indicator. This is used internally to determine if the stored data is hierarchical     * @return {String} The counter key     */    getTreeKey: function() {        return Ext.String.format("{0}-tree", this.id);    },<span id='Ext-data-proxy-WebStorage-method-getIds'>    /**</span>     * @private     * Returns the array of record IDs stored in this Proxy     * @return {Number[]} The record IDs. Each is cast as a Number     */    getIds: function() {        var me = this,            ids = (me.getStorageObject().getItem(me.id) || "").split(","),            model = me.model,            length = ids.length,            isString = model.prototype.fields.get(model.prototype.idProperty).type.type === 'string',            i;        if (length == 1 && ids[0] === "") {            ids = [];        } else {            for (i = 0; i < length; i++) {                ids[i] = isString ? ids[i] : +ids[i];            }        }        return ids;    },<span id='Ext-data-proxy-WebStorage-method-setIds'>    /**</span>     * @private     * Saves the array of ids representing the set of all records in the Proxy     * @param {Number[]} ids The ids to set     */    setIds: function(ids) {        var obj = this.getStorageObject(),            str = ids.join(",");        obj.removeItem(this.id);        if (!Ext.isEmpty(str)) {            obj.setItem(this.id, str);        }    },<span id='Ext-data-proxy-WebStorage-method-getNextId'>    /**</span>     * @private     * Returns the next numerical ID that can be used when realizing a model instance (see getRecordCounterKey).     * Increments the counter.     * @return {Number} The id     */    getNextId: function() {        var me = this,            obj = me.getStorageObject(),            key = me.getRecordCounterKey(),            model = me.model,            isString = model.prototype.fields.get(model.prototype.idProperty).type.type === 'string',            id;        id = me.idGenerator.generate();        obj.setItem(key, id);        if(!isString) {            id = +id;        }        return id;    },<span id='Ext-data-proxy-WebStorage-method-getTreeData'>    /**</span>     * Gets tree data and transforms it from key value pairs into a hierarchical structure.     * @private     * @return {Ext.data.NodeInterface[]}     */    getTreeData: function() {        var me = this,            ids = me.getIds(),            length = ids.length,            records = [],            recordHash = {},            root = [],            i = 0,            Model = me.model,            idProperty = Model.prototype.idProperty,            rootLength, record, parent, parentId, children, id;        for(; i < length; i++) {            id = ids[i];            // get the record for each id            record = me.getRecord(id);            // push the record into the records array            records.push(record);            // add the record to the record hash so it can be easily retrieved by id later            recordHash[id] = record;            if(!record.parentId) {                // push records that are at the root level (those with no parent id) into the "root" array                root.push(record);            }        }        rootLength = root.length;        // sort the records by parent id for greater efficiency, so that each parent record only has to be found once for all of its children        Ext.Array.sort(records, me.sortByParentId);        // append each record to its parent, starting after the root node(s), since root nodes do not need to be attached to a parent        for(i = rootLength; i < length; i++) {            record = records[i];            parentId = record.parentId;            if(!parent || parent[idProperty] !== parentId) {                // if this record has a different parent id from the previous record, we need to look up the parent by id.                parent = recordHash[parentId];                parent.children = children = [];            }            // push the record onto its parent's children array            children.push(record);        }        for(i = length; i--;) {            record = records[i];            if(!record.children && !record.leaf) {                // set non-leaf nodes with no children to loaded so the proxy won't try to dynamically load their contents when they are expanded                record.loaded = true;            }        }        // Create model instances out of all the "root-level" nodes.        for(i = rootLength; i--;) {            record = root[i];            root[i] = new Model(record, record[idProperty], record);        }        return root;    },<span id='Ext-data-proxy-WebStorage-method-sortByParentId'>    /**</span>     * Sorter function for sorting records by parentId     * @private     * @param {Object} node1     * @param {Object} node2     * @return {Number}     */    sortByParentId: function(node1, node2) {        return (node1.parentId || 0) - (node2.parentId || 0);    },<span id='Ext-data-proxy-WebStorage-method-initialize'>    /**</span>     * @private     * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called     * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.     */    initialize: function() {        var me = this,            storageObject = me.getStorageObject(),            lastId = +storageObject.getItem(me.getRecordCounterKey());        storageObject.setItem(me.id, storageObject.getItem(me.id) || "");        if(storageObject.getItem(me.getTreeKey())) {            me.isHierarchical = true;        }        me.idGenerator = new Ext.data.SequentialIdGenerator({            seed: lastId ? lastId + 1 : 1        });    },<span id='Ext-data-proxy-WebStorage-method-clear'>    /**</span>     * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the     * storage object.     */    clear: function() {        var me = this,            obj = me.getStorageObject(),            ids = me.getIds(),            len = ids.length,            i;        //remove all the records        for (i = 0; i < len; i++) {            obj.removeItem(me.getRecordKey(ids[i]));        }        //remove the supporting objects        obj.removeItem(me.getRecordCounterKey());        obj.removeItem(me.getTreeKey());        obj.removeItem(me.id);        // clear the cache        me.cache = {};    },<span id='Ext-data-proxy-WebStorage-method-getStorageObject'>    /**</span>     * @private     * Abstract function which should return the storage object that data will be saved to. This must be implemented     * in each subclass.     * @return {Object} The storage object     */    getStorageObject: function() {        //<debug>        Ext.Error.raise("The getStorageObject function has not been defined in your Ext.data.proxy.WebStorage subclass");        //</debug>    }});</pre></body></html>
 |