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