Upgrade to ExtJS 3.0.3 - Released 10/11/2009
[extjs.git] / pkgs / data-json-debug.js
1 /*!
2  * Ext JS Library 3.0.3
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.data.JsonWriter
9  * @extends Ext.data.DataWriter
10  * DataWriter extension for writing an array or single {@link Ext.data.Record} object(s) in preparation for executing a remote CRUD action.
11  */
12 Ext.data.JsonWriter = function(config) {
13     Ext.data.JsonWriter.superclass.constructor.call(this, config);
14
15     // careful to respect "returnJson", renamed to "encode"
16     // TODO: remove after v3 final release
17     if (this.returnJson != undefined) {
18         this.encode = this.returnJson;
19     }
20 }
21 Ext.extend(Ext.data.JsonWriter, Ext.data.DataWriter, {
22     /**
23      * @cfg {Boolean} returnJson <b>Deprecated</b>.  Use {@link Ext.data.JsonWriter#encode} instead.
24      */
25     returnJson : undefined,
26     /**
27      * @cfg {Boolean} encode <tt>true</tt> to {@link Ext.util.JSON#encode encode} the
28      * {@link Ext.data.DataWriter#toHash hashed data}. Defaults to <tt>true</tt>.  When using
29      * {@link Ext.data.DirectProxy}, set this to <tt>false</tt> since Ext.Direct.JsonProvider will perform
30      * its own json-encoding.  In addition, if you're using {@link Ext.data.HttpProxy}, setting to <tt>false</tt>
31      * will cause HttpProxy to transmit data using the <b>jsonData</b> configuration-params of {@link Ext.Ajax#request}
32      * instead of <b>params</b>.  When using a {@link Ext.data.Store#restful} Store, some serverside frameworks are
33      * tuned to expect data through the jsonData mechanism.  In those cases, one will want to set <b>encode: <tt>false</tt></b>, as in
34      * let the lower-level connection object (eg: Ext.Ajax) do the encoding.
35      */
36     encode : true,
37
38     /**
39      * Final action of a write event.  Apply the written data-object to params.
40      * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
41      * @param {Record[]} rs
42      * @param {Object} http params
43      * @param {Object} data object populated according to DataReader meta-data "root" and "idProperty"
44      */
45     render : function(action, rs, params, data) {
46         if (this.encode === true) {
47             params[this.meta.root] = Ext.encode(data);
48         } else {
49             params.jsonData = {};
50             params.jsonData[this.meta.root] = data;
51         }
52     },
53     /**
54      * Implements abstract Ext.data.DataWriter#createRecord
55      * @protected
56      * @param {Ext.data.Record} rec
57      * @return {Object}
58      */
59     createRecord : function(rec) {
60        return this.toHash(rec);
61     },
62     /**
63      * Implements abstract Ext.data.DataWriter#updateRecord
64      * @protected
65      * @param {Ext.data.Record} rec
66      * @return {Object}
67      */
68     updateRecord : function(rec) {
69         return this.toHash(rec);
70
71     },
72     /**
73      * Implements abstract Ext.data.DataWriter#destroyRecord
74      * @protected
75      * @param {Ext.data.Record} rec
76      * @return {Object}
77      */
78     destroyRecord : function(rec) {
79         return rec.id;
80     }
81 });/**
82  * @class Ext.data.JsonReader
83  * @extends Ext.data.DataReader
84  * <p>Data reader class to create an Array of {@link Ext.data.Record} objects
85  * from a JSON packet based on mappings in a provided {@link Ext.data.Record}
86  * constructor.</p>
87  * <p>Example code:</p>
88  * <pre><code>
89 var myReader = new Ext.data.JsonReader({
90     // metadata configuration options:
91     {@link #idProperty}: 'id'
92     {@link #root}: 'rows',
93     {@link #totalProperty}: 'results',
94     {@link Ext.data.DataReader#messageProperty}: "msg"  // The element within the response that provides a user-feedback message (optional)
95
96     // the fields config option will internally create an {@link Ext.data.Record}
97     // constructor that provides mapping for reading the record data objects
98     {@link Ext.data.DataReader#fields fields}: [
99         // map Record&#39;s 'firstname' field to data object&#39;s key of same name
100         {name: 'name'},
101         // map Record&#39;s 'job' field to data object&#39;s 'occupation' key
102         {name: 'job', mapping: 'occupation'}
103     ]
104 });
105 </code></pre>
106  * <p>This would consume a JSON data object of the form:</p><pre><code>
107 {
108     results: 2000, // Reader&#39;s configured {@link #totalProperty}
109     rows: [        // Reader&#39;s configured {@link #root}
110         // record data objects:
111         { {@link #idProperty id}: 1, firstname: 'Bill', occupation: 'Gardener' },
112         { {@link #idProperty id}: 2, firstname: 'Ben' , occupation: 'Horticulturalist' },
113         ...
114     ]
115 }
116 </code></pre>
117  * <p><b><u>Automatic configuration using metaData</u></b></p>
118  * <p>It is possible to change a JsonReader's metadata at any time by including
119  * a <b><tt>metaData</tt></b> property in the JSON data object. If the JSON data
120  * object has a <b><tt>metaData</tt></b> property, a {@link Ext.data.Store Store}
121  * object using this Reader will reconfigure itself to use the newly provided
122  * field definition and fire its {@link Ext.data.Store#metachange metachange}
123  * event. The metachange event handler may interrogate the <b><tt>metaData</tt></b>
124  * property to perform any configuration required.</p>
125  * <p>Note that reconfiguring a Store potentially invalidates objects which may
126  * refer to Fields or Records which no longer exist.</p>
127  * <p>To use this facility you would create the JsonReader like this:</p><pre><code>
128 var myReader = new Ext.data.JsonReader();
129 </code></pre>
130  * <p>The first data packet from the server would configure the reader by
131  * containing a <b><tt>metaData</tt></b> property <b>and</b> the data. For
132  * example, the JSON data object might take the form:</p><pre><code>
133 {
134     metaData: {
135         "{@link #idProperty}": "id",
136         "{@link #root}": "rows",
137         "{@link #totalProperty}": "results"
138         "{@link #successProperty}": "success",
139         "{@link Ext.data.DataReader#fields fields}": [
140             {"name": "name"},
141             {"name": "job", "mapping": "occupation"}
142         ],
143         // used by store to set its sortInfo
144         "sortInfo":{
145            "field": "name",
146            "direction": "ASC"
147         },
148         // {@link Ext.PagingToolbar paging data} (if applicable)
149         "start": 0,
150         "limit": 2,
151         // custom property
152         "foo": "bar"
153     },
154     // Reader&#39;s configured {@link #successProperty}
155     "success": true,
156     // Reader&#39;s configured {@link #totalProperty}
157     "results": 2000,
158     // Reader&#39;s configured {@link #root}
159     // (this data simulates 2 results {@link Ext.PagingToolbar per page})
160     "rows": [ // <b>*Note:</b> this must be an Array
161         { "id": 1, "name": "Bill", "occupation": "Gardener" },
162         { "id": 2, "name":  "Ben", "occupation": "Horticulturalist" }
163     ]
164 }
165  * </code></pre>
166  * <p>The <b><tt>metaData</tt></b> property in the JSON data object should contain:</p>
167  * <div class="mdetail-params"><ul>
168  * <li>any of the configuration options for this class</li>
169  * <li>a <b><tt>{@link Ext.data.Record#fields fields}</tt></b> property which
170  * the JsonReader will use as an argument to the
171  * {@link Ext.data.Record#create data Record create method} in order to
172  * configure the layout of the Records it will produce.</li>
173  * <li>a <b><tt>{@link Ext.data.Store#sortInfo sortInfo}</tt></b> property
174  * which the JsonReader will use to set the {@link Ext.data.Store}'s
175  * {@link Ext.data.Store#sortInfo sortInfo} property</li>
176  * <li>any custom properties needed</li>
177  * </ul></div>
178  *
179  * @constructor
180  * Create a new JsonReader
181  * @param {Object} meta Metadata configuration options.
182  * @param {Array/Object} recordType
183  * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
184  * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
185  * constructor created from {@link Ext.data.Record#create}.</p>
186  */
187 Ext.data.JsonReader = function(meta, recordType){
188     meta = meta || {};
189     /**
190      * @cfg {String} idProperty [id] Name of the property within a row object
191      * that contains a record identifier value.  Defaults to <tt>id</tt>
192      */
193     /**
194      * @cfg {String} successProperty [success] Name of the property from which to
195      * retrieve the success attribute. Defaults to <tt>success</tt>.  See
196      * {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
197      * for additional information.
198      */
199     /**
200      * @cfg {String} totalProperty [total] Name of the property from which to
201      * retrieve the total number of records in the dataset. This is only needed
202      * if the whole dataset is not passed in one go, but is being paged from
203      * the remote server.  Defaults to <tt>total</tt>.
204      */
205     /**
206      * @cfg {String} root [undefined] <b>Required</b>.  The name of the property
207      * which contains the Array of row objects.  Defaults to <tt>undefined</tt>.
208      * An exception will be thrown if the root property is undefined. The data
209      * packet value for this property should be an empty array to clear the data
210      * or show no data.
211      */
212     Ext.applyIf(meta, {
213         idProperty: 'id',
214         successProperty: 'success',
215         totalProperty: 'total'
216     });
217
218     Ext.data.JsonReader.superclass.constructor.call(this, meta, recordType || meta.fields);
219 };
220 Ext.extend(Ext.data.JsonReader, Ext.data.DataReader, {
221     /**
222      * This JsonReader's metadata as passed to the constructor, or as passed in
223      * the last data packet's <b><tt>metaData</tt></b> property.
224      * @type Mixed
225      * @property meta
226      */
227     /**
228      * This method is only used by a DataProxy which has retrieved data from a remote server.
229      * @param {Object} response The XHR object which contains the JSON data in its responseText.
230      * @return {Object} data A data block which is used by an Ext.data.Store object as
231      * a cache of Ext.data.Records.
232      */
233     read : function(response){
234         var json = response.responseText;
235         var o = Ext.decode(json);
236         if(!o) {
237             throw {message: 'JsonReader.read: Json object not found'};
238         }
239         return this.readRecords(o);
240     },
241
242     /**
243      * Decode a json response from server.
244      * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
245      * @param {Object} response
246      * TODO: refactor code between JsonReader#readRecords, #readResponse into 1 method.
247      * there's ugly duplication going on due to maintaining backwards compat. with 2.0.  It's time to do this.
248      */
249     readResponse : function(action, response) {
250         var o = (response.responseText !== undefined) ? Ext.decode(response.responseText) : response;
251         if(!o) {
252             throw new Ext.data.JsonReader.Error('response');
253         }
254
255         var root = this.getRoot(o);
256         if (action === Ext.data.Api.actions.create) {
257             var def = Ext.isDefined(root);
258             if (def && Ext.isEmpty(root)) {
259                 throw new Ext.data.JsonReader.Error('root-empty', this.meta.root);
260             }
261             else if (!def) {
262                 throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root);
263             }
264         }
265
266         // instantiate response object
267         var res = new Ext.data.Response({
268             action: action,
269             success: this.getSuccess(o),
270             data: this.extractData(root),
271             message: this.getMessage(o),
272             raw: o
273         });
274
275         // blow up if no successProperty
276         if (Ext.isEmpty(res.success)) {
277             throw new Ext.data.JsonReader.Error('successProperty-response', this.meta.successProperty);
278         }
279         return res;
280     },
281
282     /**
283      * Create a data block containing Ext.data.Records from a JSON object.
284      * @param {Object} o An object which contains an Array of row objects in the property specified
285      * in the config as 'root, and optionally a property, specified in the config as 'totalProperty'
286      * which contains the total size of the dataset.
287      * @return {Object} data A data block which is used by an Ext.data.Store object as
288      * a cache of Ext.data.Records.
289      */
290     readRecords : function(o){
291         /**
292          * After any data loads, the raw JSON data is available for further custom processing.  If no data is
293          * loaded or there is a load exception this property will be undefined.
294          * @type Object
295          */
296         this.jsonData = o;
297         if(o.metaData){
298             this.onMetaChange(o.metaData);
299         }
300         var s = this.meta, Record = this.recordType,
301             f = Record.prototype.fields, fi = f.items, fl = f.length, v;
302
303         var root = this.getRoot(o), c = root.length, totalRecords = c, success = true;
304         if(s.totalProperty){
305             v = parseInt(this.getTotal(o), 10);
306             if(!isNaN(v)){
307                 totalRecords = v;
308             }
309         }
310         if(s.successProperty){
311             v = this.getSuccess(o);
312             if(v === false || v === 'false'){
313                 success = false;
314             }
315         }
316
317         // TODO return Ext.data.Response instance instead.  @see #readResponse
318         return {
319             success : success,
320             records : this.extractData(root, true), // <-- true to return [Ext.data.Record]
321             totalRecords : totalRecords
322         };
323     },
324
325     // private
326     buildExtractors : function() {
327         if(this.ef){
328             return;
329         }
330         var s = this.meta, Record = this.recordType,
331             f = Record.prototype.fields, fi = f.items, fl = f.length;
332
333         if(s.totalProperty) {
334             this.getTotal = this.createAccessor(s.totalProperty);
335         }
336         if(s.successProperty) {
337             this.getSuccess = this.createAccessor(s.successProperty);
338         }
339         if (s.messageProperty) {
340             this.getMessage = this.createAccessor(s.messageProperty);
341         }
342         this.getRoot = s.root ? this.createAccessor(s.root) : function(p){return p;};
343         if (s.id || s.idProperty) {
344             var g = this.createAccessor(s.id || s.idProperty);
345             this.getId = function(rec) {
346                 var r = g(rec);
347                 return (r === undefined || r === '') ? null : r;
348             };
349         } else {
350             this.getId = function(){return null;};
351         }
352         var ef = [];
353         for(var i = 0; i < fl; i++){
354             f = fi[i];
355             var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name;
356             ef.push(this.createAccessor(map));
357         }
358         this.ef = ef;
359     },
360
361     /**
362      * @ignore
363      * TODO This isn't used anywhere??  Don't we want to use this where possible instead of complex #createAccessor?
364      */
365     simpleAccess : function(obj, subsc) {
366         return obj[subsc];
367     },
368
369     /**
370      * @ignore
371      */
372     createAccessor : function(){
373         var re = /[\[\.]/;
374         return function(expr) {
375             try {
376                 return(re.test(expr)) ?
377                 new Function('obj', 'return obj.' + expr) :
378                 function(obj){
379                     return obj[expr];
380                 };
381             } catch(e){}
382             return Ext.emptyFn;
383         };
384     }(),
385
386     /**
387      * returns extracted, type-cast rows of data.  Iterates to call #extractValues for each row
388      * @param {Object[]/Object} data-root from server response
389      * @param {Boolean} returnRecords [false] Set true to return instances of Ext.data.Record
390      * @private
391      */
392     extractData : function(root, returnRecords) {
393         var rs = undefined;
394         if (this.isData(root)) {
395             root = [root];
396         }
397         if (Ext.isArray(root)) {
398             var f       = this.recordType.prototype.fields,
399                 fi      = f.items,
400                 fl      = f.length,
401                 rs      = [];
402             if (returnRecords === true) {
403                 var Record = this.recordType;
404                 for (var i = 0; i < root.length; i++) {
405                     var n = root[i];
406                     var record = new Record(this.extractValues(n, fi, fl), this.getId(n));
407                     record.json = n;
408                     rs.push(record);
409                 }
410             }
411             else {
412                 for (var i = 0; i < root.length; i++) {
413                     rs.push(this.extractValues(root[i], fi, fl));
414                 }
415             }
416         }
417         return rs;
418     },
419
420     /**
421      * type-casts a single row of raw-data from server
422      * @param {Object} data
423      * @param {Array} items
424      * @param {Integer} len
425      * @private
426      */
427     extractValues : function(data, items, len) {
428         var f, values = {};
429         for(var j = 0; j < len; j++){
430             f = items[j];
431             var v = this.ef[j](data);
432             values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, data);
433         }
434         return values;
435     }
436 });
437
438 /**
439  * @class Ext.data.JsonReader.Error
440  * Error class for JsonReader
441  */
442 Ext.data.JsonReader.Error = Ext.extend(Ext.Error, {
443     constructor : function(message, arg) {
444         this.arg = arg;
445         Ext.Error.call(this, message);
446     },
447     name : 'Ext.data.JsonReader'
448 });
449 Ext.apply(Ext.data.JsonReader.Error.prototype, {
450     lang: {
451         'response': 'An error occurred while json-decoding your server response',
452         '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.',
453         '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.',
454         '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.',
455         '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.'
456     }
457 });
458 /**
459  * @class Ext.data.ArrayReader
460  * @extends Ext.data.JsonReader
461  * <p>Data reader class to create an Array of {@link Ext.data.Record} objects from an Array.
462  * Each element of that Array represents a row of data fields. The
463  * fields are pulled into a Record object using as a subscript, the <code>mapping</code> property
464  * of the field definition if it exists, or the field's ordinal position in the definition.</p>
465  * <p>Example code:</p>
466  * <pre><code>
467 var Employee = Ext.data.Record.create([
468     {name: 'name', mapping: 1},         // "mapping" only needed if an "id" field is present which
469     {name: 'occupation', mapping: 2}    // precludes using the ordinal position as the index.
470 ]);
471 var myReader = new Ext.data.ArrayReader({
472     {@link #idIndex}: 0
473 }, Employee);
474 </code></pre>
475  * <p>This would consume an Array like this:</p>
476  * <pre><code>
477 [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
478  * </code></pre>
479  * @constructor
480  * Create a new ArrayReader
481  * @param {Object} meta Metadata configuration options.
482  * @param {Array/Object} recordType
483  * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
484  * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
485  * constructor created from {@link Ext.data.Record#create}.</p>
486  */
487 Ext.data.ArrayReader = Ext.extend(Ext.data.JsonReader, {
488     /**
489      * @cfg {String} successProperty
490      * @hide
491      */
492     /**
493      * @cfg {Number} id (optional) The subscript within row Array that provides an ID for the Record.
494      * Deprecated. Use {@link #idIndex} instead.
495      */
496     /**
497      * @cfg {Number} idIndex (optional) The subscript within row Array that provides an ID for the Record.
498      */
499     /**
500      * Create a data block containing Ext.data.Records from an Array.
501      * @param {Object} o An Array of row objects which represents the dataset.
502      * @return {Object} data A data block which is used by an Ext.data.Store object as
503      * a cache of Ext.data.Records.
504      */
505     readRecords : function(o){
506         this.arrayData = o;
507         var s = this.meta,
508             sid = s ? Ext.num(s.idIndex, s.id) : null,
509             recordType = this.recordType, 
510             fields = recordType.prototype.fields,
511             records = [],
512             v;
513
514         if(!this.getRoot) {
515             this.getRoot = s.root ? this.getJsonAccessor(s.root) : function(p) {return p;};
516             if(s.totalProperty) {
517                 this.getTotal = this.getJsonAccessor(s.totalProperty);
518             }
519         }
520
521         var root = this.getRoot(o);
522
523         for(var i = 0, len = root.length; i < len; i++) {
524             var n = root[i],
525                 values = {},
526                 id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
527             for(var j = 0, jlen = fields.length; j < jlen; j++) {
528                 var f = fields.items[j],
529                     k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j;
530                 v = n[k] !== undefined ? n[k] : f.defaultValue;
531                 v = f.convert(v, n);
532                 values[f.name] = v;
533             }
534             var record = new recordType(values, id);
535             record.json = n;
536             records[records.length] = record;
537         }
538
539         var totalRecords = records.length;
540
541         if(s.totalProperty) {
542             v = parseInt(this.getTotal(o), 10);
543             if(!isNaN(v)) {
544                 totalRecords = v;
545             }
546         }
547
548         return {
549             records : records,
550             totalRecords : totalRecords
551         };
552     }
553 });/**
554  * @class Ext.data.ArrayStore
555  * @extends Ext.data.Store
556  * <p>Formerly known as "SimpleStore".</p>
557  * <p>Small helper class to make creating {@link Ext.data.Store}s from Array data easier.
558  * An ArrayStore will be automatically configured with a {@link Ext.data.ArrayReader}.</p>
559  * <p>A store configuration would be something like:<pre><code>
560 var store = new Ext.data.ArrayStore({
561     // store configs
562     autoDestroy: true,
563     storeId: 'myStore',
564     // reader configs
565     idIndex: 0,  
566     fields: [
567        'company',
568        {name: 'price', type: 'float'},
569        {name: 'change', type: 'float'},
570        {name: 'pctChange', type: 'float'},
571        {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
572     ]
573 });
574  * </code></pre></p>
575  * <p>This store is configured to consume a returned object of the form:<pre><code>
576 var myData = [
577     ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
578     ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
579     ['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
580     ['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
581     ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
582 ];
583  * </code></pre>
584  * An object literal of this form could also be used as the {@link #data} config option.</p>
585  * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of 
586  * <b>{@link Ext.data.ArrayReader ArrayReader}</b>.</p>
587  * @constructor
588  * @param {Object} config
589  * @xtype arraystore
590  */
591 Ext.data.ArrayStore = Ext.extend(Ext.data.Store, {
592     /**
593      * @cfg {Ext.data.DataReader} reader @hide
594      */
595     constructor: function(config){
596         Ext.data.ArrayStore.superclass.constructor.call(this, Ext.apply(config, {
597             reader: new Ext.data.ArrayReader(config)
598         }));
599     },
600
601     loadData : function(data, append){
602         if(this.expandData === true){
603             var r = [];
604             for(var i = 0, len = data.length; i < len; i++){
605                 r[r.length] = [data[i]];
606             }
607             data = r;
608         }
609         Ext.data.ArrayStore.superclass.loadData.call(this, data, append);
610     }
611 });
612 Ext.reg('arraystore', Ext.data.ArrayStore);
613
614 // backwards compat
615 Ext.data.SimpleStore = Ext.data.ArrayStore;
616 Ext.reg('simplestore', Ext.data.SimpleStore);/**
617  * @class Ext.data.JsonStore
618  * @extends Ext.data.Store
619  * <p>Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
620  * A JsonStore will be automatically configured with a {@link Ext.data.JsonReader}.</p>
621  * <p>A store configuration would be something like:<pre><code>
622 var store = new Ext.data.JsonStore({
623     // store configs
624     autoDestroy: true,
625     url: 'get-images.php',
626     storeId: 'myStore',
627     // reader configs
628     root: 'images',
629     idProperty: 'name',
630     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
631 });
632  * </code></pre></p>
633  * <p>This store is configured to consume a returned object of the form:<pre><code>
634 {
635     images: [
636         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
637         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
638     ]
639 }
640  * </code></pre>
641  * An object literal of this form could also be used as the {@link #data} config option.</p>
642  * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
643  * <b>{@link Ext.data.JsonReader JsonReader}</b>.</p>
644  * @constructor
645  * @param {Object} config
646  * @xtype jsonstore
647  */
648 Ext.data.JsonStore = Ext.extend(Ext.data.Store, {
649     /**
650      * @cfg {Ext.data.DataReader} reader @hide
651      */
652     constructor: function(config){
653         Ext.data.JsonStore.superclass.constructor.call(this, Ext.apply(config, {
654             reader: new Ext.data.JsonReader(config)
655         }));
656     }
657 });
658 Ext.reg('jsonstore', Ext.data.JsonStore);