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