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