PageRenderTime 35ms CodeModel.GetById 19ms app.highlight 13ms RepoModel.GetById 0ms app.codeStats 1ms

/ext-4.0.7/src/data/proxy/WebStorage.js

https://bitbucket.org/srogerf/javascript
JavaScript | 416 lines | 232 code | 69 blank | 115 comment | 52 complexity | b165a3a224b3ea162bafc3414f79f3b6 MD5 | raw file
  1/*
  2
  3This file is part of Ext JS 4
  4
  5Copyright (c) 2011 Sencha Inc
  6
  7Contact:  http://www.sencha.com/contact
  8
  9GNU General Public License Usage
 10This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
 11
 12If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
 13
 14*/
 15/**
 16 * @author Ed Spencer
 17 *
 18 * WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage LocalStorage} and {@link
 19 * Ext.data.proxy.SessionStorage SessionStorage} proxies. It uses the new HTML5 key/value client-side storage objects to
 20 * save {@link Ext.data.Model model instances} for offline use.
 21 * @private
 22 */
 23Ext.define('Ext.data.proxy.WebStorage', {
 24    extend: 'Ext.data.proxy.Client',
 25    alternateClassName: 'Ext.data.WebStorageProxy',
 26
 27    /**
 28     * @cfg {String} id
 29     * The unique ID used as the key in which all record data are stored in the local storage object.
 30     */
 31    id: undefined,
 32
 33    /**
 34     * Creates the proxy, throws an error if local storage is not supported in the current browser.
 35     * @param {Object} config (optional) Config object.
 36     */
 37    constructor: function(config) {
 38        this.callParent(arguments);
 39
 40        /**
 41         * @property {Object} cache
 42         * Cached map of records already retrieved by this Proxy. Ensures that the same instance is always retrieved.
 43         */
 44        this.cache = {};
 45
 46        //<debug>
 47        if (this.getStorageObject() === undefined) {
 48            Ext.Error.raise("Local Storage is not supported in this browser, please use another type of data proxy");
 49        }
 50        //</debug>
 51
 52        //if an id is not given, try to use the store's id instead
 53        this.id = this.id || (this.store ? this.store.storeId : undefined);
 54
 55        //<debug>
 56        if (this.id === undefined) {
 57            Ext.Error.raise("No unique id was provided to the local storage proxy. See Ext.data.proxy.LocalStorage documentation for details");
 58        }
 59        //</debug>
 60
 61        this.initialize();
 62    },
 63
 64    //inherit docs
 65    create: function(operation, callback, scope) {
 66        var records = operation.records,
 67            length  = records.length,
 68            ids     = this.getIds(),
 69            id, record, i;
 70
 71        operation.setStarted();
 72
 73        for (i = 0; i < length; i++) {
 74            record = records[i];
 75
 76            if (record.phantom) {
 77                record.phantom = false;
 78                id = this.getNextId();
 79            } else {
 80                id = record.getId();
 81            }
 82
 83            this.setRecord(record, id);
 84            ids.push(id);
 85        }
 86
 87        this.setIds(ids);
 88
 89        operation.setCompleted();
 90        operation.setSuccessful();
 91
 92        if (typeof callback == 'function') {
 93            callback.call(scope || this, operation);
 94        }
 95    },
 96
 97    //inherit docs
 98    read: function(operation, callback, scope) {
 99        //TODO: respect sorters, filters, start and limit options on the Operation
100
101        var records = [],
102            ids     = this.getIds(),
103            length  = ids.length,
104            i, recordData, record;
105
106        //read a single record
107        if (operation.id) {
108            record = this.getRecord(operation.id);
109
110            if (record) {
111                records.push(record);
112                operation.setSuccessful();
113            }
114        } else {
115            for (i = 0; i < length; i++) {
116                records.push(this.getRecord(ids[i]));
117            }
118            operation.setSuccessful();
119        }
120
121        operation.setCompleted();
122
123        operation.resultSet = Ext.create('Ext.data.ResultSet', {
124            records: records,
125            total  : records.length,
126            loaded : true
127        });
128
129        if (typeof callback == 'function') {
130            callback.call(scope || this, operation);
131        }
132    },
133
134    //inherit docs
135    update: function(operation, callback, scope) {
136        var records = operation.records,
137            length  = records.length,
138            ids     = this.getIds(),
139            record, id, i;
140
141        operation.setStarted();
142
143        for (i = 0; i < length; i++) {
144            record = records[i];
145            this.setRecord(record);
146
147            //we need to update the set of ids here because it's possible that a non-phantom record was added
148            //to this proxy - in which case the record's id would never have been added via the normal 'create' call
149            id = record.getId();
150            if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {
151                ids.push(id);
152            }
153        }
154        this.setIds(ids);
155
156        operation.setCompleted();
157        operation.setSuccessful();
158
159        if (typeof callback == 'function') {
160            callback.call(scope || this, operation);
161        }
162    },
163
164    //inherit
165    destroy: function(operation, callback, scope) {
166        var records = operation.records,
167            length  = records.length,
168            ids     = this.getIds(),
169
170            //newIds is a copy of ids, from which we remove the destroyed records
171            newIds  = [].concat(ids),
172            i;
173
174        for (i = 0; i < length; i++) {
175            Ext.Array.remove(newIds, records[i].getId());
176            this.removeRecord(records[i], false);
177        }
178
179        this.setIds(newIds);
180
181        operation.setCompleted();
182        operation.setSuccessful();
183
184        if (typeof callback == 'function') {
185            callback.call(scope || this, operation);
186        }
187    },
188
189    /**
190     * @private
191     * Fetches a model instance from the Proxy by ID. Runs each field's decode function (if present) to decode the data.
192     * @param {String} id The record's unique ID
193     * @return {Ext.data.Model} The model instance
194     */
195    getRecord: function(id) {
196        if (this.cache[id] === undefined) {
197            var rawData = Ext.decode(this.getStorageObject().getItem(this.getRecordKey(id))),
198                data    = {},
199                Model   = this.model,
200                fields  = Model.prototype.fields.items,
201                length  = fields.length,
202                i, field, name, record;
203
204            for (i = 0; i < length; i++) {
205                field = fields[i];
206                name  = field.name;
207
208                if (typeof field.decode == 'function') {
209                    data[name] = field.decode(rawData[name]);
210                } else {
211                    data[name] = rawData[name];
212                }
213            }
214
215            record = new Model(data, id);
216            record.phantom = false;
217
218            this.cache[id] = record;
219        }
220
221        return this.cache[id];
222    },
223
224    /**
225     * Saves the given record in the Proxy. Runs each field's encode function (if present) to encode the data.
226     * @param {Ext.data.Model} record The model instance
227     * @param {String} [id] The id to save the record under (defaults to the value of the record's getId() function)
228     */
229    setRecord: function(record, id) {
230        if (id) {
231            record.setId(id);
232        } else {
233            id = record.getId();
234        }
235
236        var me = this,
237            rawData = record.data,
238            data    = {},
239            model   = me.model,
240            fields  = model.prototype.fields.items,
241            length  = fields.length,
242            i = 0,
243            field, name, obj, key;
244
245        for (; i < length; i++) {
246            field = fields[i];
247            name  = field.name;
248
249            if (typeof field.encode == 'function') {
250                data[name] = field.encode(rawData[name], record);
251            } else {
252                data[name] = rawData[name];
253            }
254        }
255
256        obj = me.getStorageObject();
257        key = me.getRecordKey(id);
258
259        //keep the cache up to date
260        me.cache[id] = record;
261
262        //iPad bug requires that we remove the item before setting it
263        obj.removeItem(key);
264        obj.setItem(key, Ext.encode(data));
265    },
266
267    /**
268     * @private
269     * Physically removes a given record from the local storage. Used internally by {@link #destroy}, which you should
270     * use instead because it updates the list of currently-stored record ids
271     * @param {String/Number/Ext.data.Model} id The id of the record to remove, or an Ext.data.Model instance
272     */
273    removeRecord: function(id, updateIds) {
274        var me = this,
275            ids;
276
277        if (id.isModel) {
278            id = id.getId();
279        }
280
281        if (updateIds !== false) {
282            ids = me.getIds();
283            Ext.Array.remove(ids, id);
284            me.setIds(ids);
285        }
286
287        me.getStorageObject().removeItem(me.getRecordKey(id));
288    },
289
290    /**
291     * @private
292     * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when
293     * storing data in the local storage object and should prevent naming collisions.
294     * @param {String/Number/Ext.data.Model} id The record id, or a Model instance
295     * @return {String} The unique key for this record
296     */
297    getRecordKey: function(id) {
298        if (id.isModel) {
299            id = id.getId();
300        }
301
302        return Ext.String.format("{0}-{1}", this.id, id);
303    },
304
305    /**
306     * @private
307     * Returns the unique key used to store the current record counter for this proxy. This is used internally when
308     * realizing models (creating them when they used to be phantoms), in order to give each model instance a unique id.
309     * @return {String} The counter key
310     */
311    getRecordCounterKey: function() {
312        return Ext.String.format("{0}-counter", this.id);
313    },
314
315    /**
316     * @private
317     * Returns the array of record IDs stored in this Proxy
318     * @return {Number[]} The record IDs. Each is cast as a Number
319     */
320    getIds: function() {
321        var ids    = (this.getStorageObject().getItem(this.id) || "").split(","),
322            length = ids.length,
323            i;
324
325        if (length == 1 && ids[0] === "") {
326            ids = [];
327        } else {
328            for (i = 0; i < length; i++) {
329                ids[i] = parseInt(ids[i], 10);
330            }
331        }
332
333        return ids;
334    },
335
336    /**
337     * @private
338     * Saves the array of ids representing the set of all records in the Proxy
339     * @param {Number[]} ids The ids to set
340     */
341    setIds: function(ids) {
342        var obj = this.getStorageObject(),
343            str = ids.join(",");
344
345        obj.removeItem(this.id);
346
347        if (!Ext.isEmpty(str)) {
348            obj.setItem(this.id, str);
349        }
350    },
351
352    /**
353     * @private
354     * Returns the next numerical ID that can be used when realizing a model instance (see getRecordCounterKey).
355     * Increments the counter.
356     * @return {Number} The id
357     */
358    getNextId: function() {
359        var obj  = this.getStorageObject(),
360            key  = this.getRecordCounterKey(),
361            last = obj.getItem(key),
362            ids, id;
363
364        if (last === null) {
365            ids = this.getIds();
366            last = ids[ids.length - 1] || 0;
367        }
368
369        id = parseInt(last, 10) + 1;
370        obj.setItem(key, id);
371
372        return id;
373    },
374
375    /**
376     * @private
377     * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called
378     * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.
379     */
380    initialize: function() {
381        var storageObject = this.getStorageObject();
382        storageObject.setItem(this.id, storageObject.getItem(this.id) || "");
383    },
384
385    /**
386     * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the
387     * storage object.
388     */
389    clear: function() {
390        var obj = this.getStorageObject(),
391            ids = this.getIds(),
392            len = ids.length,
393            i;
394
395        //remove all the records
396        for (i = 0; i < len; i++) {
397            this.removeRecord(ids[i]);
398        }
399
400        //remove the supporting objects
401        obj.removeItem(this.getRecordCounterKey());
402        obj.removeItem(this.id);
403    },
404
405    /**
406     * @private
407     * Abstract function which should return the storage object that data will be saved to. This must be implemented
408     * in each subclass.
409     * @return {Object} The storage object
410     */
411    getStorageObject: function() {
412        //<debug>
413        Ext.Error.raise("The getStorageObject function has not been defined in your Ext.data.proxy.WebStorage subclass");
414        //</debug>
415    }
416});