| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778 | <!DOCTYPE html><html><head>  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  <title>The source code</title>  <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />  <script type="text/javascript" src="../resources/prettify/prettify.js"></script>  <style type="text/css">    .highlight { display: block; background-color: #ddd; }  </style>  <script type="text/javascript">    function highlight() {      document.getElementById(location.hash.replace(/#/, "")).className = "highlight";    }  </script></head><body onload="prettyPrint(); highlight();">  <pre class="prettyprint lang-js"><span id='Ext-data-reader-Reader'>/**</span> * @author Ed Spencer * * Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link * Ext.data.Store Store} - often in response to an AJAX request. In general there is usually no need to create * a Reader instance directly, since a Reader is almost always used together with a {@link Ext.data.proxy.Proxy Proxy}, * and is configured using the Proxy's {@link Ext.data.proxy.Proxy#cfg-reader reader} configuration property: *  *     Ext.create('Ext.data.Store', { *         model: 'User', *         proxy: { *             type: 'ajax', *             url : 'users.json', *             reader: { *                 type: 'json', *                 root: 'users' *             } *         }, *     }); *      * The above reader is configured to consume a JSON string that looks something like this: *   *     { *         "success": true, *         "users": [ *             { "name": "User 1" }, *             { "name": "User 2" } *         ] *     } *  * * # Loading Nested Data * * Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.association.Association * associations} configured on each Model. Below is an example demonstrating the flexibility of these associations in a * fictional CRM system which manages a User, their Orders, OrderItems and Products. First we'll define the models: * *     Ext.define("User", { *         extend: 'Ext.data.Model', *         fields: [ *             'id', 'name' *         ], * *         hasMany: {model: 'Order', name: 'orders'}, * *         proxy: { *             type: 'rest', *             url : 'users.json', *             reader: { *                 type: 'json', *                 root: 'users' *             } *         } *     }); * *     Ext.define("Order", { *         extend: 'Ext.data.Model', *         fields: [ *             'id', 'total' *         ], * *         hasMany  : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'}, *         belongsTo: 'User' *     }); * *     Ext.define("OrderItem", { *         extend: 'Ext.data.Model', *         fields: [ *             'id', 'price', 'quantity', 'order_id', 'product_id' *         ], * *         belongsTo: ['Order', {model: 'Product', associationKey: 'product'}] *     }); * *     Ext.define("Product", { *         extend: 'Ext.data.Model', *         fields: [ *             'id', 'name' *         ], * *         hasMany: 'OrderItem' *     }); * * This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems. * Finally, each OrderItem has a single Product. This allows us to consume data like this: * *     { *         "users": [ *             { *                 "id": 123, *                 "name": "Ed", *                 "orders": [ *                     { *                         "id": 50, *                         "total": 100, *                         "order_items": [ *                             { *                                 "id"      : 20, *                                 "price"   : 40, *                                 "quantity": 2, *                                 "product" : { *                                     "id": 1000, *                                     "name": "MacBook Pro" *                                 } *                             }, *                             { *                                 "id"      : 21, *                                 "price"   : 20, *                                 "quantity": 3, *                                 "product" : { *                                     "id": 1001, *                                     "name": "iPhone" *                                 } *                             } *                         ] *                     } *                 ] *             } *         ] *     } * * The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the * Orders for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case), * and finally the Product associated with each OrderItem. Now we can read the data and use it as follows: * *     var store = Ext.create('Ext.data.Store', { *         model: "User" *     }); * *     store.load({ *         callback: function() { *             //the user that was loaded *             var user = store.first(); * *             console.log("Orders for " + user.get('name') + ":") * *             //iterate over the Orders for each User *             user.orders().each(function(order) { *                 console.log("Order ID: " + order.getId() + ", which contains items:"); * *                 //iterate over the OrderItems for each Order *                 order.orderItems().each(function(orderItem) { *                     //we know that the Product data is already loaded, so we can use the synchronous getProduct *                     //usually, we would use the asynchronous version (see {@link Ext.data.association.BelongsTo}) *                     var product = orderItem.getProduct(); * *                     console.log(orderItem.get('quantity') + ' orders of ' + product.get('name')); *                 }); *             }); *         } *     }); * * Running the code above results in the following: * *     Orders for Ed: *     Order ID: 50, which contains items: *     2 orders of MacBook Pro *     3 orders of iPhone */Ext.define('Ext.data.reader.Reader', {    requires: ['Ext.data.ResultSet', 'Ext.XTemplate'],    alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],    mixins: {        observable: 'Ext.util.Observable'    },<span id='Ext-data-reader-Reader-cfg-idProperty'>    /**</span>     * @cfg {String} idProperty     * Name of the property within a row object that contains a record identifier value. Defaults to the id of the     * model. If an idProperty is explicitly specified it will override the idProperty defined on the model.     */<span id='Ext-data-reader-Reader-cfg-totalProperty'>    /**</span>     * @cfg {String} [totalProperty="total"]     * Name of the property from which to retrieve the total number of records in the dataset. This is only needed if     * the whole dataset is not passed in one go, but is being paged from the remote server.     */    totalProperty: 'total',<span id='Ext-data-reader-Reader-cfg-successProperty'>    /**</span>     * @cfg {String} [successProperty="success"]     * Name of the property from which to retrieve the `success` attribute, the value of which indicates     * whether a given request succeeded or failed (typically a boolean or 'true'|'false'). See     * {@link Ext.data.proxy.Server}.{@link Ext.data.proxy.Server#exception exception} for additional information.     */    successProperty: 'success',<span id='Ext-data-reader-Reader-cfg-root'>    /**</span>     * @cfg {String} root     * The name of the property which contains the data items corresponding to the Model(s) for which this     * Reader is configured.  For JSON reader it's a property name (or a dot-separated list of property names     * if the root is nested).  For XML reader it's a CSS selector.  For Array reader the root is not applicable     * since the data is assumed to be a single-level array of arrays.     *      * By default the natural root of the data will be used: the root JSON array, the root XML element, or the array.     *     * The data packet value for this property should be an empty array to clear the data or show no data.     */    root: '',    <span id='Ext-data-reader-Reader-cfg-messageProperty'>    /**</span>     * @cfg {String} messageProperty     * The name of the property which contains a response message. This property is optional.     */    <span id='Ext-data-reader-Reader-cfg-implicitIncludes'>    /**</span>     * @cfg {Boolean} [implicitIncludes=true]     * True to automatically parse models nested within other models in a response object. See the     * Ext.data.reader.Reader intro docs for full explanation.     */    implicitIncludes: true,    <span id='Ext-data-reader-Reader-cfg-readRecordsOnFailure'>    /**</span>     * @cfg {Boolean} [readRecordsOnFailure=true]     * True to extract the records from a data packet even if the {@link #successProperty} returns false.     */    readRecordsOnFailure: true,    <span id='Ext-data-reader-Reader-property-metaData'>    /**</span>     * @property {Object} metaData     * The raw meta data that was most recently read, if any. Meta data can include existing     * Reader config options like {@link #idProperty}, {@link #totalProperty}, etc. that get     * automatically applied to the Reader, and those can still be accessed directly from the Reader     * if needed. However, meta data is also often used to pass other custom data to be processed     * by application code. For example, it is common when reconfiguring the data model of a grid to     * also pass a corresponding column model config to be applied to the grid. Any such data will     * not get applied to the Reader directly (it just gets passed through and is ignored by Ext).     * This metaData property gives you access to all meta data that was passed, including any such     * custom data ignored by the reader.     *      * This is a read-only property, and it will get replaced each time a new meta data object is     * passed to the reader. Note that typically you would handle proxy's     * {@link Ext.data.proxy.Proxy#metachange metachange} event which passes this exact same meta     * object to listeners. However this property is available if it's more convenient to access it     * via the reader directly in certain cases.     * @readonly     */        /*     * @property {Boolean} isReader     * `true` in this class to identify an object as an instantiated Reader, or subclass thereof.     */    isReader: true,    // Private flag to the generated convertRecordData function to indicate whether to apply Field default    // values to fields for which no value is present in the raw data.    // This is set to false by a Server Proxy which is reading the response from a "create" or "update" operation.    applyDefaults: true,    lastFieldGeneration: null,    <span id='Ext-data-reader-Reader-method-constructor'>    /**</span>     * Creates new Reader.     * @param {Object} config (optional) Config object.     */    constructor: function(config) {        var me = this;                me.mixins.observable.constructor.call(me, config);        me.fieldCount = 0;        me.model = Ext.ModelManager.getModel(me.model);        me.accessExpressionFn = Ext.Function.bind(me.createFieldAccessExpression, me);        // Extractors can only be calculated if the fields MixedCollection has been set.        // A Model may only complete its setup (set the prototype properties) after asynchronous loading        // which would mean that there may be no "fields"        // If this happens, the load callback will call proxy.setModel which calls reader.setModel which        // triggers buildExtractors.        if (me.model && me.model.prototype.fields) {            me.buildExtractors();        }        this.addEvents(<span id='Ext-data-reader-Reader-event-exception'>            /**</span>             * @event             * Fires when the reader receives improperly encoded data from the server             * @param {Ext.data.reader.Reader} reader A reference to this reader             * @param {XMLHttpRequest} response The XMLHttpRequest response object             * @param {Ext.data.ResultSet} error The error object             */            'exception'        );    },<span id='Ext-data-reader-Reader-method-setModel'>    /**</span>     * Sets a new model for the reader.     * @private     * @param {Object} model The model to set.     * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured     */    setModel: function(model, setOnProxy) {        var me = this;                me.model = Ext.ModelManager.getModel(model);        me.buildExtractors(true);                if (setOnProxy && me.proxy) {            me.proxy.setModel(me.model, true);        }    },<span id='Ext-data-reader-Reader-method-read'>    /**</span>     * Reads the given response object. This method normalizes the different types of response object that may be passed to it.     * If it's an XMLHttpRequest object, hand off to the subclass' {@link #getResponseData} method.     * Else, hand off the reading of records to the {@link #readRecords} method.     * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object     * @return {Ext.data.ResultSet} The parsed or default ResultSet object     */    read: function(response) {        var data;        if (response) {            data = response.responseText ? this.getResponseData(response) : this.readRecords(response);        }        return data || this.nullResultSet;    },<span id='Ext-data-reader-Reader-method-readRecords'>    /**</span>     * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call this function     * before running its own logic and returning the Ext.data.ResultSet instance. For most Readers additional     * processing should not be needed.     * @param {Object} data The raw data object     * @return {Ext.data.ResultSet} A ResultSet object     */    readRecords: function(data) {        var me = this,            success,            recordCount,            records,            root,            total,            value,            message;                /*         * We check here whether fields collection has changed since the last read.         * This works around an issue when a Model is used for both a Tree and another         * source, because the tree decorates the model with extra fields and it causes         * issues because the readers aren't notified.         */        if (me.lastFieldGeneration !== me.model.prototype.fields.generation) {            me.buildExtractors(true);        }        <span id='Ext-data-reader-Reader-property-rawData'>        /**</span>         * @property {Object} rawData         * The raw data object that was last passed to {@link #readRecords}. Stored for further processing if needed.         */        me.rawData = data;        data = me.getData(data);                success = true;        recordCount = 0;        records = [];                    if (me.successProperty) {            value = me.getSuccess(data);            if (value === false || value === 'false') {                success = false;            }        }                if (me.messageProperty) {            message = me.getMessage(data);        }                // Only try and extract other data if call was successful        if (me.readRecordsOnFailure || success) {            // If we pass an array as the data, we dont use getRoot on the data.            // Instead the root equals to the data.            root = Ext.isArray(data) ? data : me.getRoot(data);                        if (root) {                total = root.length;            }          if (me.totalProperty) {                value = parseInt(me.getTotal(data), 10);                if (!isNaN(value)) {                    total = value;                }            }           if (root) {                records = me.extractData(root);                recordCount = records.length;            }        }        return new Ext.data.ResultSet({            total  : total || recordCount,            count  : recordCount,            records: records,            success: success,            message: message        });    },<span id='Ext-data-reader-Reader-method-extractData'>    /**</span>     * Returns extracted, type-cast rows of data.     * @param {Object[]/Object} root from server response     * @return {Array} An array of records containing the extracted data     * @private     */    extractData : function(root) {        var me = this,            records = [],            Model   = me.model,            length  = root.length,            convertedValues, node, record, i;                    if (!root.length && Ext.isObject(root)) {            root = [root];            length = 1;        }        for (i = 0; i < length; i++) {            node = root[i];            if (!node.isModel) {                 // Create a record with an empty data object.                // Populate that data object by extracting and converting field values from raw data                record = new Model(undefined, me.getId(node), node, convertedValues = {});                // If the server did not include an id in the response data, the Model constructor will mark the record as phantom.                // We  need to set phantom to false here because records created from a server response using a reader by definition are not phantom records.                record.phantom = false;                // Use generated function to extract all fields at once                me.convertRecordData(convertedValues, node, record);                records.push(record);                                if (me.implicitIncludes) {                    me.readAssociated(record, node);                }            } else {                // If we're given a model instance in the data, just push it on                // without doing any conversion                records.push(node);            }        }        return records;    },    <span id='Ext-data-reader-Reader-method-readAssociated'>    /**</span>     * @private     * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations     * on the record provided.     * @param {Ext.data.Model} record The record to load associations for     * @param {Object} data The data object     * @return {String} Return value description     */    readAssociated: function(record, data) {        var associations = record.associations.items,            i            = 0,            length       = associations.length,            association, associationData, proxy, reader;                for (; i < length; i++) {            association     = associations[i];            associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);                        if (associationData) {                reader = association.getReader();                if (!reader) {                    proxy = association.associatedModel.proxy;                    // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one                    if (proxy) {                        reader = proxy.getReader();                    } else {                        reader = new this.constructor({                            model: association.associatedName                        });                    }                }                association.read(record, reader, associationData);            }          }    },    <span id='Ext-data-reader-Reader-method-getAssociatedDataRoot'>    /**</span>     * @private     * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific     * record, this should return the relevant part of that data for the given association name. This is only really     * needed to support the XML Reader, which has to do a query to get the associated data object     * @param {Object} data The raw data object     * @param {String} associationName The name of the association to get data for (uses associationKey if present)     * @return {Object} The root     */    getAssociatedDataRoot: function(data, associationName) {        return data[associationName];    },        getFields: function() {        return this.model.prototype.fields.items;    },<span id='Ext-data-reader-Reader-method-getData'>    /**</span>     * @private     * By default this function just returns what is passed to it. It can be overridden in a subclass     * to return something else. See XmlReader for an example.     * @param {Object} data The data object     * @return {Object} The normalized data object     */    getData: function(data) {        return data;    },<span id='Ext-data-reader-Reader-method-getRoot'>    /**</span>     * @private     * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type     * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.     * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.     * @param {Object} data The data object     * @return {Object} The same data object     */    getRoot: function(data) {        return data;    },<span id='Ext-data-reader-Reader-method-getResponseData'>    /**</span>     * Takes a raw response object (as passed to the {@link #read} method) and returns the useful data     * segment from it. This must be implemented by each subclass.     * @param {Object} response The response object     * @return {Ext.data.ResultSet} A ResultSet object     */    getResponseData: function(response) {        //<debug>        Ext.Error.raise("getResponseData must be implemented in the Ext.data.reader.Reader subclass");        //</debug>    },<span id='Ext-data-reader-Reader-method-onMetaChange'>    /**</span>     * @private     * Reconfigures the meta data tied to this Reader     */    onMetaChange : function(meta) {        var me = this,            fields = meta.fields || me.getFields(),            newModel,            clientIdProperty;                // save off the raw meta data        me.metaData = meta;                // set any reader-specific configs from meta if available        me.root = meta.root || me.root;        me.idProperty = meta.idProperty || me.idProperty;        me.totalProperty = meta.totalProperty || me.totalProperty;        me.successProperty = meta.successProperty || me.successProperty;        me.messageProperty = meta.messageProperty || me.messageProperty;        clientIdProperty = meta.clientIdProperty;        if (me.model) {            me.model.setFields(fields, me.idProperty, clientIdProperty);            me.setModel(me.model, true);        }        else {            newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {                extend: 'Ext.data.Model',                fields: fields,                clientIdProperty: clientIdProperty            });            if (me.idProperty) {                // We only do this if the reader actually has a custom idProperty set,                // otherwise let the model use its own default value. It is valid for                // the reader idProperty to be undefined, in which case it will use the                // model's idProperty (in getIdProperty()).                newModel.idProperty = me.idProperty;            }            me.setModel(newModel, true);        }    },    <span id='Ext-data-reader-Reader-method-getIdProperty'>    /**</span>     * Get the idProperty to use for extracting data     * @private     * @return {String} The id property     */    getIdProperty: function(){        return this.idProperty || this.model.prototype.idProperty;    },<span id='Ext-data-reader-Reader-method-buildExtractors'>    /**</span>     * @private     * This builds optimized functions for retrieving record data and meta data from an object.     * Subclasses may need to implement their own getRoot function.     * @param {Boolean} [force=false] True to automatically remove existing extractor functions first     */    buildExtractors: function(force) {        var me          = this,            idProp      = me.getIdProperty(),            totalProp   = me.totalProperty,            successProp = me.successProperty,            messageProp = me.messageProperty,            accessor,            idField,            map;                    if (force === true) {            delete me.convertRecordData;        }                if (me.convertRecordData) {            return;        }           //build the extractors for all the meta data        if (totalProp) {            me.getTotal = me.createAccessor(totalProp);        }        if (successProp) {            me.getSuccess = me.createAccessor(successProp);        }        if (messageProp) {            me.getMessage = me.createAccessor(messageProp);        }        if (idProp) {            idField = me.model.prototype.fields.get(idProp);            if (idField) {                map = idField.mapping;                idProp = (map !== undefined && map !== null) ? map : idProp;            }            accessor = me.createAccessor(idProp);            me.getId = function(record) {                var id = accessor.call(me, record);                return (id === undefined || id === '') ? null : id;            };        } else {            me.getId = function() {                return null;            };        }        me.convertRecordData = me.buildRecordDataExtractor();        me.lastFieldGeneration = me.model.prototype.fields.generation;    },    recordDataExtractorTemplate : [        'var me = this\n',        '    ,fields = me.model.prototype.fields\n',        '    ,value\n',        '    ,internalId\n',        '<tpl for="fields">',        '    ,__field{#} = fields.get("{name}")\n',        '</tpl>', ';\n',        'return function(dest, source, record) {\n',        '<tpl for="fields">',        // createFieldAccessExpression must be implemented in subclasses to extract data from the source object in the correct way        '    value = {[ this.createFieldAccessExpression(values, "__field" + xindex, "source") ]};\n',        // Code for processing a source property when a custom convert is defined            '<tpl if="hasCustomConvert">',        '    dest["{name}"] = value === undefined ? __field{#}.convert(__field{#}.defaultValue, record) : __field{#}.convert(value, record);\n',        // Code for processing a source property when there is a default value            '<tpl elseif="defaultValue !== undefined">',        '    if (value === undefined) {\n',        '        if (me.applyDefaults) {\n',                '<tpl if="convert">',        '            dest["{name}"] = __field{#}.convert(__field{#}.defaultValue, record);\n',                '<tpl else>',        '            dest["{name}"] = __field{#}.defaultValue\n',                '</tpl>',        '        };\n',        '    } else {\n',                '<tpl if="convert">',        '        dest["{name}"] = __field{#}.convert(value, record);\n',                '<tpl else>',        '        dest["{name}"] = value;\n',                '</tpl>',        '    };',        // Code for processing a source property value when there is no default value            '<tpl else>',        '    if (value !== undefined) {\n',                '<tpl if="convert">',        '        dest["{name}"] = __field{#}.convert(value, record);\n',                '<tpl else>',        '        dest["{name}"] = value;\n',                '</tpl>',        '    }\n',            '</tpl>',        '</tpl>',        // set the client id as the internalId of the record.        // clientId handles the case where a client side record did not previously exist on the server,        // so the server is passing back a client id that can be used to pair the server side record up with the client record        '<tpl if="clientIdProp">',        '    if (record && (internalId = {[ this.createFieldAccessExpression(\{mapping: values.clientIdProp\}, null, "source") ]})) {\n',        '        record.{["internalId"]} = internalId;\n',        '    }\n',        '</tpl>',        '};'    ],<span id='Ext-data-reader-Reader-method-buildRecordDataExtractor'>    /**</span>     * @private     * Return a function which will read a raw row object in the format this Reader accepts, and populates     * a record's data object with converted data values.     *     * The returned function must be passed the following parameters:     *     * - dest A record's empty data object into which the new field value properties are injected.     * - source A raw row data object of whatever type this Reader consumes     * - record The record which is being populated.     *     */    buildRecordDataExtractor: function() {        var me = this,            modelProto = me.model.prototype,            templateData = {                clientIdProp: modelProto.clientIdProperty,                fields: modelProto.fields.items            };        me.recordDataExtractorTemplate.createFieldAccessExpression = me.accessExpressionFn;        // Here we are creating a new Function and invoking it immediately in the scope of this Reader        // It declares several vars capturing the configured context of this Reader, and returns a function        // which, when passed a record data object, a raw data row in the format this Reader is configured to read,        // and the record which is being created, will populate the record's data object from the raw row data.        return Ext.functionFactory(me.recordDataExtractorTemplate.apply(templateData)).call(me);    },    destroyReader: function() {        var me = this;        delete me.proxy;        delete me.model;        delete me.convertRecordData;        delete me.getId;        delete me.getTotal;        delete me.getSuccess;        delete me.getMessage;    }}, function() {    var proto = this.prototype;    Ext.apply(proto, {        // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)        nullResultSet: new Ext.data.ResultSet({            total  : 0,            count  : 0,            records: [],            success: true        }),        recordDataExtractorTemplate: new Ext.XTemplate(proto.recordDataExtractorTemplate)    });});</pre></body></html>
 |