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; }
11 <script type="text/javascript">
12 function highlight() {
13 document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
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
23 * <p>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.</p>
27 * <p><u>Loading Nested Data</u></p>
29 * <p>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:
33 <pre><code>
34 Ext.define("User", {
35 extend: 'Ext.data.Model',
40 hasMany: {model: 'Order', name: 'orders'},
52 Ext.define("Order", {
53 extend: 'Ext.data.Model',
58 hasMany : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
62 Ext.define("OrderItem", {
63 extend: 'Ext.data.Model',
65 'id', 'price', 'quantity', 'order_id', 'product_id'
68 belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
71 Ext.define("Product", {
72 extend: 'Ext.data.Model',
79 </code></pre>
81 * <p>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:</p>
84 <pre><code>
89 "name": "Ed",
93 "total": 100,
94 "order_items": [
97 "price" : 40,
98 "quantity": 2,
99 "product" : {
100 "id": 1000,
101 "name": "MacBook Pro"
106 "price" : 20,
107 "quantity": 3,
108 "product" : {
109 "id": 1001,
110 "name": "iPhone"
119 </code></pre>
121 * <p>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:
125 <pre><code>
126 var store = new Ext.data.Store({
127 model: "User"
131 callback: function() {
132 //the user that was loaded
133 var user = store.first();
135 console.log("Orders for " + user.get('name') + ":")
137 //iterate over the Orders for each User
138 user.orders().each(function(order) {
139 console.log("Order ID: " + order.getId() + ", which contains items:");
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();
147 console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
152 </code></pre>
154 * <p>Running the code above results in the following:</p>
156 <pre><code>
158 Order ID: 50, which contains items:
159 2 orders of MacBook Pro
161 </code></pre>
164 Ext.define('Ext.data.reader.Reader', {
165 requires: ['Ext.data.ResultSet'],
166 alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
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 <tt>The id of the model</tt>.
171 * If an idProperty is explicitly specified it will override that of the one specified
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 <tt>total</tt>.
181 totalProperty: 'total',
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 <tt>success</tt>. See
186 * {@link Ext.data.proxy.Proxy}.{@link Ext.data.proxy.Proxy#exception exception}
187 * for additional information.
189 successProperty: 'success',
191 <span id='Ext-data-reader-Reader-cfg-root'> /**
192 </span> * @cfg {String} root <b>Required</b>. The name of the property
193 * which contains the Array of row objects. Defaults to <tt>undefined</tt>.
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
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.
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.
209 implicitIncludes: true,
213 <span id='Ext-data-reader-Reader-method-constructor'> /**
214 </span> * Creates new Reader.
215 * @param {Object} config (optional) Config object.
217 constructor: function(config) {
220 Ext.apply(me, config || {});
222 me.model = Ext.ModelManager.getModel(config.model);
224 me.buildExtractors();
228 <span id='Ext-data-reader-Reader-method-setModel'> /**
229 </span> * Sets a new model for the reader.
231 * @param {Object} model The model to set.
232 * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
234 setModel: function(model, setOnProxy) {
237 me.model = Ext.ModelManager.getModel(model);
238 me.buildExtractors(true);
240 if (setOnProxy && me.proxy) {
241 me.proxy.setModel(me.model, true);
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
251 read: function(response) {
254 if (response && response.responseText) {
255 data = this.getResponseData(response);
259 return this.readRecords(data);
261 return this.nullResultSet;
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
272 readRecords: function(data) {
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.
281 if (me.fieldCount !== me.getFields().length) {
282 me.buildExtractors(true);
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
292 data = me.getData(data);
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),
299 total, value, records, message;
305 if (me.totalProperty) {
306 value = parseInt(me.getTotal(data), 10);
312 if (me.successProperty) {
313 value = me.getSuccess(data);
314 if (value === false || value === 'false') {
319 if (me.messageProperty) {
320 message = me.getMessage(data);
324 records = me.extractData(root);
325 recordCount = records.length;
331 return Ext.create('Ext.data.ResultSet', {
332 total : total || recordCount,
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
345 extractData : function(root) {
351 length = root.length,
352 idProp = me.getIdProperty(),
355 if (!root.length && Ext.isObject(root)) {
360 for (; i < length; i++) {
362 values = me.extractValues(node);
366 record = new Model(values, id, node);
367 records.push(record);
369 if (me.implicitIncludes) {
370 me.readAssociated(record, node);
377 <span id='Ext-data-reader-Reader-method-readAssociated'> /**
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
385 readAssociated: function(record, data) {
386 var associations = record.associations.items,
388 length = associations.length,
389 association, associationData, proxy, reader;
391 for (; i < length; i++) {
392 association = associations[i];
393 associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
395 if (associationData) {
396 reader = association.getReader();
398 proxy = association.associatedModel.proxy;
399 // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
401 reader = proxy.getReader();
403 reader = new this.constructor({
404 model: association.associatedName
408 association.read(record, reader, associationData);
413 <span id='Ext-data-reader-Reader-method-getAssociatedDataRoot'> /**
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
422 getAssociatedDataRoot: function(data, associationName) {
423 return data[associationName];
426 getFields: function() {
427 return this.model.prototype.fields.items;
430 <span id='Ext-data-reader-Reader-method-extractValues'> /**
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
437 extractValues: function(data) {
438 var fields = this.getFields(),
440 length = fields.length,
444 for (; i < length; i++) {
446 value = this.extractorFunctions[i](data);
448 output[field.name] = value;
454 <span id='Ext-data-reader-Reader-method-getData'> /**
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
461 getData: function(data) {
465 <span id='Ext-data-reader-Reader-method-getRoot'> /**
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
473 getRoot: function(data) {
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
482 getResponseData: function(response) {
484 Ext.Error.raise("getResponseData must be implemented in the Ext.data.reader.Reader subclass");
488 <span id='Ext-data-reader-Reader-method-onMetaChange'> /**
490 * Reconfigures the meta data tied to this Reader
492 onMetaChange : function(meta) {
493 var fields = meta.fields,
496 Ext.apply(this, meta);
499 newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {
500 extend: 'Ext.data.Model',
503 this.setModel(newModel, true);
505 this.buildExtractors(true);
509 <span id='Ext-data-reader-Reader-method-getIdProperty'> /**
510 </span> * Get the idProperty to use for extracting data
512 * @return {String} The id property
514 getIdProperty: function(){
515 var prop = this.idProperty;
516 if (Ext.isEmpty(prop)) {
517 prop = this.model.prototype.idProperty;
522 <span id='Ext-data-reader-Reader-method-buildExtractors'> /**
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)
528 buildExtractors: function(force) {
530 idProp = me.getIdProperty(),
531 totalProp = me.totalProperty,
532 successProp = me.successProperty,
533 messageProp = me.messageProperty,
536 if (force === true) {
537 delete me.extractorFunctions;
540 if (me.extractorFunctions) {
544 //build the extractors for all the meta data
546 me.getTotal = me.createAccessor(totalProp);
550 me.getSuccess = me.createAccessor(successProp);
554 me.getMessage = me.createAccessor(messageProp);
558 accessor = me.createAccessor(idProp);
560 me.getId = function(record) {
561 var id = accessor.call(me, record);
562 return (id === undefined || id === '') ? null : id;
565 me.getId = function() {
569 me.buildFieldExtractors();
572 <span id='Ext-data-reader-Reader-method-buildFieldExtractors'> /**
575 buildFieldExtractors: function() {
576 //now build the extractors for all the fields
578 fields = me.getFields(),
581 extractorFunctions = [],
584 for (; i < ln; i++) {
586 map = (field.mapping !== undefined && field.mapping !== null) ? field.mapping : field.name;
588 extractorFunctions.push(me.createAccessor(map));
592 me.extractorFunctions = extractorFunctions;
596 // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
597 nullResultSet: Ext.create('Ext.data.ResultSet', {