Upgrade to ExtJS 4.0.2 - Released 06/09/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'>/**
19 </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  */
164 Ext.define('Ext.data.reader.Reader', {
165     requires: ['Ext.data.ResultSet'],
166     alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
167     
168 <span id='Ext-data-reader-Reader-cfg-idProperty'>    /**
169 </span>     * @cfg {String} idProperty Name of the property within a row object
170      * that contains a record identifier value.  Defaults to &lt;tt&gt;The id of the model&lt;/tt&gt;.
171      * If an idProperty is explicitly specified it will override that of the one specified
172      * on the model
173      */
174
175 <span id='Ext-data-reader-Reader-cfg-totalProperty'>    /**
176 </span>     * @cfg {String} totalProperty Name of the property from which to
177      * retrieve the total number of records in the dataset. This is only needed
178      * if the whole dataset is not passed in one go, but is being paged from
179      * the remote server.  Defaults to &lt;tt&gt;total&lt;/tt&gt;.
180      */
181     totalProperty: 'total',
182
183 <span id='Ext-data-reader-Reader-cfg-successProperty'>    /**
184 </span>     * @cfg {String} successProperty Name of the property from which to
185      * retrieve the success attribute. Defaults to &lt;tt&gt;success&lt;/tt&gt;.  See
186      * {@link Ext.data.proxy.Proxy}.{@link Ext.data.proxy.Proxy#exception exception}
187      * for additional information.
188      */
189     successProperty: 'success',
190
191 <span id='Ext-data-reader-Reader-cfg-root'>    /**
192 </span>     * @cfg {String} root &lt;b&gt;Required&lt;/b&gt;.  The name of the property
193      * which contains the Array of row objects.  Defaults to &lt;tt&gt;undefined&lt;/tt&gt;.
194      * An exception will be thrown if the root property is undefined. The data
195      * packet value for this property should be an empty array to clear the data
196      * or show no data.
197      */
198     root: '',
199     
200 <span id='Ext-data-reader-Reader-cfg-messageProperty'>    /**
201 </span>     * @cfg {String} messageProperty The name of the property which contains a response message.
202      * This property is optional.
203      */
204     
205 <span id='Ext-data-reader-Reader-cfg-implicitIncludes'>    /**
206 </span>     * @cfg {Boolean} implicitIncludes True to automatically parse models nested within other models in a response
207      * object. See the Ext.data.reader.Reader intro docs for full explanation. Defaults to true.
208      */
209     implicitIncludes: true,
210     
211     isReader: true,
212     
213 <span id='Ext-data-reader-Reader-method-constructor'>    /**
214 </span>     * Creates new Reader.
215      * @param {Object} config (optional) Config object.
216      */
217     constructor: function(config) {
218         var me = this;
219         
220         Ext.apply(me, config || {});
221         me.fieldCount = 0;
222         me.model = Ext.ModelManager.getModel(config.model);
223         if (me.model) {
224             me.buildExtractors();
225         }
226     },
227
228 <span id='Ext-data-reader-Reader-method-setModel'>    /**
229 </span>     * Sets a new model for the reader.
230      * @private
231      * @param {Object} model The model to set.
232      * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
233      */
234     setModel: function(model, setOnProxy) {
235         var me = this;
236         
237         me.model = Ext.ModelManager.getModel(model);
238         me.buildExtractors(true);
239         
240         if (setOnProxy &amp;&amp; me.proxy) {
241             me.proxy.setModel(me.model, true);
242         }
243     },
244
245 <span id='Ext-data-reader-Reader-method-read'>    /**
246 </span>     * Reads the given response object. This method normalizes the different types of response object that may be passed
247      * to it, before handing off the reading of records to the {@link #readRecords} function.
248      * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
249      * @return {Ext.data.ResultSet} The parsed ResultSet object
250      */
251     read: function(response) {
252         var data = response;
253         
254         if (response &amp;&amp; response.responseText) {
255             data = this.getResponseData(response);
256         }
257         
258         if (data) {
259             return this.readRecords(data);
260         } else {
261             return this.nullResultSet;
262         }
263     },
264
265 <span id='Ext-data-reader-Reader-method-readRecords'>    /**
266 </span>     * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call
267      * this function before running its own logic and returning the Ext.data.ResultSet instance. For most
268      * Readers additional processing should not be needed.
269      * @param {Mixed} data The raw data object
270      * @return {Ext.data.ResultSet} A ResultSet object
271      */
272     readRecords: function(data) {
273         var me  = this;
274         
275         /*
276          * We check here whether the number of fields has changed since the last read.
277          * This works around an issue when a Model is used for both a Tree and another
278          * source, because the tree decorates the model with extra fields and it causes
279          * issues because the readers aren't notified.
280          */
281         if (me.fieldCount !== me.getFields().length) {
282             me.buildExtractors(true);
283         }
284         
285 <span id='Ext-data-reader-Reader-property-rawData'>        /**
286 </span>         * The raw data object that was last passed to readRecords. Stored for further processing if needed
287          * @property rawData
288          * @type Mixed
289          */
290         me.rawData = data;
291
292         data = me.getData(data);
293
294         // If we pass an array as the data, we dont use getRoot on the data.
295         // Instead the root equals to the data.
296         var root    = Ext.isArray(data) ? data : me.getRoot(data),
297             success = true,
298             recordCount = 0,
299             total, value, records, message;
300             
301         if (root) {
302             total = root.length;
303         }
304
305         if (me.totalProperty) {
306             value = parseInt(me.getTotal(data), 10);
307             if (!isNaN(value)) {
308                 total = value;
309             }
310         }
311
312         if (me.successProperty) {
313             value = me.getSuccess(data);
314             if (value === false || value === 'false') {
315                 success = false;
316             }
317         }
318         
319         if (me.messageProperty) {
320             message = me.getMessage(data);
321         }
322         
323         if (root) {
324             records = me.extractData(root);
325             recordCount = records.length;
326         } else {
327             recordCount = 0;
328             records = [];
329         }
330
331         return Ext.create('Ext.data.ResultSet', {
332             total  : total || recordCount,
333             count  : recordCount,
334             records: records,
335             success: success,
336             message: message
337         });
338     },
339
340 <span id='Ext-data-reader-Reader-method-extractData'>    /**
341 </span>     * Returns extracted, type-cast rows of data.  Iterates to call #extractValues for each row
342      * @param {Object[]/Object} data-root from server response
343      * @private
344      */
345     extractData : function(root) {
346         var me = this,
347             values  = [],
348             records = [],
349             Model   = me.model,
350             i       = 0,
351             length  = root.length,
352             idProp  = me.getIdProperty(),
353             node, id, record;
354             
355         if (!root.length &amp;&amp; Ext.isObject(root)) {
356             root = [root];
357             length = 1;
358         }
359
360         for (; i &lt; length; i++) {
361             node   = root[i];
362             values = me.extractValues(node);
363             id     = me.getId(node);
364
365             
366             record = new Model(values, id, node);
367             records.push(record);
368                 
369             if (me.implicitIncludes) {
370                 me.readAssociated(record, node);
371             }
372         }
373
374         return records;
375     },
376     
377 <span id='Ext-data-reader-Reader-method-readAssociated'>    /**
378 </span>     * @private
379      * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
380      * on the record provided.
381      * @param {Ext.data.Model} record The record to load associations for
382      * @param {Mixed} data The data object
383      * @return {String} Return value description
384      */
385     readAssociated: function(record, data) {
386         var associations = record.associations.items,
387             i            = 0,
388             length       = associations.length,
389             association, associationData, proxy, reader;
390         
391         for (; i &lt; length; i++) {
392             association     = associations[i];
393             associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
394             
395             if (associationData) {
396                 reader = association.getReader();
397                 if (!reader) {
398                     proxy = association.associatedModel.proxy;
399                     // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
400                     if (proxy) {
401                         reader = proxy.getReader();
402                     } else {
403                         reader = new this.constructor({
404                             model: association.associatedName
405                         });
406                     }
407                 }
408                 association.read(record, reader, associationData);
409             }  
410         }
411     },
412     
413 <span id='Ext-data-reader-Reader-method-getAssociatedDataRoot'>    /**
414 </span>     * @private
415      * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
416      * record, this should return the relevant part of that data for the given association name. This is only really
417      * needed to support the XML Reader, which has to do a query to get the associated data object
418      * @param {Mixed} data The raw data object
419      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
420      * @return {Mixed} The root
421      */
422     getAssociatedDataRoot: function(data, associationName) {
423         return data[associationName];
424     },
425     
426     getFields: function() {
427         return this.model.prototype.fields.items;
428     },
429
430 <span id='Ext-data-reader-Reader-method-extractValues'>    /**
431 </span>     * @private
432      * Given an object representing a single model instance's data, iterates over the model's fields and
433      * builds an object with the value for each field.
434      * @param {Object} data The data object to convert
435      * @return {Object} Data object suitable for use with a model constructor
436      */
437     extractValues: function(data) {
438         var fields = this.getFields(),
439             i      = 0,
440             length = fields.length,
441             output = {},
442             field, value;
443
444         for (; i &lt; length; i++) {
445             field = fields[i];
446             value = this.extractorFunctions[i](data);
447
448             output[field.name] = value;
449         }
450
451         return output;
452     },
453
454 <span id='Ext-data-reader-Reader-method-getData'>    /**
455 </span>     * @private
456      * By default this function just returns what is passed to it. It can be overridden in a subclass
457      * to return something else. See XmlReader for an example.
458      * @param {Object} data The data object
459      * @return {Object} The normalized data object
460      */
461     getData: function(data) {
462         return data;
463     },
464
465 <span id='Ext-data-reader-Reader-method-getRoot'>    /**
466 </span>     * @private
467      * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
468      * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
469      * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
470      * @param {Mixed} data The data object
471      * @return {Mixed} The same data object
472      */
473     getRoot: function(data) {
474         return data;
475     },
476
477 <span id='Ext-data-reader-Reader-method-getResponseData'>    /**
478 </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
479      * @param {Object} response The responce object
480      * @return {Object} The useful data from the response
481      */
482     getResponseData: function(response) {
483         //&lt;debug&gt;
484         Ext.Error.raise(&quot;getResponseData must be implemented in the Ext.data.reader.Reader subclass&quot;);
485         //&lt;/debug&gt;
486     },
487
488 <span id='Ext-data-reader-Reader-method-onMetaChange'>    /**
489 </span>     * @private
490      * Reconfigures the meta data tied to this Reader
491      */
492     onMetaChange : function(meta) {
493         var fields = meta.fields,
494             newModel;
495         
496         Ext.apply(this, meta);
497         
498         if (fields) {
499             newModel = Ext.define(&quot;Ext.data.reader.Json-Model&quot; + Ext.id(), {
500                 extend: 'Ext.data.Model',
501                 fields: fields
502             });
503             this.setModel(newModel, true);
504         } else {
505             this.buildExtractors(true);
506         }
507     },
508     
509 <span id='Ext-data-reader-Reader-method-getIdProperty'>    /**
510 </span>     * Get the idProperty to use for extracting data
511      * @private
512      * @return {String} The id property
513      */
514     getIdProperty: function(){
515         var prop = this.idProperty;
516         if (Ext.isEmpty(prop)) {
517             prop = this.model.prototype.idProperty;
518         }
519         return prop;
520     },
521
522 <span id='Ext-data-reader-Reader-method-buildExtractors'>    /**
523 </span>     * @private
524      * This builds optimized functions for retrieving record data and meta data from an object.
525      * Subclasses may need to implement their own getRoot function.
526      * @param {Boolean} force True to automatically remove existing extractor functions first (defaults to false)
527      */
528     buildExtractors: function(force) {
529         var me          = this,
530             idProp      = me.getIdProperty(),
531             totalProp   = me.totalProperty,
532             successProp = me.successProperty,
533             messageProp = me.messageProperty,
534             accessor;
535             
536         if (force === true) {
537             delete me.extractorFunctions;
538         }
539         
540         if (me.extractorFunctions) {
541             return;
542         }   
543
544         //build the extractors for all the meta data
545         if (totalProp) {
546             me.getTotal = me.createAccessor(totalProp);
547         }
548
549         if (successProp) {
550             me.getSuccess = me.createAccessor(successProp);
551         }
552
553         if (messageProp) {
554             me.getMessage = me.createAccessor(messageProp);
555         }
556
557         if (idProp) {
558             accessor = me.createAccessor(idProp);
559
560             me.getId = function(record) {
561                 var id = accessor.call(me, record);
562                 return (id === undefined || id === '') ? null : id;
563             };
564         } else {
565             me.getId = function() {
566                 return null;
567             };
568         }
569         me.buildFieldExtractors();
570     },
571
572 <span id='Ext-data-reader-Reader-method-buildFieldExtractors'>    /**
573 </span>     * @private
574      */
575     buildFieldExtractors: function() {
576         //now build the extractors for all the fields
577         var me = this,
578             fields = me.getFields(),
579             ln = fields.length,
580             i  = 0,
581             extractorFunctions = [],
582             field, map;
583
584         for (; i &lt; ln; i++) {
585             field = fields[i];
586             map   = (field.mapping !== undefined &amp;&amp; field.mapping !== null) ? field.mapping : field.name;
587
588             extractorFunctions.push(me.createAccessor(map));
589         }
590         me.fieldCount = ln;
591
592         me.extractorFunctions = extractorFunctions;
593     }
594 }, function() {
595     Ext.apply(this, {
596         // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
597         nullResultSet: Ext.create('Ext.data.ResultSet', {
598             total  : 0,
599             count  : 0,
600             records: [],
601             success: true
602         })
603     });
604 });</pre>
605 </body>
606 </html>