3 * Copyright(c) 2006-2009 Ext JS, LLC
5 * http://www.extjs.com/license
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.
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;
19 Ext.extend(Ext.data.JsonWriter, Ext.data.DataWriter, {
21 * @cfg {Boolean} returnJson <b>Deprecated. Use {@link Ext.data.JsonWriter#encode} instead.
23 returnJson : undefined,
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>
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"
42 render : function(action, rs, params, data) {
43 Ext.apply(params, data);
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]);
49 params[this.meta.root] = Ext.encode(params[this.meta.root]);
55 * @param {Ext.data.Record} rec
57 createRecord : function(rec) {
58 return this.toHash(rec);
63 * @param {Ext.data.Record} rec
65 updateRecord : function(rec) {
66 return this.toHash(rec);
72 * @param {Ext.data.Record} rec
74 destroyRecord : function(rec) {
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>
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
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)
94 Employee // {@link Ext.data.Record} constructor that provides mapping for JSON object
97 * <p>This would consume a JSON data object of the form:</p><pre><code>
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
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>
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();
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
136 totalProperty: 'results',
139 {name: 'job', mapping: 'occupation'}
141 sortInfo: {field: 'name', direction:'ASC'}, // used by store to set its sortInfo
142 foo: 'bar' // custom property
146 { 'id': 1, 'name': 'Bill', occupation: 'Gardener' },
147 { 'id': 2, 'name': 'Ben', occupation: 'Horticulturalist' }
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
163 * @cfg {String} idProperty [id] Name of the property within a row object that contains a record identifier value. Defaults to <tt>id</tt>
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>
172 Ext.data.JsonReader = function(meta, recordType){
175 // default idProperty, successProperty & totalProperty -> "id", "success", "total"
178 successProperty: 'success',
179 totalProperty: 'total'
182 Ext.data.JsonReader.superclass.constructor.call(this, meta, recordType || meta.fields);
184 Ext.extend(Ext.data.JsonReader, Ext.data.DataReader, {
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.
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.
197 read : function(response){
198 var json = response.responseText;
199 var o = Ext.decode(json);
201 throw {message: "JsonReader.read: Json object not found"};
203 return this.readRecords(o);
206 // private function a store will implement
207 onMetaChange : function(meta, recordType, o){
214 simpleAccess: function(obj, subsc) {
221 getJsonAccessor: function(){
223 return function(expr) {
225 return(re.test(expr)) ?
226 new Function("obj", "return obj." + expr) :
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.
243 readRecords : function(o){
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.
252 this.meta = o.metaData;
253 this.recordType = Ext.data.Record.create(o.metaData.fields);
254 this.onMetaChange(this.meta, this.recordType, o);
256 var s = this.meta, Record = this.recordType,
257 f = Record.prototype.fields, fi = f.items, fl = f.length, v;
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;
263 v = parseInt(this.getTotal(o), 10);
268 if(s.successProperty){
269 v = this.getSuccess(o);
270 if(v === false || v === 'false'){
276 for(var i = 0; i < c; i++){
278 var record = new Record(this.extractValues(n, fi, fl), this.getId(n));
285 totalRecords : totalRecords
290 buildExtractors : function() {
294 var s = this.meta, Record = this.recordType,
295 f = Record.prototype.fields, fi = f.items, fl = f.length;
297 if(s.totalProperty) {
298 this.getTotal = this.getJsonAccessor(s.totalProperty);
300 if(s.successProperty) {
301 this.getSuccess = this.getJsonAccessor(s.successProperty);
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) {
308 return (r === undefined || r === "") ? null : r;
311 this.getId = function(){return null;};
314 for(var i = 0; i < fl; i++){
316 var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name;
317 ef.push(this.getJsonAccessor(map));
322 // private extractValues
323 extractValues: function(data, items, len) {
325 for(var j = 0; j < len; j++){
327 var v = this.ef[j](data);
328 values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, data);
334 * Decode a json response from server.
335 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
336 * @param {Object} response
338 readResponse : function(action, response) {
339 var o = (response.responseText !== undefined) ? Ext.decode(response.responseText) : response;
341 throw new Ext.data.JsonReader.Error('response');
343 if (Ext.isEmpty(o[this.meta.successProperty])) {
344 throw new Ext.data.JsonReader.Error('successProperty-response', this.meta.successProperty);
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);
351 else if (o[this.meta.root] === undefined) {
352 throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root);
355 // make sure extraction functions are defined.
356 this.ef = this.buildExtractors();
362 * @class Ext.data.JsonReader.Error
363 * Error class for JsonReader
365 Ext.data.JsonReader.Error = Ext.extend(Ext.Error, {
366 constructor : function(message, arg) {
368 Ext.Error.call(this, message);
370 name : 'Ext.data.JsonReader'
372 Ext.apply(Ext.data.JsonReader.Error.prototype, {
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.'
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>
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.
395 var myReader = new Ext.data.ArrayReader({
399 * <p>This would consume an Array like this:</p>
401 [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
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>
411 Ext.data.ArrayReader = Ext.extend(Ext.data.JsonReader, {
413 * @cfg {String} successProperty
417 * @cfg {Number} id (optional) The subscript within row Array that provides an ID for the Record.
418 * Deprecated. Use {@link #idIndex} instead.
421 * @cfg {Number} idIndex (optional) The subscript within row Array that provides an ID for the Record.
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.
429 readRecords : function(o){
432 sid = s ? Ext.num(s.idIndex, s.id) : null,
433 recordType = this.recordType,
434 fields = recordType.prototype.fields,
439 this.getRoot = s.root ? this.getJsonAccessor(s.root) : function(p) {return p;};
440 if(s.totalProperty) {
441 this.getTotal = this.getJsonAccessor(s.totalProperty);
445 var root = this.getRoot(o);
447 for(var i = 0; i < root.length; i++) {
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;
458 var record = new recordType(values, id);
460 records[records.length] = record;
463 var totalRecords = records.length;
465 if(s.totalProperty) {
466 v = parseInt(this.getTotal(o), 10);
474 totalRecords : totalRecords
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({
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'}
499 * <p>This store is configured to consume a returned object of the form:<pre><code>
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']
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>
512 * @param {Object} config
515 Ext.data.ArrayStore = Ext.extend(Ext.data.Store, {
517 * @cfg {Ext.data.DataReader} reader @hide
519 constructor: function(config){
520 Ext.data.ArrayStore.superclass.constructor.call(this, Ext.apply(config, {
521 reader: new Ext.data.ArrayReader(config)
525 loadData : function(data, append){
526 if(this.expandData === true){
528 for(var i = 0, len = data.length; i < len; i++){
529 r[r.length] = [data[i]];
533 Ext.data.ArrayStore.superclass.loadData.call(this, data, append);
536 Ext.reg('arraystore', Ext.data.ArrayStore);
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({
549 url: 'get-images.php',
554 fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
557 * <p>This store is configured to consume a returned object of the form:<pre><code>
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)}
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>
569 * @param {Object} config
572 Ext.data.JsonStore = Ext.extend(Ext.data.Store, {
574 * @cfg {Ext.data.DataReader} reader @hide
576 constructor: function(config){
577 Ext.data.JsonStore.superclass.constructor.call(this, Ext.apply(config, {
578 reader: new Ext.data.JsonReader(config)
582 Ext.reg('jsonstore', Ext.data.JsonStore);