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