Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / docs / source / Reader.html
1 <!DOCTYPE html><html><head><title>Sencha Documentation Project</title><link rel="stylesheet" href="../reset.css" type="text/css"><link rel="stylesheet" href="../prettify.css" type="text/css"><link rel="stylesheet" href="../prettify_sa.css" type="text/css"><script type="text/javascript" src="../prettify.js"></script></head><body onload="prettyPrint()"><pre class="prettyprint"><pre><span id='Ext-data.reader.Reader-method-constructor'><span id='Ext-data.reader.Reader'>/**
2 </span></span> * @author Ed Spencer
3  * @class Ext.data.reader.Reader
4  * @extends Object
5  * 
6  * &lt;p&gt;Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link Ext.data.Store Store}
7  * - usually in response to an AJAX request. This is normally handled transparently by passing some configuration to either the 
8  * {@link Ext.data.Model Model} or the {@link Ext.data.Store Store} in question - see their documentation for further details.&lt;/p&gt;
9  * 
10  * &lt;p&gt;&lt;u&gt;Loading Nested Data&lt;/u&gt;&lt;/p&gt;
11  * 
12  * &lt;p&gt;Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.Association associations}
13  * configured on each Model. Below is an example demonstrating the flexibility of these associations in a fictional CRM system which
14  * manages a User, their Orders, OrderItems and Products. First we'll define the models:
15  * 
16 &lt;pre&gt;&lt;code&gt;
17 Ext.define(&quot;User&quot;, {
18     extend: 'Ext.data.Model',
19     fields: [
20         'id', 'name'
21     ],
22
23     hasMany: {model: 'Order', name: 'orders'},
24
25     proxy: {
26         type: 'rest',
27         url : 'users.json',
28         reader: {
29             type: 'json',
30             root: 'users'
31         }
32     }
33 });
34
35 Ext.define(&quot;Order&quot;, {
36     extend: 'Ext.data.Model',
37     fields: [
38         'id', 'total'
39     ],
40
41     hasMany  : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
42     belongsTo: 'User'
43 });
44
45 Ext.define(&quot;OrderItem&quot;, {
46     extend: 'Ext.data.Model',
47     fields: [
48         'id', 'price', 'quantity', 'order_id', 'product_id'
49     ],
50
51     belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
52 });
53
54 Ext.define(&quot;Product&quot;, {
55     extend: 'Ext.data.Model',
56     fields: [
57         'id', 'name'
58     ],
59
60     hasMany: 'OrderItem'
61 });
62 &lt;/code&gt;&lt;/pre&gt;
63  * 
64  * &lt;p&gt;This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems. Finally,
65  * each OrderItem has a single Product. This allows us to consume data like this:&lt;/p&gt;
66  * 
67 &lt;pre&gt;&lt;code&gt;
68 {
69     &quot;users&quot;: [
70         {
71             &quot;id&quot;: 123,
72             &quot;name&quot;: &quot;Ed&quot;,
73             &quot;orders&quot;: [
74                 {
75                     &quot;id&quot;: 50,
76                     &quot;total&quot;: 100,
77                     &quot;order_items&quot;: [
78                         {
79                             &quot;id&quot;      : 20,
80                             &quot;price&quot;   : 40,
81                             &quot;quantity&quot;: 2,
82                             &quot;product&quot; : {
83                                 &quot;id&quot;: 1000,
84                                 &quot;name&quot;: &quot;MacBook Pro&quot;
85                             }
86                         },
87                         {
88                             &quot;id&quot;      : 21,
89                             &quot;price&quot;   : 20,
90                             &quot;quantity&quot;: 3,
91                             &quot;product&quot; : {
92                                 &quot;id&quot;: 1001,
93                                 &quot;name&quot;: &quot;iPhone&quot;
94                             }
95                         }
96                     ]
97                 }
98             ]
99         }
100     ]
101 }
102 &lt;/code&gt;&lt;/pre&gt;
103  * 
104  * &lt;p&gt;The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the Orders
105  * for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case), and finally
106  * the Product associated with each OrderItem. Now we can read the data and use it as follows:
107  * 
108 &lt;pre&gt;&lt;code&gt;
109 var store = new Ext.data.Store({
110     model: &quot;User&quot;
111 });
112
113 store.load({
114     callback: function() {
115         //the user that was loaded
116         var user = store.first();
117
118         console.log(&quot;Orders for &quot; + user.get('name') + &quot;:&quot;)
119
120         //iterate over the Orders for each User
121         user.orders().each(function(order) {
122             console.log(&quot;Order ID: &quot; + order.getId() + &quot;, which contains items:&quot;);
123
124             //iterate over the OrderItems for each Order
125             order.orderItems().each(function(orderItem) {
126                 //we know that the Product data is already loaded, so we can use the synchronous getProduct
127                 //usually, we would use the asynchronous version (see {@link Ext.data.BelongsToAssociation})
128                 var product = orderItem.getProduct();
129
130                 console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
131             });
132         });
133     }
134 });
135 &lt;/code&gt;&lt;/pre&gt;
136  * 
137  * &lt;p&gt;Running the code above results in the following:&lt;/p&gt;
138  * 
139 &lt;pre&gt;&lt;code&gt;
140 Orders for Ed:
141 Order ID: 50, which contains items:
142 2 orders of MacBook Pro
143 3 orders of iPhone
144 &lt;/code&gt;&lt;/pre&gt;
145  * 
146  * @constructor
147  * @param {Object} config Optional config object
148  */
149 Ext.define('Ext.data.reader.Reader', {
150     requires: ['Ext.data.ResultSet'],
151     alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
152     
153 <span id='Ext-data.reader.Reader-cfg-idProperty'>    /**
154 </span>     * @cfg {String} idProperty Name of the property within a row object
155      * that contains a record identifier value.  Defaults to &lt;tt&gt;The id of the model&lt;/tt&gt;.
156      * If an idProperty is explicitly specified it will override that of the one specified
157      * on the model
158      */
159
160 <span id='Ext-data.reader.Reader-cfg-totalProperty'>    /**
161 </span>     * @cfg {String} totalProperty Name of the property from which to
162      * retrieve the total number of records in the dataset. This is only needed
163      * if the whole dataset is not passed in one go, but is being paged from
164      * the remote server.  Defaults to &lt;tt&gt;total&lt;/tt&gt;.
165      */
166     totalProperty: 'total',
167
168 <span id='Ext-data.reader.Reader-cfg-successProperty'>    /**
169 </span>     * @cfg {String} successProperty Name of the property from which to
170      * retrieve the success attribute. Defaults to &lt;tt&gt;success&lt;/tt&gt;.  See
171      * {@link Ext.data.proxy.Proxy}.{@link Ext.data.proxy.Proxy#exception exception}
172      * for additional information.
173      */
174     successProperty: 'success',
175
176 <span id='Ext-data.reader.Reader-cfg-root'>    /**
177 </span>     * @cfg {String} root &lt;b&gt;Required&lt;/b&gt;.  The name of the property
178      * which contains the Array of row objects.  Defaults to &lt;tt&gt;undefined&lt;/tt&gt;.
179      * An exception will be thrown if the root property is undefined. The data
180      * packet value for this property should be an empty array to clear the data
181      * or show no data.
182      */
183     root: '',
184     
185 <span id='Ext-data.reader.Reader-cfg-messageProperty'>    /**
186 </span>     * @cfg {String} messageProperty The name of the property which contains a response message.
187      * This property is optional.
188      */
189     
190 <span id='Ext-data.reader.Reader-cfg-implicitIncludes'>    /**
191 </span>     * @cfg {Boolean} implicitIncludes True to automatically parse models nested within other models in a response
192      * object. See the Ext.data.reader.Reader intro docs for full explanation. Defaults to true.
193      */
194     implicitIncludes: true,
195     
196     isReader: true,
197     
198     constructor: function(config) {
199         var me = this;
200         
201         Ext.apply(me, config || {});
202         me.fieldCount = 0;
203         me.model = Ext.ModelManager.getModel(config.model);
204         if (me.model) {
205             me.buildExtractors();
206         }
207     },
208
209 <span id='Ext-data.reader.Reader-method-setModel'>    /**
210 </span>     * Sets a new model for the reader.
211      * @private
212      * @param {Object} model The model to set.
213      * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
214      */
215     setModel: function(model, setOnProxy) {
216         var me = this;
217         
218         me.model = Ext.ModelManager.getModel(model);
219         me.buildExtractors(true);
220         
221         if (setOnProxy &amp;&amp; me.proxy) {
222             me.proxy.setModel(me.model, true);
223         }
224     },
225
226 <span id='Ext-data.reader.Reader-method-read'>    /**
227 </span>     * Reads the given response object. This method normalizes the different types of response object that may be passed
228      * to it, before handing off the reading of records to the {@link #readRecords} function.
229      * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
230      * @return {Ext.data.ResultSet} The parsed ResultSet object
231      */
232     read: function(response) {
233         var data = response;
234         
235         if (response &amp;&amp; response.responseText) {
236             data = this.getResponseData(response);
237         }
238         
239         if (data) {
240             return this.readRecords(data);
241         } else {
242             return this.nullResultSet;
243         }
244     },
245
246 <span id='Ext-data.reader.Reader-method-readRecords'>    /**
247 </span>     * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call
248      * this function before running its own logic and returning the Ext.data.ResultSet instance. For most
249      * Readers additional processing should not be needed.
250      * @param {Mixed} data The raw data object
251      * @return {Ext.data.ResultSet} A ResultSet object
252      */
253     readRecords: function(data) {
254         var me  = this;
255         
256         /*
257          * We check here whether the number of fields has changed since the last read.
258          * This works around an issue when a Model is used for both a Tree and another
259          * source, because the tree decorates the model with extra fields and it causes
260          * issues because the readers aren't notified.
261          */
262         if (me.fieldCount !== me.getFields().length) {
263             me.buildExtractors(true);
264         }
265         
266 <span id='Ext-data.reader.Reader-property-rawData'>        /**
267 </span>         * The raw data object that was last passed to readRecords. Stored for further processing if needed
268          * @property rawData
269          * @type Mixed
270          */
271         me.rawData = data;
272
273         data = me.getData(data);
274
275         // If we pass an array as the data, we dont use getRoot on the data.
276         // Instead the root equals to the data.
277         var root    = Ext.isArray(data) ? data : me.getRoot(data),
278             success = true,
279             recordCount = 0,
280             total, value, records, message;
281             
282         if (root) {
283             total = root.length;
284         }
285
286         if (me.totalProperty) {
287             value = parseInt(me.getTotal(data), 10);
288             if (!isNaN(value)) {
289                 total = value;
290             }
291         }
292
293         if (me.successProperty) {
294             value = me.getSuccess(data);
295             if (value === false || value === 'false') {
296                 success = false;
297             }
298         }
299         
300         if (me.messageProperty) {
301             message = me.getMessage(data);
302         }
303         
304         if (root) {
305             records = me.extractData(root);
306             recordCount = records.length;
307         } else {
308             recordCount = 0;
309             records = [];
310         }
311
312         return Ext.create('Ext.data.ResultSet', {
313             total  : total || recordCount,
314             count  : recordCount,
315             records: records,
316             success: success,
317             message: message
318         });
319     },
320
321 <span id='Ext-data.reader.Reader-method-extractData'>    /**
322 </span>     * Returns extracted, type-cast rows of data.  Iterates to call #extractValues for each row
323      * @param {Object[]/Object} data-root from server response
324      * @private
325      */
326     extractData : function(root) {
327         var me = this,
328             values  = [],
329             records = [],
330             Model   = me.model,
331             i       = 0,
332             length  = root.length,
333             idProp  = me.getIdProperty(),
334             node, id, record;
335             
336         if (!root.length &amp;&amp; Ext.isObject(root)) {
337             root = [root];
338             length = 1;
339         }
340
341         for (; i &lt; length; i++) {
342             node   = root[i];
343             values = me.extractValues(node);
344             id     = me.getId(node);
345
346             
347             record = new Model(values, id);
348             record.raw = node;
349             records.push(record);
350                 
351             if (me.implicitIncludes) {
352                 me.readAssociated(record, node);
353             }
354         }
355
356         return records;
357     },
358     
359 <span id='Ext-data.reader.Reader-method-readAssociated'>    /**
360 </span>     * @private
361      * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
362      * on the record provided.
363      * @param {Ext.data.Model} record The record to load associations for
364      * @param {Mixed} data The data object
365      * @return {String} Return value description
366      */
367     readAssociated: function(record, data) {
368         var associations = record.associations.items,
369             i            = 0,
370             length       = associations.length,
371             association, associationData, proxy, reader;
372         
373         for (; i &lt; length; i++) {
374             association     = associations[i];
375             associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
376             
377             if (associationData) {
378                 reader = association.getReader();
379                 if (!reader) {
380                     proxy = association.associatedModel.proxy;
381                     // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
382                     if (proxy) {
383                         reader = proxy.getReader();
384                     } else {
385                         reader = new this.constructor({
386                             model: association.associatedName
387                         });
388                     }
389                 }
390                 association.read(record, reader, associationData);
391             }  
392         }
393     },
394     
395 <span id='Ext-data.reader.Reader-method-getAssociatedDataRoot'>    /**
396 </span>     * @private
397      * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
398      * record, this should return the relevant part of that data for the given association name. This is only really
399      * needed to support the XML Reader, which has to do a query to get the associated data object
400      * @param {Mixed} data The raw data object
401      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
402      * @return {Mixed} The root
403      */
404     getAssociatedDataRoot: function(data, associationName) {
405         return data[associationName];
406     },
407     
408     getFields: function() {
409         return this.model.prototype.fields.items;
410     },
411
412 <span id='Ext-data.reader.Reader-method-extractValues'>    /**
413 </span>     * @private
414      * Given an object representing a single model instance's data, iterates over the model's fields and
415      * builds an object with the value for each field.
416      * @param {Object} data The data object to convert
417      * @return {Object} Data object suitable for use with a model constructor
418      */
419     extractValues: function(data) {
420         var fields = this.getFields(),
421             i      = 0,
422             length = fields.length,
423             output = {},
424             field, value;
425
426         for (; i &lt; length; i++) {
427             field = fields[i];
428             value = this.extractorFunctions[i](data);
429
430             output[field.name] = value;
431         }
432
433         return output;
434     },
435
436 <span id='Ext-data.reader.Reader-method-getData'>    /**
437 </span>     * @private
438      * By default this function just returns what is passed to it. It can be overridden in a subclass
439      * to return something else. See XmlReader for an example.
440      * @param {Object} data The data object
441      * @return {Object} The normalized data object
442      */
443     getData: function(data) {
444         return data;
445     },
446
447 <span id='Ext-data.reader.Reader-method-getRoot'>    /**
448 </span>     * @private
449      * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
450      * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
451      * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
452      * @param {Mixed} data The data object
453      * @return {Mixed} The same data object
454      */
455     getRoot: function(data) {
456         return data;
457     },
458
459 <span id='Ext-data.reader.Reader-method-getResponseData'>    /**
460 </span>     * Takes a raw response object (as passed to this.read) and returns the useful data segment of it. This must be implemented by each subclass
461      * @param {Object} response The responce object
462      * @return {Object} The useful data from the response
463      */
464     getResponseData: function(response) {
465         //&lt;debug&gt;
466         Ext.Error.raise(&quot;getResponseData must be implemented in the Ext.data.reader.Reader subclass&quot;);
467         //&lt;/debug&gt;
468     },
469
470 <span id='Ext-data.reader.Reader-method-onMetaChange'>    /**
471 </span>     * @private
472      * Reconfigures the meta data tied to this Reader
473      */
474     onMetaChange : function(meta) {
475         var fields = meta.fields,
476             newModel;
477         
478         Ext.apply(this, meta);
479         
480         if (fields) {
481             newModel = Ext.define(&quot;Ext.data.reader.Json-Model&quot; + Ext.id(), {
482                 extend: 'Ext.data.Model',
483                 fields: fields
484             });
485             this.setModel(newModel, true);
486         } else {
487             this.buildExtractors(true);
488         }
489     },
490     
491 <span id='Ext-data.reader.Reader-method-getIdProperty'>    /**
492 </span>     * Get the idProperty to use for extracting data
493      * @private
494      * @return {String} The id property
495      */
496     getIdProperty: function(){
497         var prop = this.idProperty;
498         if (Ext.isEmpty(prop)) {
499             prop = this.model.prototype.idProperty;
500         }
501         return prop;
502     },
503
504 <span id='Ext-data.reader.Reader-method-buildExtractors'>    /**
505 </span>     * @private
506      * This builds optimized functions for retrieving record data and meta data from an object.
507      * Subclasses may need to implement their own getRoot function.
508      * @param {Boolean} force True to automatically remove existing extractor functions first (defaults to false)
509      */
510     buildExtractors: function(force) {
511         var me          = this,
512             idProp      = me.getIdProperty(),
513             totalProp   = me.totalProperty,
514             successProp = me.successProperty,
515             messageProp = me.messageProperty,
516             accessor;
517             
518         if (force === true) {
519             delete me.extractorFunctions;
520         }
521         
522         if (me.extractorFunctions) {
523             return;
524         }   
525
526         //build the extractors for all the meta data
527         if (totalProp) {
528             me.getTotal = me.createAccessor(totalProp);
529         }
530
531         if (successProp) {
532             me.getSuccess = me.createAccessor(successProp);
533         }
534
535         if (messageProp) {
536             me.getMessage = me.createAccessor(messageProp);
537         }
538
539         if (idProp) {
540             accessor = me.createAccessor(idProp);
541
542             me.getId = function(record) {
543                 var id = accessor.call(me, record);
544                 return (id === undefined || id === '') ? null : id;
545             };
546         } else {
547             me.getId = function() {
548                 return null;
549             };
550         }
551         me.buildFieldExtractors();
552     },
553
554 <span id='Ext-data.reader.Reader-method-buildFieldExtractors'>    /**
555 </span>     * @private
556      */
557     buildFieldExtractors: function() {
558         //now build the extractors for all the fields
559         var me = this,
560             fields = me.getFields(),
561             ln = fields.length,
562             i  = 0,
563             extractorFunctions = [],
564             field, map;
565
566         for (; i &lt; ln; i++) {
567             field = fields[i];
568             map   = (field.mapping !== undefined &amp;&amp; field.mapping !== null) ? field.mapping : field.name;
569
570             extractorFunctions.push(me.createAccessor(map));
571         }
572         me.fieldCount = ln;
573
574         me.extractorFunctions = extractorFunctions;
575     }
576 }, function() {
577     Ext.apply(this, {
578         // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
579         nullResultSet: Ext.create('Ext.data.ResultSet', {
580             total  : 0,
581             count  : 0,
582             records: [],
583             success: true
584         })
585     });
586 });</pre></pre></body></html>