PageRenderTime 42ms CodeModel.GetById 31ms app.highlight 8ms RepoModel.GetById 0ms app.codeStats 0ms

/javascripts/lib/src/data/JsonReader.js

https://bitbucket.org/ksokmesa/sina-asian
JavaScript | 358 lines | 151 code | 15 blank | 192 comment | 32 complexity | 7ddba4af509576e414873a90bda14d8f MD5 | raw file
Possible License(s): GPL-3.0
  1/*!
  2 * Ext JS Library 3.2.1
  3 * Copyright(c) 2006-2010 Ext JS, Inc.
  4 * licensing@extjs.com
  5 * http://www.extjs.com/license
  6 */
  7/**
  8 * @class Ext.data.JsonReader
  9 * @extends Ext.data.DataReader
 10 * <p>Data reader class to create an Array of {@link Ext.data.Record} objects
 11 * from a JSON packet based on mappings in a provided {@link Ext.data.Record}
 12 * constructor.</p>
 13 * <p>Example code:</p>
 14 * <pre><code>
 15var myReader = new Ext.data.JsonReader({
 16    // metadata configuration options:
 17    {@link #idProperty}: 'id'
 18    {@link #root}: 'rows',
 19    {@link #totalProperty}: 'results',
 20    {@link Ext.data.DataReader#messageProperty}: "msg"  // The element within the response that provides a user-feedback message (optional)
 21
 22    // the fields config option will internally create an {@link Ext.data.Record}
 23    // constructor that provides mapping for reading the record data objects
 24    {@link Ext.data.DataReader#fields fields}: [
 25        // map Record&#39;s 'firstname' field to data object&#39;s key of same name
 26        {name: 'name'},
 27        // map Record&#39;s 'job' field to data object&#39;s 'occupation' key
 28        {name: 'job', mapping: 'occupation'}
 29    ]
 30});
 31</code></pre>
 32 * <p>This would consume a JSON data object of the form:</p><pre><code>
 33{
 34    results: 2000, // Reader&#39;s configured {@link #totalProperty}
 35    rows: [        // Reader&#39;s configured {@link #root}
 36        // record data objects:
 37        { {@link #idProperty id}: 1, firstname: 'Bill', occupation: 'Gardener' },
 38        { {@link #idProperty id}: 2, firstname: 'Ben' , occupation: 'Horticulturalist' },
 39        ...
 40    ]
 41}
 42</code></pre>
 43 * <p><b><u>Automatic configuration using metaData</u></b></p>
 44 * <p>It is possible to change a JsonReader's metadata at any time by including
 45 * a <b><tt>metaData</tt></b> property in the JSON data object. If the JSON data
 46 * object has a <b><tt>metaData</tt></b> property, a {@link Ext.data.Store Store}
 47 * object using this Reader will reconfigure itself to use the newly provided
 48 * field definition and fire its {@link Ext.data.Store#metachange metachange}
 49 * event. The metachange event handler may interrogate the <b><tt>metaData</tt></b>
 50 * property to perform any configuration required.</p>
 51 * <p>Note that reconfiguring a Store potentially invalidates objects which may
 52 * refer to Fields or Records which no longer exist.</p>
 53 * <p>To use this facility you would create the JsonReader like this:</p><pre><code>
 54var myReader = new Ext.data.JsonReader();
 55</code></pre>
 56 * <p>The first data packet from the server would configure the reader by
 57 * containing a <b><tt>metaData</tt></b> property <b>and</b> the data. For
 58 * example, the JSON data object might take the form:</p><pre><code>
 59{
 60    metaData: {
 61        "{@link #idProperty}": "id",
 62        "{@link #root}": "rows",
 63        "{@link #totalProperty}": "results"
 64        "{@link #successProperty}": "success",
 65        "{@link Ext.data.DataReader#fields fields}": [
 66            {"name": "name"},
 67            {"name": "job", "mapping": "occupation"}
 68        ],
 69        // used by store to set its sortInfo
 70        "sortInfo":{
 71           "field": "name",
 72           "direction": "ASC"
 73        },
 74        // {@link Ext.PagingToolbar paging data} (if applicable)
 75        "start": 0,
 76        "limit": 2,
 77        // custom property
 78        "foo": "bar"
 79    },
 80    // Reader&#39;s configured {@link #successProperty}
 81    "success": true,
 82    // Reader&#39;s configured {@link #totalProperty}
 83    "results": 2000,
 84    // Reader&#39;s configured {@link #root}
 85    // (this data simulates 2 results {@link Ext.PagingToolbar per page})
 86    "rows": [ // <b>*Note:</b> this must be an Array
 87        { "id": 1, "name": "Bill", "occupation": "Gardener" },
 88        { "id": 2, "name":  "Ben", "occupation": "Horticulturalist" }
 89    ]
 90}
 91 * </code></pre>
 92 * <p>The <b><tt>metaData</tt></b> property in the JSON data object should contain:</p>
 93 * <div class="mdetail-params"><ul>
 94 * <li>any of the configuration options for this class</li>
 95 * <li>a <b><tt>{@link Ext.data.Record#fields fields}</tt></b> property which
 96 * the JsonReader will use as an argument to the
 97 * {@link Ext.data.Record#create data Record create method} in order to
 98 * configure the layout of the Records it will produce.</li>
 99 * <li>a <b><tt>{@link Ext.data.Store#sortInfo sortInfo}</tt></b> property
100 * which the JsonReader will use to set the {@link Ext.data.Store}'s
101 * {@link Ext.data.Store#sortInfo sortInfo} property</li>
102 * <li>any custom properties needed</li>
103 * </ul></div>
104 *
105 * @constructor
106 * Create a new JsonReader
107 * @param {Object} meta Metadata configuration options.
108 * @param {Array/Object} recordType
109 * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
110 * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
111 * constructor created from {@link Ext.data.Record#create}.</p>
112 */
113Ext.data.JsonReader = function(meta, recordType){
114    meta = meta || {};
115    /**
116     * @cfg {String} idProperty [id] Name of the property within a row object
117     * that contains a record identifier value.  Defaults to <tt>id</tt>
118     */
119    /**
120     * @cfg {String} successProperty [success] Name of the property from which to
121     * retrieve the success attribute. Defaults to <tt>success</tt>.  See
122     * {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
123     * for additional information.
124     */
125    /**
126     * @cfg {String} totalProperty [total] Name of the property from which to
127     * retrieve the total number of records in the dataset. This is only needed
128     * if the whole dataset is not passed in one go, but is being paged from
129     * the remote server.  Defaults to <tt>total</tt>.
130     */
131    /**
132     * @cfg {String} root [undefined] <b>Required</b>.  The name of the property
133     * which contains the Array of row objects.  Defaults to <tt>undefined</tt>.
134     * An exception will be thrown if the root property is undefined. The data
135     * packet value for this property should be an empty array to clear the data
136     * or show no data.
137     */
138    Ext.applyIf(meta, {
139        idProperty: 'id',
140        successProperty: 'success',
141        totalProperty: 'total'
142    });
143
144    Ext.data.JsonReader.superclass.constructor.call(this, meta, recordType || meta.fields);
145};
146Ext.extend(Ext.data.JsonReader, Ext.data.DataReader, {
147    /**
148     * This JsonReader's metadata as passed to the constructor, or as passed in
149     * the last data packet's <b><tt>metaData</tt></b> property.
150     * @type Mixed
151     * @property meta
152     */
153    /**
154     * This method is only used by a DataProxy which has retrieved data from a remote server.
155     * @param {Object} response The XHR object which contains the JSON data in its responseText.
156     * @return {Object} data A data block which is used by an Ext.data.Store object as
157     * a cache of Ext.data.Records.
158     */
159    read : function(response){
160        var json = response.responseText;
161        var o = Ext.decode(json);
162        if(!o) {
163            throw {message: 'JsonReader.read: Json object not found'};
164        }
165        return this.readRecords(o);
166    },
167
168    /*
169     * TODO: refactor code between JsonReader#readRecords, #readResponse into 1 method.
170     * there's ugly duplication going on due to maintaining backwards compat. with 2.0.  It's time to do this.
171     */
172    /**
173     * Decode a JSON response from server.
174     * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
175     * @param {Object} response The XHR object returned through an Ajax server request.
176     * @return {Response} A {@link Ext.data.Response Response} object containing the data response, and also status information.
177     */
178    readResponse : function(action, response) {
179        var o = (response.responseText !== undefined) ? Ext.decode(response.responseText) : response;
180        if(!o) {
181            throw new Ext.data.JsonReader.Error('response');
182        }
183
184        var root = this.getRoot(o);
185        if (action === Ext.data.Api.actions.create) {
186            var def = Ext.isDefined(root);
187            if (def && Ext.isEmpty(root)) {
188                throw new Ext.data.JsonReader.Error('root-empty', this.meta.root);
189            }
190            else if (!def) {
191                throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root);
192            }
193        }
194
195        // instantiate response object
196        var res = new Ext.data.Response({
197            action: action,
198            success: this.getSuccess(o),
199            data: (root) ? this.extractData(root, false) : [],
200            message: this.getMessage(o),
201            raw: o
202        });
203
204        // blow up if no successProperty
205        if (Ext.isEmpty(res.success)) {
206            throw new Ext.data.JsonReader.Error('successProperty-response', this.meta.successProperty);
207        }
208        return res;
209    },
210
211    /**
212     * Create a data block containing Ext.data.Records from a JSON object.
213     * @param {Object} o An object which contains an Array of row objects in the property specified
214     * in the config as 'root, and optionally a property, specified in the config as 'totalProperty'
215     * which contains the total size of the dataset.
216     * @return {Object} data A data block which is used by an Ext.data.Store object as
217     * a cache of Ext.data.Records.
218     */
219    readRecords : function(o){
220        /**
221         * After any data loads, the raw JSON data is available for further custom processing.  If no data is
222         * loaded or there is a load exception this property will be undefined.
223         * @type Object
224         */
225        this.jsonData = o;
226        if(o.metaData){
227            this.onMetaChange(o.metaData);
228        }
229        var s = this.meta, Record = this.recordType,
230            f = Record.prototype.fields, fi = f.items, fl = f.length, v;
231
232        var root = this.getRoot(o), c = root.length, totalRecords = c, success = true;
233        if(s.totalProperty){
234            v = parseInt(this.getTotal(o), 10);
235            if(!isNaN(v)){
236                totalRecords = v;
237            }
238        }
239        if(s.successProperty){
240            v = this.getSuccess(o);
241            if(v === false || v === 'false'){
242                success = false;
243            }
244        }
245
246        // TODO return Ext.data.Response instance instead.  @see #readResponse
247        return {
248            success : success,
249            records : this.extractData(root, true), // <-- true to return [Ext.data.Record]
250            totalRecords : totalRecords
251        };
252    },
253
254    // private
255    buildExtractors : function() {
256        if(this.ef){
257            return;
258        }
259        var s = this.meta, Record = this.recordType,
260            f = Record.prototype.fields, fi = f.items, fl = f.length;
261
262        if(s.totalProperty) {
263            this.getTotal = this.createAccessor(s.totalProperty);
264        }
265        if(s.successProperty) {
266            this.getSuccess = this.createAccessor(s.successProperty);
267        }
268        if (s.messageProperty) {
269            this.getMessage = this.createAccessor(s.messageProperty);
270        }
271        this.getRoot = s.root ? this.createAccessor(s.root) : function(p){return p;};
272        if (s.id || s.idProperty) {
273            var g = this.createAccessor(s.id || s.idProperty);
274            this.getId = function(rec) {
275                var r = g(rec);
276                return (r === undefined || r === '') ? null : r;
277            };
278        } else {
279            this.getId = function(){return null;};
280        }
281        var ef = [];
282        for(var i = 0; i < fl; i++){
283            f = fi[i];
284            var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name;
285            ef.push(this.createAccessor(map));
286        }
287        this.ef = ef;
288    },
289
290    /**
291     * @ignore
292     * TODO This isn't used anywhere??  Don't we want to use this where possible instead of complex #createAccessor?
293     */
294    simpleAccess : function(obj, subsc) {
295        return obj[subsc];
296    },
297
298    /**
299     * @ignore
300     */
301    createAccessor : function(){
302        var re = /[\[\.]/;
303        return function(expr) {
304            if(Ext.isEmpty(expr)){
305                return Ext.emptyFn;
306            }
307            if(Ext.isFunction(expr)){
308                return expr;
309            }
310            var i = String(expr).search(re);
311            if(i >= 0){
312                return new Function('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
313            }
314            return function(obj){
315                return obj[expr];
316            };
317
318        };
319    }(),
320
321    /**
322     * type-casts a single row of raw-data from server
323     * @param {Object} data
324     * @param {Array} items
325     * @param {Integer} len
326     * @private
327     */
328    extractValues : function(data, items, len) {
329        var f, values = {};
330        for(var j = 0; j < len; j++){
331            f = items[j];
332            var v = this.ef[j](data);
333            values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, data);
334        }
335        return values;
336    }
337});
338
339/**
340 * @class Ext.data.JsonReader.Error
341 * Error class for JsonReader
342 */
343Ext.data.JsonReader.Error = Ext.extend(Ext.Error, {
344    constructor : function(message, arg) {
345        this.arg = arg;
346        Ext.Error.call(this, message);
347    },
348    name : 'Ext.data.JsonReader'
349});
350Ext.apply(Ext.data.JsonReader.Error.prototype, {
351    lang: {
352        'response': 'An error occurred while json-decoding your server response',
353        'successProperty-response': 'Could not locate your "successProperty" in your server response.  Please review your JsonReader config to ensure the config-property "successProperty" matches the property in your server-response.  See the JsonReader docs.',
354        'root-undefined-config': 'Your JsonReader was configured without a "root" property.  Please review your JsonReader config and make sure to define the root property.  See the JsonReader docs.',
355        'idProperty-undefined' : 'Your JsonReader was configured without an "idProperty"  Please review your JsonReader configuration and ensure the "idProperty" is set (e.g.: "id").  See the JsonReader docs.',
356        'root-empty': 'Data was expected to be returned by the server in the "root" property of the response.  Please review your JsonReader configuration to ensure the "root" property matches that returned in the server-response.  See JsonReader docs.'
357    }
358});