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