Upgrade to ExtJS 3.0.0 - Released 07/06/2009
[extjs.git] / docs / source / JsonReader.html
1 <html>\r
2 <head>\r
3   <title>The source code</title>\r
4     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />\r
5     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>\r
6 </head>\r
7 <body  onload="prettyPrint();">\r
8     <pre class="prettyprint lang-js"><div id="cls-Ext.data.JsonReader"></div>/**
9  * @class Ext.data.JsonReader
10  * @extends Ext.data.DataReader
11  * <p>Data reader class to create an Array of {@link Ext.data.Record} objects from a JSON response
12  * based on mappings in a provided {@link Ext.data.Record} constructor.</p>
13  * <p>Example code:</p>
14  * <pre><code>
15 var Employee = Ext.data.Record.create([
16     {name: 'firstname'},                  // map the Record's "firstname" field to the row object's key of the same name
17     {name: 'job', mapping: 'occupation'}  // map the Record's "job" field to the row object's "occupation" key
18 ]);
19 var myReader = new Ext.data.JsonReader(
20     {                             // The metadata property, with configuration options:
21         totalProperty: "results", //   the property which contains the total dataset size (optional)
22         root: "rows",             //   the property which contains an Array of record data objects
23         idProperty: "id"          //   the property within each row object that provides an ID for the record (optional)
24     },
25     Employee  // {@link Ext.data.Record} constructor that provides mapping for JSON object
26 );
27 </code></pre>
28  * <p>This would consume a JSON data object of the form:</p><pre><code>
29 {
30     results: 2,  // Reader's configured totalProperty
31     rows: [      // Reader's configured root
32         { id: 1, firstname: 'Bill', occupation: 'Gardener' },         // a row object
33         { id: 2, firstname: 'Ben' , occupation: 'Horticulturalist' }  // another row object
34     ]
35 }
36 </code></pre>
37  * <p><b><u>Automatic configuration using metaData</u></b></p>
38  * <p>It is possible to change a JsonReader's metadata at any time by including a <b><tt>metaData</tt></b>
39  * property in the JSON data object. If the JSON data object has a <b><tt>metaData</tt></b> property, a
40  * {@link Ext.data.Store Store} object using this Reader will reconfigure itself to use the newly provided
41  * field definition and fire its {@link Ext.data.Store#metachange metachange} event. The metachange event
42  * handler may interrogate the <b><tt>metaData</tt></b> property to perform any configuration required.
43  * Note that reconfiguring a Store potentially invalidates objects which may refer to Fields or Records
44  * which no longer exist.</p>
45  * <p>The <b><tt>metaData</tt></b> property in the JSON data object may contain:</p>
46  * <div class="mdetail-params"><ul>
47  * <li>any of the configuration options for this class</li>
48  * <li>a <b><tt>{@link Ext.data.Record#fields fields}</tt></b> property which the JsonReader will
49  * use as an argument to the {@link Ext.data.Record#create data Record create method} in order to
50  * configure the layout of the Records it will produce.</li>
51  * <li>a <b><tt>{@link Ext.data.Store#sortInfo sortInfo}</tt></b> property which the JsonReader will
52  * use to set the {@link Ext.data.Store}'s {@link Ext.data.Store#sortInfo sortInfo} property</li>
53  * <li>any user-defined properties needed</li>
54  * </ul></div>
55  * <p>To use this facility to send the same data as the example above (without having to code the creation
56  * of the Record constructor), you would create the JsonReader like this:</p><pre><code>
57 var myReader = new Ext.data.JsonReader();
58 </code></pre>
59  * <p>The first data packet from the server would configure the reader by containing a
60  * <b><tt>metaData</tt></b> property <b>and</b> the data. For example, the JSON data object might take
61  * the form:</p>
62 <pre><code>
63 {
64     metaData: {
65         idProperty: 'id',
66         root: 'rows',
67         totalProperty: 'results',
68         fields: [
69             {name: 'name'},
70             {name: 'job', mapping: 'occupation'}
71         ],
72         sortInfo: {field: 'name', direction:'ASC'}, // used by store to set its sortInfo
73         foo: 'bar' // custom property
74     },
75     results: 2,
76     rows: [ // an Array
77         { 'id': 1, 'name': 'Bill', occupation: 'Gardener' },
78         { 'id': 2, 'name': 'Ben', occupation: 'Horticulturalist' }
79     ]
80 }
81 </code></pre>
82  * @cfg {String} totalProperty [total] Name of the property from which to retrieve the total number of records
83  * in the dataset. This is only needed if the whole dataset is not passed in one go, but is being
84  * paged from the remote server.  Defaults to <tt>total</tt>.
85  * @cfg {String} successProperty [success] Name of the property from which to
86  * retrieve the success attribute. Defaults to <tt>success</tt>.  See
87  * {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
88  * for additional information.
89  * @cfg {String} root [undefined] <b>Required</b>.  The name of the property
90  * which contains the Array of row objects.  Defaults to <tt>undefined</tt>.
91  * An exception will be thrown if the root property is undefined. The data packet
92  * value for this property should be an empty array to clear the data or show
93  * no data.
94  * @cfg {String} idProperty [id] Name of the property within a row object that contains a record identifier value.  Defaults to <tt>id</tt>
95  * @constructor
96  * Create a new JsonReader
97  * @param {Object} meta Metadata configuration options.
98  * @param {Array/Object} recordType
99  * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
100  * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
101  * constructor created from {@link Ext.data.Record#create}.</p>
102  */
103 Ext.data.JsonReader = function(meta, recordType){
104     meta = meta || {};
105
106     // default idProperty, successProperty & totalProperty -> "id", "success", "total"
107     Ext.applyIf(meta, {
108         idProperty: 'id',
109         successProperty: 'success',
110         totalProperty: 'total'
111     });
112
113     Ext.data.JsonReader.superclass.constructor.call(this, meta, recordType || meta.fields);
114 };
115 Ext.extend(Ext.data.JsonReader, Ext.data.DataReader, {
116     /**
117      * This JsonReader's metadata as passed to the constructor, or as passed in
118      * the last data packet's <b><tt>metaData</tt></b> property.
119      * @type Mixed
120      * @property meta
121      */
122     /**
123      * This method is only used by a DataProxy which has retrieved data from a remote server.
124      * @param {Object} response The XHR object which contains the JSON data in its responseText.
125      * @return {Object} data A data block which is used by an Ext.data.Store object as
126      * a cache of Ext.data.Records.
127      */
128     read : function(response){
129         var json = response.responseText;
130         var o = Ext.decode(json);
131         if(!o) {
132             throw {message: "JsonReader.read: Json object not found"};
133         }
134         return this.readRecords(o);
135     },
136
137     // private function a store will implement
138     onMetaChange : function(meta, recordType, o){
139
140     },
141
142     /**
143      * @ignore
144      */
145     simpleAccess: function(obj, subsc) {
146         return obj[subsc];
147     },
148
149     /**
150      * @ignore
151      */
152     getJsonAccessor: function(){
153         var re = /[\[\.]/;
154         return function(expr) {
155             try {
156                 return(re.test(expr)) ?
157                 new Function("obj", "return obj." + expr) :
158                 function(obj){
159                     return obj[expr];
160                 };
161             } catch(e){}
162             return Ext.emptyFn;
163         };
164     }(),
165
166     /**
167      * Create a data block containing Ext.data.Records from a JSON object.
168      * @param {Object} o An object which contains an Array of row objects in the property specified
169      * in the config as 'root, and optionally a property, specified in the config as 'totalProperty'
170      * which contains the total size of the dataset.
171      * @return {Object} data A data block which is used by an Ext.data.Store object as
172      * a cache of Ext.data.Records.
173      */
174     readRecords : function(o){
175         /**
176          * After any data loads, the raw JSON data is available for further custom processing.  If no data is
177          * loaded or there is a load exception this property will be undefined.
178          * @type Object
179          */
180         this.jsonData = o;
181         if(o.metaData){
182             delete this.ef;
183             this.meta = o.metaData;
184             this.recordType = Ext.data.Record.create(o.metaData.fields);
185             this.onMetaChange(this.meta, this.recordType, o);
186         }
187         var s = this.meta, Record = this.recordType,
188             f = Record.prototype.fields, fi = f.items, fl = f.length, v;
189
190         var root = this.getRoot(o), c = root.length, totalRecords = c, success = true;
191         if(s.totalProperty){
192             v = parseInt(this.getTotal(o), 10);
193             if(!isNaN(v)){
194                 totalRecords = v;
195             }
196         }
197         if(s.successProperty){
198             v = this.getSuccess(o);
199             if(v === false || v === 'false'){
200                 success = false;
201             }
202         }
203
204         var records = [];
205         for(var i = 0; i < c; i++){
206             var n = root[i];
207             var record = new Record(this.extractValues(n, fi, fl), this.getId(n));
208             record.json = n;
209             records[i] = record;
210         }
211         return {
212             success : success,
213             records : records,
214             totalRecords : totalRecords
215         };
216     },
217
218     // private
219     buildExtractors : function() {
220         if(this.ef){
221             return;
222         }
223         var s = this.meta, Record = this.recordType,
224             f = Record.prototype.fields, fi = f.items, fl = f.length;
225
226         if(s.totalProperty) {
227             this.getTotal = this.getJsonAccessor(s.totalProperty);
228         }
229         if(s.successProperty) {
230             this.getSuccess = this.getJsonAccessor(s.successProperty);
231         }
232         this.getRoot = s.root ? this.getJsonAccessor(s.root) : function(p){return p;};
233         if (s.id || s.idProperty) {
234             var g = this.getJsonAccessor(s.id || s.idProperty);
235             this.getId = function(rec) {
236                 var r = g(rec);
237                 return (r === undefined || r === "") ? null : r;
238             };
239         } else {
240             this.getId = function(){return null;};
241         }
242         var ef = [];
243         for(var i = 0; i < fl; i++){
244             f = fi[i];
245             var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name;
246             ef.push(this.getJsonAccessor(map));
247         }
248         this.ef = ef;
249     },
250
251     // private extractValues
252     extractValues: function(data, items, len) {
253         var f, values = {};
254         for(var j = 0; j < len; j++){
255             f = items[j];
256             var v = this.ef[j](data);
257             values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, data);
258         }
259         return values;
260     },
261
262     /**
263      * Decode a json response from server.
264      * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
265      * @param {Object} response
266      */
267     readResponse : function(action, response) {
268         var o = (response.responseText !== undefined) ? Ext.decode(response.responseText) : response;
269         if(!o) {
270             throw new Ext.data.JsonReader.Error('response');
271         }
272         if (Ext.isEmpty(o[this.meta.successProperty])) {
273             throw new Ext.data.JsonReader.Error('successProperty-response', this.meta.successProperty);
274         }
275         // TODO, separate empty and undefined exceptions.
276         if (action === Ext.data.Api.actions.create) {
277             if (Ext.isEmpty(o[this.meta.root])) {
278                 throw new Ext.data.JsonReader.Error('root-emtpy', this.meta.root);
279             }
280             else if (o[this.meta.root] === undefined) {
281                 throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root);
282             }
283         }
284
285         return o;
286     }
287 });
288
289 /**
290  * @class Ext.data.JsonReader.Error
291  * Error class for JsonReader
292  */
293 Ext.data.JsonReader.Error = Ext.extend(Ext.Error, {
294     constructor : function(message, arg) {
295         this.arg = arg;
296         Ext.Error.call(this, message);
297     },
298     name : 'Ext.data.JsonReader'
299 });
300 Ext.apply(Ext.data.JsonReader.Error.prototype, {
301     lang: {
302         'response': "An error occurred while json-decoding your server response",
303         '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.',
304         '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.',
305         '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.',
306         '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.',
307         '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.'
308     }
309 });
310 </pre>    \r
311 </body>\r
312 </html>