3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This 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.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
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.
23 Ext.define('Ext.data.proxy.WebStorage', {
24 extend: 'Ext.data.proxy.Client',
25 alternateClassName: 'Ext.data.WebStorageProxy',
29 * The unique ID used as the key in which all record data are stored in the local storage object.
34 * Creates the proxy, throws an error if local storage is not supported in the current browser.
35 * @param {Object} config (optional) Config object.
37 constructor: function(config) {
38 this.callParent(arguments);
41 * @property {Object} cache
42 * Cached map of records already retrieved by this Proxy. Ensures that the same instance is always retrieved.
47 if (this.getStorageObject() === undefined) {
48 Ext.Error.raise("Local Storage is not supported in this browser, please use another type of data proxy");
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);
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");
65 create: function(operation, callback, scope) {
66 var records = operation.records,
67 length = records.length,
71 operation.setStarted();
73 for (i = 0; i < length; i++) {
77 record.phantom = false;
78 id = this.getNextId();
83 this.setRecord(record, id);
89 operation.setCompleted();
90 operation.setSuccessful();
92 if (typeof callback == 'function') {
93 callback.call(scope || this, operation);
98 read: function(operation, callback, scope) {
99 //TODO: respect sorters, filters, start and limit options on the Operation
104 i, recordData, record;
106 //read a single record
108 record = this.getRecord(operation.id);
111 records.push(record);
112 operation.setSuccessful();
115 for (i = 0; i < length; i++) {
116 records.push(this.getRecord(ids[i]));
118 operation.setSuccessful();
121 operation.setCompleted();
123 operation.resultSet = Ext.create('Ext.data.ResultSet', {
125 total : records.length,
129 if (typeof callback == 'function') {
130 callback.call(scope || this, operation);
135 update: function(operation, callback, scope) {
136 var records = operation.records,
137 length = records.length,
141 operation.setStarted();
143 for (i = 0; i < length; i++) {
145 this.setRecord(record);
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
150 if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {
156 operation.setCompleted();
157 operation.setSuccessful();
159 if (typeof callback == 'function') {
160 callback.call(scope || this, operation);
165 destroy: function(operation, callback, scope) {
166 var records = operation.records,
167 length = records.length,
170 //newIds is a copy of ids, from which we remove the destroyed records
171 newIds = [].concat(ids),
174 for (i = 0; i < length; i++) {
175 Ext.Array.remove(newIds, records[i].getId());
176 this.removeRecord(records[i], false);
181 operation.setCompleted();
182 operation.setSuccessful();
184 if (typeof callback == 'function') {
185 callback.call(scope || this, operation);
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
195 getRecord: function(id) {
196 if (this.cache[id] === undefined) {
197 var rawData = Ext.decode(this.getStorageObject().getItem(this.getRecordKey(id))),
200 fields = Model.prototype.fields.items,
201 length = fields.length,
202 i, field, name, record;
204 for (i = 0; i < length; i++) {
208 if (typeof field.decode == 'function') {
209 data[name] = field.decode(rawData[name]);
211 data[name] = rawData[name];
215 record = new Model(data, id);
216 record.phantom = false;
218 this.cache[id] = record;
221 return this.cache[id];
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)
229 setRecord: function(record, id) {
237 rawData = record.data,
240 fields = model.prototype.fields.items,
241 length = fields.length,
243 field, name, obj, key;
245 for (; i < length; i++) {
249 if (typeof field.encode == 'function') {
250 data[name] = field.encode(rawData[name], record);
252 data[name] = rawData[name];
256 obj = me.getStorageObject();
257 key = me.getRecordKey(id);
259 //keep the cache up to date
260 me.cache[id] = record;
262 //iPad bug requires that we remove the item before setting it
264 obj.setItem(key, Ext.encode(data));
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
273 removeRecord: function(id, updateIds) {
281 if (updateIds !== false) {
283 Ext.Array.remove(ids, id);
287 me.getStorageObject().removeItem(me.getRecordKey(id));
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
297 getRecordKey: function(id) {
302 return Ext.String.format("{0}-{1}", this.id, id);
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
311 getRecordCounterKey: function() {
312 return Ext.String.format("{0}-counter", this.id);
317 * Returns the array of record IDs stored in this Proxy
318 * @return {Number[]} The record IDs. Each is cast as a Number
321 var ids = (this.getStorageObject().getItem(this.id) || "").split(","),
325 if (length == 1 && ids[0] === "") {
328 for (i = 0; i < length; i++) {
329 ids[i] = parseInt(ids[i], 10);
338 * Saves the array of ids representing the set of all records in the Proxy
339 * @param {Number[]} ids The ids to set
341 setIds: function(ids) {
342 var obj = this.getStorageObject(),
345 obj.removeItem(this.id);
347 if (!Ext.isEmpty(str)) {
348 obj.setItem(this.id, str);
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
358 getNextId: function() {
359 var obj = this.getStorageObject(),
360 key = this.getRecordCounterKey(),
361 last = obj.getItem(key),
366 last = ids[ids.length - 1] || 0;
369 id = parseInt(last, 10) + 1;
370 obj.setItem(key, id);
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.
380 initialize: function() {
381 var storageObject = this.getStorageObject();
382 storageObject.setItem(this.id, storageObject.getItem(this.id) || "");
386 * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the
390 var obj = this.getStorageObject(),
395 //remove all the records
396 for (i = 0; i < len; i++) {
397 this.removeRecord(ids[i]);
400 //remove the supporting objects
401 obj.removeItem(this.getRecordCounterKey());
402 obj.removeItem(this.id);
407 * Abstract function which should return the storage object that data will be saved to. This must be implemented
409 * @return {Object} The storage object
411 getStorageObject: function() {
413 Ext.Error.raise("The getStorageObject function has not been defined in your Ext.data.proxy.WebStorage subclass");