3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
18 * Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link
19 * Ext.data.Store Store} - often in response to an AJAX request. In general there is usually no need to create
20 * a Reader instance directly, since a Reader is almost always used together with a {@link Ext.data.proxy.Proxy Proxy},
21 * and is configured using the Proxy's {@link Ext.data.proxy.Proxy#cfg-reader reader} configuration property:
23 * Ext.create('Ext.data.Store', {
35 * The above reader is configured to consume a JSON string that looks something like this:
40 * { "name": "User 1" },
41 * { "name": "User 2" }
46 * # Loading Nested Data
48 * Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.Association
49 * associations} configured on each Model. Below is an example demonstrating the flexibility of these associations in a
50 * fictional CRM system which manages a User, their Orders, OrderItems and Products. First we'll define the models:
52 * Ext.define("User", {
53 * extend: 'Ext.data.Model',
58 * hasMany: {model: 'Order', name: 'orders'},
70 * Ext.define("Order", {
71 * extend: 'Ext.data.Model',
76 * hasMany : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
80 * Ext.define("OrderItem", {
81 * extend: 'Ext.data.Model',
83 * 'id', 'price', 'quantity', 'order_id', 'product_id'
86 * belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
89 * Ext.define("Product", {
90 * extend: 'Ext.data.Model',
95 * hasMany: 'OrderItem'
98 * This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems.
99 * Finally, each OrderItem has a single Product. This allows us to consume data like this:
117 * "name": "MacBook Pro"
136 * The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the
137 * Orders for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case),
138 * and finally the Product associated with each OrderItem. Now we can read the data and use it as follows:
140 * var store = Ext.create('Ext.data.Store', {
145 * callback: function() {
146 * //the user that was loaded
147 * var user = store.first();
149 * console.log("Orders for " + user.get('name') + ":")
151 * //iterate over the Orders for each User
152 * user.orders().each(function(order) {
153 * console.log("Order ID: " + order.getId() + ", which contains items:");
155 * //iterate over the OrderItems for each Order
156 * order.orderItems().each(function(orderItem) {
157 * //we know that the Product data is already loaded, so we can use the synchronous getProduct
158 * //usually, we would use the asynchronous version (see {@link Ext.data.BelongsToAssociation})
159 * var product = orderItem.getProduct();
161 * console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
167 * Running the code above results in the following:
170 * Order ID: 50, which contains items:
171 * 2 orders of MacBook Pro
174 Ext.define('Ext.data.reader.Reader', {
175 requires: ['Ext.data.ResultSet'],
176 alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
179 * @cfg {String} idProperty
180 * Name of the property within a row object that contains a record identifier value. Defaults to The id of the
181 * model. If an idProperty is explicitly specified it will override that of the one specified on the model
185 * @cfg {String} totalProperty
186 * Name of the property from which to retrieve the total number of records in the dataset. This is only needed if
187 * the whole dataset is not passed in one go, but is being paged from the remote server. Defaults to total.
189 totalProperty: 'total',
192 * @cfg {String} successProperty
193 * Name of the property from which to retrieve the success attribute. Defaults to success. See
194 * {@link Ext.data.proxy.Server}.{@link Ext.data.proxy.Server#exception exception} for additional information.
196 successProperty: 'success',
200 * The name of the property which contains the Array of row objects. For JSON reader it's dot-separated list
201 * of property names. For XML reader it's a CSS selector. For array reader it's not applicable.
203 * By default the natural root of the data will be used. The root Json array, the root XML element, or the array.
205 * The data packet value for this property should be an empty array to clear the data or show no data.
210 * @cfg {String} messageProperty
211 * The name of the property which contains a response message. This property is optional.
215 * @cfg {Boolean} implicitIncludes
216 * True to automatically parse models nested within other models in a response object. See the
217 * Ext.data.reader.Reader intro docs for full explanation. Defaults to true.
219 implicitIncludes: true,
224 * Creates new Reader.
225 * @param {Object} config (optional) Config object.
227 constructor: function(config) {
230 Ext.apply(me, config || {});
232 me.model = Ext.ModelManager.getModel(config.model);
234 me.buildExtractors();
239 * Sets a new model for the reader.
241 * @param {Object} model The model to set.
242 * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
244 setModel: function(model, setOnProxy) {
247 me.model = Ext.ModelManager.getModel(model);
248 me.buildExtractors(true);
250 if (setOnProxy && me.proxy) {
251 me.proxy.setModel(me.model, true);
256 * Reads the given response object. This method normalizes the different types of response object that may be passed
257 * to it, before handing off the reading of records to the {@link #readRecords} function.
258 * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
259 * @return {Ext.data.ResultSet} The parsed ResultSet object
261 read: function(response) {
264 if (response && response.responseText) {
265 data = this.getResponseData(response);
269 return this.readRecords(data);
271 return this.nullResultSet;
276 * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call this function
277 * before running its own logic and returning the Ext.data.ResultSet instance. For most Readers additional
278 * processing should not be needed.
279 * @param {Object} data The raw data object
280 * @return {Ext.data.ResultSet} A ResultSet object
282 readRecords: function(data) {
286 * We check here whether the number of fields has changed since the last read.
287 * This works around an issue when a Model is used for both a Tree and another
288 * source, because the tree decorates the model with extra fields and it causes
289 * issues because the readers aren't notified.
291 if (me.fieldCount !== me.getFields().length) {
292 me.buildExtractors(true);
296 * @property {Object} rawData
297 * The raw data object that was last passed to readRecords. Stored for further processing if needed
301 data = me.getData(data);
303 // If we pass an array as the data, we dont use getRoot on the data.
304 // Instead the root equals to the data.
305 var root = Ext.isArray(data) ? data : me.getRoot(data),
308 total, value, records, message;
314 if (me.totalProperty) {
315 value = parseInt(me.getTotal(data), 10);
321 if (me.successProperty) {
322 value = me.getSuccess(data);
323 if (value === false || value === 'false') {
328 if (me.messageProperty) {
329 message = me.getMessage(data);
333 records = me.extractData(root);
334 recordCount = records.length;
340 return Ext.create('Ext.data.ResultSet', {
341 total : total || recordCount,
350 * Returns extracted, type-cast rows of data. Iterates to call #extractValues for each row
351 * @param {Object[]/Object} root from server response
354 extractData : function(root) {
360 length = root.length,
361 idProp = me.getIdProperty(),
364 if (!root.length && Ext.isObject(root)) {
369 for (; i < length; i++) {
371 values = me.extractValues(node);
375 record = new Model(values, id, node);
376 records.push(record);
378 if (me.implicitIncludes) {
379 me.readAssociated(record, node);
388 * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
389 * on the record provided.
390 * @param {Ext.data.Model} record The record to load associations for
391 * @param {Object} data The data object
392 * @return {String} Return value description
394 readAssociated: function(record, data) {
395 var associations = record.associations.items,
397 length = associations.length,
398 association, associationData, proxy, reader;
400 for (; i < length; i++) {
401 association = associations[i];
402 associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
404 if (associationData) {
405 reader = association.getReader();
407 proxy = association.associatedModel.proxy;
408 // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
410 reader = proxy.getReader();
412 reader = new this.constructor({
413 model: association.associatedName
417 association.read(record, reader, associationData);
424 * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
425 * record, this should return the relevant part of that data for the given association name. This is only really
426 * needed to support the XML Reader, which has to do a query to get the associated data object
427 * @param {Object} data The raw data object
428 * @param {String} associationName The name of the association to get data for (uses associationKey if present)
429 * @return {Object} The root
431 getAssociatedDataRoot: function(data, associationName) {
432 return data[associationName];
435 getFields: function() {
436 return this.model.prototype.fields.items;
441 * Given an object representing a single model instance's data, iterates over the model's fields and
442 * builds an object with the value for each field.
443 * @param {Object} data The data object to convert
444 * @return {Object} Data object suitable for use with a model constructor
446 extractValues: function(data) {
447 var fields = this.getFields(),
449 length = fields.length,
453 for (; i < length; i++) {
455 value = this.extractorFunctions[i](data);
457 output[field.name] = value;
465 * By default this function just returns what is passed to it. It can be overridden in a subclass
466 * to return something else. See XmlReader for an example.
467 * @param {Object} data The data object
468 * @return {Object} The normalized data object
470 getData: function(data) {
476 * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
477 * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
478 * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
479 * @param {Object} data The data object
480 * @return {Object} The same data object
482 getRoot: function(data) {
487 * Takes a raw response object (as passed to this.read) and returns the useful data segment of it. This must be
488 * implemented by each subclass
489 * @param {Object} response The responce object
490 * @return {Object} The useful data from the response
492 getResponseData: function(response) {
494 Ext.Error.raise("getResponseData must be implemented in the Ext.data.reader.Reader subclass");
500 * Reconfigures the meta data tied to this Reader
502 onMetaChange : function(meta) {
503 var fields = meta.fields,
506 Ext.apply(this, meta);
509 newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {
510 extend: 'Ext.data.Model',
513 this.setModel(newModel, true);
515 this.buildExtractors(true);
520 * Get the idProperty to use for extracting data
522 * @return {String} The id property
524 getIdProperty: function(){
525 var prop = this.idProperty;
526 if (Ext.isEmpty(prop)) {
527 prop = this.model.prototype.idProperty;
534 * This builds optimized functions for retrieving record data and meta data from an object.
535 * Subclasses may need to implement their own getRoot function.
536 * @param {Boolean} [force=false] True to automatically remove existing extractor functions first
538 buildExtractors: function(force) {
540 idProp = me.getIdProperty(),
541 totalProp = me.totalProperty,
542 successProp = me.successProperty,
543 messageProp = me.messageProperty,
546 if (force === true) {
547 delete me.extractorFunctions;
550 if (me.extractorFunctions) {
554 //build the extractors for all the meta data
556 me.getTotal = me.createAccessor(totalProp);
560 me.getSuccess = me.createAccessor(successProp);
564 me.getMessage = me.createAccessor(messageProp);
568 accessor = me.createAccessor(idProp);
570 me.getId = function(record) {
571 var id = accessor.call(me, record);
572 return (id === undefined || id === '') ? null : id;
575 me.getId = function() {
579 me.buildFieldExtractors();
585 buildFieldExtractors: function() {
586 //now build the extractors for all the fields
588 fields = me.getFields(),
591 extractorFunctions = [],
594 for (; i < ln; i++) {
596 map = (field.mapping !== undefined && field.mapping !== null) ? field.mapping : field.name;
598 extractorFunctions.push(me.createAccessor(map));
602 me.extractorFunctions = extractorFunctions;
606 // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
607 nullResultSet: Ext.create('Ext.data.ResultSet', {