commit extjs-2.2.1
[extjs.git] / source / data / JsonReader.js
1 /*\r
2  * Ext JS Library 2.2.1\r
3  * Copyright(c) 2006-2009, Ext JS, LLC.\r
4  * licensing@extjs.com\r
5  * \r
6  * http://extjs.com/license\r
7  */\r
8 \r
9 /**\r
10  * @class Ext.data.JsonReader\r
11  * @extends Ext.data.DataReader\r
12  * Data reader class to create an Array of {@link Ext.data.Record} objects from a JSON response\r
13  * based on mappings in a provided {@link Ext.data.Record} constructor.<br>\r
14  * <p>\r
15  * Example code:\r
16  * <pre><code>\r
17 var Employee = Ext.data.Record.create([\r
18     {name: 'firstname'},                  // Map the Record's "firstname" field to the row object's key of the same name\r
19     {name: 'job', mapping: 'occupation'}  // Map the "job" field to the row object's "occupation" key\r
20 ]);\r
21 var myReader = new Ext.data.JsonReader({\r
22     totalProperty: "results",             // The property which contains the total dataset size (optional)\r
23     root: "rows",                         // The property which contains an Array of row objects\r
24     id: "id"                              // The property within each row object that provides an ID for the record (optional)\r
25 }, Employee);\r
26 </code></pre>\r
27  * <p>\r
28  * This would consume a JSON object of the form:\r
29  * <pre><code>\r
30 {\r
31     results: 2,\r
32     rows: [\r
33         { id: 1, firstname: 'Bill', occupation: 'Gardener' },         // a row object\r
34         { id: 2, firstname: 'Ben' , occupation: 'Horticulturalist' }  // another row object\r
35     ]\r
36 }\r
37 </code></pre>\r
38  * <p>It is possible to change a JsonReader's metadata at any time by including a\r
39  * <b><tt>metaData</tt></b> property in the data object. If this is detected in the\r
40  * object, a {@link Ext.data.Store Store} object using this Reader will reconfigure\r
41  * itself to use the newly provided field definition and fire its\r
42  * {@link Ext.data.Store#metachange metachange} event. In\r
43  * undergoing this change, the Store sets its {@link Ext.data.Store#sortInfo sortInfo} property\r
44  * from the <tt>sortInfo</tt> property in the new metadata. Note that reconfiguring a Store\r
45  * potentially invalidates objects which may refer to Fields or Records which no longer exist.</p>\r
46  *\r
47  * <p>The <b><tt>metaData</tt></b> property may contain any of the configuration\r
48  * options for this class. Additionally, it may contain a <b><tt>fields</tt></b>\r
49  * property which the JsonReader will use as an argument to {@link Ext.data.Record#create}\r
50  * to configure the layout of the Records which it will produce.<p>\r
51  * Using the <b><tt>metaData</tt></b> property, and the Store's {@link Ext.data.Store#metachange metachange} event,\r
52  * it is possible to have a Store-driven control initialize itself. The metachange\r
53  * event handler may interrogate the <b><tt>metaData</tt></b> property (which\r
54  * may contain any user-defined properties needed) and the <b><tt>metaData.fields</tt></b>\r
55  * property to perform any configuration required.</p>\r
56  *\r
57  * <p>To use this facility to send the same data as the above example without\r
58  * having to code the creation of the Record constructor, you would create the\r
59  * JsonReader like this:</p><pre><code>\r
60 var myReader = new Ext.data.JsonReader();\r
61 </code></pre>\r
62  * <p>The first data packet from the server would configure the reader by\r
63  * containing a metaData property as well as the data:</p><pre><code>\r
64 {\r
65     metaData: {\r
66         totalProperty: 'results',\r
67         root: 'rows',\r
68         id: 'id',\r
69         fields: [\r
70             {name: 'name'},\r
71             {name: 'occupation'}\r
72         ]\r
73     },\r
74     results: 2,\r
75     rows: [\r
76         { 'id': 1, 'name': 'Bill', occupation: 'Gardener' },\r
77         { 'id': 2, 'name': 'Ben', occupation: 'Horticulturalist' }\r
78     ]\r
79 }\r
80 </code></pre>\r
81  * @cfg {String} totalProperty Name of the property from which to retrieve the total number of records\r
82  * in the dataset. This is only needed if the whole dataset is not passed in one go, but is being\r
83  * paged from the remote server.\r
84  * @cfg {String} successProperty Name of the property from which to retrieve the success attribute used by forms.\r
85  * @cfg {String} root name of the property which contains the Array of row objects.\r
86  * @cfg {String} id Name of the property within a row object that contains a record identifier value.\r
87  * @constructor\r
88  * Create a new JsonReader\r
89  * @param {Object} meta Metadata configuration options.\r
90  * @param {Object} recordType Either an Array of field definition objects as passed to\r
91  * {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record} constructor created using {@link Ext.data.Record#create}.\r
92  */\r
93 Ext.data.JsonReader = function(meta, recordType){\r
94     meta = meta || {};\r
95     Ext.data.JsonReader.superclass.constructor.call(this, meta, recordType || meta.fields);\r
96 };\r
97 Ext.extend(Ext.data.JsonReader, Ext.data.DataReader, {\r
98     /**\r
99      * This JsonReader's metadata as passed to the constructor, or as passed in\r
100      * the last data packet's <b><tt>metaData</tt></b> property.\r
101      * @type Mixed\r
102      * @property meta\r
103      */\r
104     /**\r
105      * This method is only used by a DataProxy which has retrieved data from a remote server.\r
106      * @param {Object} response The XHR object which contains the JSON data in its responseText.\r
107      * @return {Object} data A data block which is used by an Ext.data.Store object as\r
108      * a cache of Ext.data.Records.\r
109      */\r
110     read : function(response){\r
111         var json = response.responseText;\r
112         var o = eval("("+json+")");\r
113         if(!o) {\r
114             throw {message: "JsonReader.read: Json object not found"};\r
115         }\r
116         return this.readRecords(o);\r
117     },\r
118 \r
119     // private function a store will implement\r
120     onMetaChange : function(meta, recordType, o){\r
121 \r
122     },\r
123 \r
124     /**\r
125          * @ignore\r
126          */\r
127     simpleAccess: function(obj, subsc) {\r
128         return obj[subsc];\r
129     },\r
130 \r
131         /**\r
132          * @ignore\r
133          */\r
134     getJsonAccessor: function(){\r
135         var re = /[\[\.]/;\r
136         return function(expr) {\r
137             try {\r
138                 return(re.test(expr))\r
139                     ? new Function("obj", "return obj." + expr)\r
140                     : function(obj){\r
141                         return obj[expr];\r
142                     };\r
143             } catch(e){}\r
144             return Ext.emptyFn;\r
145         };\r
146     }(),\r
147 \r
148     /**\r
149      * Create a data block containing Ext.data.Records from a JSON object.\r
150      * @param {Object} o An object which contains an Array of row objects in the property specified\r
151      * in the config as 'root, and optionally a property, specified in the config as 'totalProperty'\r
152      * which contains the total size of the dataset.\r
153      * @return {Object} data A data block which is used by an Ext.data.Store object as\r
154      * a cache of Ext.data.Records.\r
155      */\r
156     readRecords : function(o){\r
157         /**\r
158          * After any data loads, the raw JSON data is available for further custom processing.  If no data is\r
159          * loaded or there is a load exception this property will be undefined.\r
160          * @type Object\r
161          */\r
162         this.jsonData = o;\r
163         if(o.metaData){\r
164             delete this.ef;\r
165             this.meta = o.metaData;\r
166             this.recordType = Ext.data.Record.create(o.metaData.fields);\r
167             this.onMetaChange(this.meta, this.recordType, o);\r
168         }\r
169         var s = this.meta, Record = this.recordType,\r
170             f = Record.prototype.fields, fi = f.items, fl = f.length;\r
171 \r
172 //      Generate extraction functions for the totalProperty, the root, the id, and for each field\r
173         if (!this.ef) {\r
174             if(s.totalProperty) {\r
175                     this.getTotal = this.getJsonAccessor(s.totalProperty);\r
176                 }\r
177                 if(s.successProperty) {\r
178                     this.getSuccess = this.getJsonAccessor(s.successProperty);\r
179                 }\r
180                 this.getRoot = s.root ? this.getJsonAccessor(s.root) : function(p){return p;};\r
181                 if (s.id) {\r
182                         var g = this.getJsonAccessor(s.id);\r
183                         this.getId = function(rec) {\r
184                                 var r = g(rec);\r
185                                 return (r === undefined || r === "") ? null : r;\r
186                         };\r
187                 } else {\r
188                         this.getId = function(){return null;};\r
189                 }\r
190             this.ef = [];\r
191             for(var i = 0; i < fl; i++){\r
192                 f = fi[i];\r
193                 var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name;\r
194                 this.ef[i] = this.getJsonAccessor(map);\r
195             }\r
196         }\r
197 \r
198         var root = this.getRoot(o), c = root.length, totalRecords = c, success = true;\r
199         if(s.totalProperty){\r
200             var v = parseInt(this.getTotal(o), 10);\r
201             if(!isNaN(v)){\r
202                 totalRecords = v;\r
203             }\r
204         }\r
205         if(s.successProperty){\r
206             var v = this.getSuccess(o);\r
207             if(v === false || v === 'false'){\r
208                 success = false;\r
209             }\r
210         }\r
211         var records = [];\r
212             for(var i = 0; i < c; i++){\r
213                     var n = root[i];\r
214                 var values = {};\r
215                 var id = this.getId(n);\r
216                 for(var j = 0; j < fl; j++){\r
217                     f = fi[j];\r
218                 var v = this.ef[j](n);\r
219                 values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, n);\r
220                 }\r
221                 var record = new Record(values, id);\r
222                 record.json = n;\r
223                 records[i] = record;\r
224             }\r
225             return {\r
226                 success : success,\r
227                 records : records,\r
228                 totalRecords : totalRecords\r
229             };\r
230     }\r
231 });