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; }
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
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:
26 * Ext.create('Ext.data.Store', {
38 * The above reader is configured to consume a JSON string that looks something like this:
41 * "success": true,
42 * "users": [
43 * { "name": "User 1" },
44 * { "name": "User 2" }
49 * # Loading Nested Data
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:
55 * Ext.define("User", {
56 * extend: 'Ext.data.Model',
61 * hasMany: {model: 'Order', name: 'orders'},
73 * Ext.define("Order", {
74 * extend: 'Ext.data.Model',
79 * hasMany : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
83 * Ext.define("OrderItem", {
84 * extend: 'Ext.data.Model',
86 * 'id', 'price', 'quantity', 'order_id', 'product_id'
89 * belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
92 * Ext.define("Product", {
93 * extend: 'Ext.data.Model',
98 * hasMany: 'OrderItem'
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:
105 * "users": [
107 * "id": 123,
108 * "name": "Ed",
109 * "orders": [
111 * "id": 50,
112 * "total": 100,
113 * "order_items": [
115 * "id" : 20,
116 * "price" : 40,
117 * "quantity": 2,
118 * "product" : {
119 * "id": 1000,
120 * "name": "MacBook Pro"
124 * "id" : 21,
125 * "price" : 20,
126 * "quantity": 3,
127 * "product" : {
128 * "id": 1001,
129 * "name": "iPhone"
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:
143 * var store = Ext.create('Ext.data.Store', {
144 * model: "User"
148 * callback: function() {
149 * //the user that was loaded
150 * var user = store.first();
152 * console.log("Orders for " + user.get('name') + ":")
154 * //iterate over the Orders for each User
155 * user.orders().each(function(order) {
156 * console.log("Order ID: " + order.getId() + ", which contains items:");
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();
164 * console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
170 * Running the code above results in the following:
173 * Order ID: 50, which contains items:
174 * 2 orders of MacBook Pro
177 Ext.define('Ext.data.reader.Reader', {
178 requires: ['Ext.data.ResultSet'],
179 alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
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
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.
192 totalProperty: 'total',
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.
199 successProperty: 'success',
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.
206 * By default the natural root of the data will be used. The root Json array, the root XML element, or the array.
208 * The data packet value for this property should be an empty array to clear the data or show no data.
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.
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.
222 implicitIncludes: true,
226 <span id='Ext-data-reader-Reader-method-constructor'> /**
227 </span> * Creates new Reader.
228 * @param {Object} config (optional) Config object.
230 constructor: function(config) {
233 Ext.apply(me, config || {});
235 me.model = Ext.ModelManager.getModel(config.model);
237 me.buildExtractors();
241 <span id='Ext-data-reader-Reader-method-setModel'> /**
242 </span> * Sets a new model for the reader.
244 * @param {Object} model The model to set.
245 * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
247 setModel: function(model, setOnProxy) {
250 me.model = Ext.ModelManager.getModel(model);
251 me.buildExtractors(true);
253 if (setOnProxy && me.proxy) {
254 me.proxy.setModel(me.model, true);
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
264 read: function(response) {
267 if (response && response.responseText) {
268 data = this.getResponseData(response);
272 return this.readRecords(data);
274 return this.nullResultSet;
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
285 readRecords: function(data) {
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.
294 if (me.fieldCount !== me.getFields().length) {
295 me.buildExtractors(true);
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
304 data = me.getData(data);
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),
311 total, value, records, message;
317 if (me.totalProperty) {
318 value = parseInt(me.getTotal(data), 10);
324 if (me.successProperty) {
325 value = me.getSuccess(data);
326 if (value === false || value === 'false') {
331 if (me.messageProperty) {
332 message = me.getMessage(data);
336 records = me.extractData(root);
337 recordCount = records.length;
343 return Ext.create('Ext.data.ResultSet', {
344 total : total || recordCount,
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
357 extractData : function(root) {
363 length = root.length,
364 idProp = me.getIdProperty(),
367 if (!root.length && Ext.isObject(root)) {
372 for (; i < length; i++) {
374 values = me.extractValues(node);
378 record = new Model(values, id, node);
379 records.push(record);
381 if (me.implicitIncludes) {
382 me.readAssociated(record, node);
389 <span id='Ext-data-reader-Reader-method-readAssociated'> /**
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
397 readAssociated: function(record, data) {
398 var associations = record.associations.items,
400 length = associations.length,
401 association, associationData, proxy, reader;
403 for (; i < length; i++) {
404 association = associations[i];
405 associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
407 if (associationData) {
408 reader = association.getReader();
410 proxy = association.associatedModel.proxy;
411 // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
413 reader = proxy.getReader();
415 reader = new this.constructor({
416 model: association.associatedName
420 association.read(record, reader, associationData);
425 <span id='Ext-data-reader-Reader-method-getAssociatedDataRoot'> /**
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
434 getAssociatedDataRoot: function(data, associationName) {
435 return data[associationName];
438 getFields: function() {
439 return this.model.prototype.fields.items;
442 <span id='Ext-data-reader-Reader-method-extractValues'> /**
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
449 extractValues: function(data) {
450 var fields = this.getFields(),
452 length = fields.length,
456 for (; i < length; i++) {
458 value = this.extractorFunctions[i](data);
460 output[field.name] = value;
466 <span id='Ext-data-reader-Reader-method-getData'> /**
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
473 getData: function(data) {
477 <span id='Ext-data-reader-Reader-method-getRoot'> /**
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
485 getRoot: function(data) {
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
495 getResponseData: function(response) {
497 Ext.Error.raise("getResponseData must be implemented in the Ext.data.reader.Reader subclass");
501 <span id='Ext-data-reader-Reader-method-onMetaChange'> /**
503 * Reconfigures the meta data tied to this Reader
505 onMetaChange : function(meta) {
506 var fields = meta.fields,
509 Ext.apply(this, meta);
512 newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {
513 extend: 'Ext.data.Model',
516 this.setModel(newModel, true);
518 this.buildExtractors(true);
522 <span id='Ext-data-reader-Reader-method-getIdProperty'> /**
523 </span> * Get the idProperty to use for extracting data
525 * @return {String} The id property
527 getIdProperty: function(){
528 var prop = this.idProperty;
529 if (Ext.isEmpty(prop)) {
530 prop = this.model.prototype.idProperty;
535 <span id='Ext-data-reader-Reader-method-buildExtractors'> /**
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
541 buildExtractors: function(force) {
543 idProp = me.getIdProperty(),
544 totalProp = me.totalProperty,
545 successProp = me.successProperty,
546 messageProp = me.messageProperty,
549 if (force === true) {
550 delete me.extractorFunctions;
553 if (me.extractorFunctions) {
557 //build the extractors for all the meta data
559 me.getTotal = me.createAccessor(totalProp);
563 me.getSuccess = me.createAccessor(successProp);
567 me.getMessage = me.createAccessor(messageProp);
571 accessor = me.createAccessor(idProp);
573 me.getId = function(record) {
574 var id = accessor.call(me, record);
575 return (id === undefined || id === '') ? null : id;
578 me.getId = function() {
582 me.buildFieldExtractors();
585 <span id='Ext-data-reader-Reader-method-buildFieldExtractors'> /**
588 buildFieldExtractors: function() {
589 //now build the extractors for all the fields
591 fields = me.getFields(),
594 extractorFunctions = [],
597 for (; i < ln; i++) {
599 map = (field.mapping !== undefined && field.mapping !== null) ? field.mapping : field.name;
601 extractorFunctions.push(me.createAccessor(map));
605 me.extractorFunctions = extractorFunctions;
609 // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
610 nullResultSet: Ext.create('Ext.data.ResultSet', {