]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/data/reader/Json.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / data / reader / Json.js
CommitLineData
6527f429
DM
1/**\r
2 * The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually\r
3 * happens as a result of loading a Store - for example we might create something like this:\r
4 *\r
5 * Ext.define('User', {\r
6 * extend: 'Ext.data.Model',\r
7 * fields: ['id', 'name', 'email']\r
8 * });\r
9 *\r
10 * var store = Ext.create('Ext.data.Store', {\r
11 * model: 'User',\r
12 * proxy: {\r
13 * type: 'ajax',\r
14 * url : 'users.json',\r
15 * reader: {\r
16 * type: 'json'\r
17 * }\r
18 * }\r
19 * });\r
20 *\r
21 * The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're\r
22 * not already familiar with them.\r
23 *\r
24 * We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s\r
25 * {@link Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the\r
26 * Store, so it is as if we passed this instead:\r
27 *\r
28 * reader: {\r
29 * type : 'json',\r
30 * model: 'User'\r
31 * }\r
32 *\r
33 * The reader we set up is ready to read data from our server - at the moment it will accept a response like this:\r
34 *\r
35 * [\r
36 * {\r
37 * "id": 1,\r
38 * "name": "Ed Spencer",\r
39 * "email": "ed@sencha.com"\r
40 * },\r
41 * {\r
42 * "id": 2,\r
43 * "name": "Abe Elias",\r
44 * "email": "abe@sencha.com"\r
45 * }\r
46 * ]\r
47 *\r
48 * ## Reading other JSON formats\r
49 *\r
50 * If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually\r
51 * pass JsonReader a couple of configuration options to make it parse your format. For example, we can use the\r
52 * {@link #cfg-rootProperty} configuration to parse data that comes back like this:\r
53 *\r
54 * {\r
55 * "users": [\r
56 * {\r
57 * "id": 1,\r
58 * "name": "Ed Spencer",\r
59 * "email": "ed@sencha.com"\r
60 * },\r
61 * {\r
62 * "id": 2,\r
63 * "name": "Abe Elias",\r
64 * "email": "abe@sencha.com"\r
65 * }\r
66 * ]\r
67 * }\r
68 *\r
69 * To parse this we just pass in a {@link #rootProperty} configuration that matches the 'users' above:\r
70 *\r
71 * reader: {\r
72 * type: 'json',\r
73 * rootProperty: 'users'\r
74 * }\r
75 *\r
76 * Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata\r
77 * around each record inside a nested structure like this:\r
78 *\r
79 * {\r
80 * "total": 122,\r
81 * "offset": 0,\r
82 * "users": [\r
83 * {\r
84 * "id": "ed-spencer-1",\r
85 * "value": 1,\r
86 * "user": {\r
87 * "id": 1,\r
88 * "name": "Ed Spencer",\r
89 * "email": "ed@sencha.com"\r
90 * }\r
91 * }\r
92 * ]\r
93 * }\r
94 *\r
95 * In the case above the record data is nested an additional level inside the "users" array as each "user" item has\r
96 * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the\r
97 * JSON above we need to specify the {@link #record} configuration like this:\r
98 *\r
99 * reader: {\r
100 * type : 'json',\r
101 * rootProperty : 'users',\r
102 * record: 'user'\r
103 * }\r
104 *\r
105 * ## Response MetaData\r
106 *\r
107 * The server can return metadata in its response, in addition to the record data, that describe attributes\r
108 * of the data set itself or are used to reconfigure the Reader. To pass metadata in the response you simply\r
109 * add a `metaData` attribute to the root of the response data. The metaData attribute can contain anything,\r
110 * but supports a specific set of properties that are handled by the Reader if they are present:\r
111 * \r
112 * - {@link #rootProperty}: the property name of the root response node containing the record data\r
113 * - {@link #totalProperty}: property name for the total number of records in the data\r
114 * - {@link #successProperty}: property name for the success status of the response\r
115 * - {@link #messageProperty}: property name for an optional response message\r
116 * - {@link Ext.data.Model#cfg-fields fields}: Config used to reconfigure the Model's fields before converting the\r
117 * response data into records\r
118 * \r
119 * An initial Reader configuration containing all of these properties might look like this ("fields" would be\r
120 * included in the Model definition, not shown):\r
121 *\r
122 * reader: {\r
123 * type : 'json',\r
124 * rootProperty : 'root',\r
125 * totalProperty : 'total',\r
126 * successProperty: 'success',\r
127 * messageProperty: 'message'\r
128 * }\r
129 *\r
130 * If you were to pass a response object containing attributes different from those initially defined above, you could\r
131 * use the `metaData` attribute to reconfigure the Reader on the fly. For example:\r
132 *\r
133 * {\r
134 * "count": 1,\r
135 * "ok": true,\r
136 * "msg": "Users found",\r
137 * "users": [{\r
138 * "userId": 123,\r
139 * "name": "Ed Spencer",\r
140 * "email": "ed@sencha.com"\r
141 * }],\r
142 * "metaData": {\r
143 * "rootProperty": "users",\r
144 * "totalProperty": 'count',\r
145 * "successProperty": 'ok',\r
146 * "messageProperty": 'msg'\r
147 * }\r
148 * }\r
149 *\r
150 * You can also place any other arbitrary data you need into the `metaData` attribute which will be ignored by the Reader,\r
151 * but will be accessible via the Reader's {@link #metaData} property (which is also passed to listeners via the Proxy's\r
152 * {@link Ext.data.proxy.Proxy#metachange metachange} event (also relayed by the store). Application code can then\r
153 * process the passed metadata in any way it chooses.\r
154 * \r
155 * A simple example for how this can be used would be customizing the fields for a Model that is bound to a grid. By passing\r
156 * the `fields` property the Model will be automatically updated by the Reader internally, but that change will not be\r
157 * reflected automatically in the grid unless you also update the column configuration. You could do this manually, or you\r
158 * could simply pass a standard grid {@link Ext.panel.Table#columns column} config object as part of the `metaData` attribute\r
159 * and then pass that along to the grid. Here's a very simple example for how that could be accomplished:\r
160 *\r
161 * // response format:\r
162 * {\r
163 * ...\r
164 * "metaData": {\r
165 * "fields": [\r
166 * { "name": "userId", "type": "int" },\r
167 * { "name": "name", "type": "string" },\r
168 * { "name": "birthday", "type": "date", "dateFormat": "Y-j-m" },\r
169 * ],\r
170 * "columns": [\r
171 * { "text": "User ID", "dataIndex": "userId", "width": 40 },\r
172 * { "text": "User Name", "dataIndex": "name", "flex": 1 },\r
173 * { "text": "Birthday", "dataIndex": "birthday", "flex": 1, "format": 'Y-j-m', "xtype": "datecolumn" }\r
174 * ]\r
175 * }\r
176 * }\r
177 *\r
178 * The Reader will automatically read the meta fields config and rebuild the Model based on the new fields, but to handle\r
179 * the new column configuration you would need to handle the metadata within the application code. This is done simply enough\r
180 * by handling the metachange event on either the store or the proxy, e.g.:\r
181 *\r
182 * var store = Ext.create('Ext.data.Store', {\r
183 * ...\r
184 * listeners: {\r
185 * 'metachange': function(store, meta) {\r
186 * myGrid.reconfigure(store, meta.columns);\r
187 * }\r
188 * }\r
189 * });\r
190 *\r
191 */\r
192Ext.define('Ext.data.reader.Json', {\r
193 extend: 'Ext.data.reader.Reader',\r
194 \r
195 requires: [\r
196 'Ext.JSON'\r
197 ],\r
198 \r
199 alternateClassName: 'Ext.data.JsonReader',\r
200 alias : 'reader.json',\r
201\r
202 config: {\r
203 /**\r
204 * @cfg {String} record The optional location within the JSON response that the record data itself can be found at.\r
205 * See the JsonReader intro docs for more details. This is not often needed.\r
206 */\r
207 record: null,\r
208 \r
209 /**\r
210 * @cfg {String} [metaProperty]\r
211 * Name of the property from which to retrieve the `metaData` attribute. See {@link #metaData}.\r
212 */\r
213 metaProperty: 'metaData',\r
214\r
215 /**\r
216 * @cfg {Boolean} useSimpleAccessors True to ensure that field names/mappings are treated as literals when\r
217 * reading values.\r
218 *\r
219 * For example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a property bar\r
220 * from foo, then a property baz from bar. Setting the simple accessors to true will read the property with the name\r
221 * "foo.bar.baz" direct from the root object.\r
222 */\r
223 useSimpleAccessors: false,\r
224 \r
225 /**\r
226 * @cfg {Boolean} preserveRawData\r
227 * The reader will keep a copy of the most recent request in the {@link #rawData} property. For performance reasons,\r
228 * the data object for each record is used directly as the model data. This means that these objects may be modified and\r
229 * thus modify the raw data. To ensure the objects are copied, set this option to `true`. NB: This only applies to items \r
230 * that are read as part of the data array, any other metadata will not be modified:\r
231 * \r
232 * {\r
233 * "someOtherData": 1, // Won't be modified\r
234 * "root": [{}, {}, {}] // The objects here will be modified\r
235 * }\r
236 */\r
237 preserveRawData: false\r
238 },\r
239 \r
240 updateRootProperty: function() {\r
241 this.forceBuildExtractors(); \r
242 },\r
243 \r
244 updateMetaProperty: function() {\r
245 this.forceBuildExtractors();\r
246 },\r
247\r
248 /**\r
249 * Reads a JSON object and returns a ResultSet. Uses the internal getTotal and getSuccess extractors to\r
250 * retrieve meta data from the response, and extractData to turn the JSON data into model instances.\r
251 * @param {Object} data The raw JSON data\r
252 * @param {Object} [readOptions] See {@link #read} for details.\r
253 * @return {Ext.data.ResultSet} A ResultSet containing model instances and meta data about the results\r
254 */\r
255 readRecords: function(data, readOptions, /* private */ internalReadOptions) {\r
256 var me = this,\r
257 meta;\r
258 \r
259 //this has to be before the call to super because we use the meta data in the superclass readRecords\r
260 if (me.getMeta) {\r
261 meta = me.getMeta(data);\r
262 if (meta) {\r
263 me.onMetaChange(meta);\r
264 }\r
265 } else if (data.metaData) {\r
266 me.onMetaChange(data.metaData);\r
267 }\r
268\r
269 return me.callParent([data, readOptions, internalReadOptions]);\r
270 },\r
271\r
272 getResponseData: function(response) {\r
273 var error;\r
274\r
275 try {\r
276 return Ext.decode(response.responseText);\r
277 } catch (ex) {\r
278 error = this.createReadError(ex.message);\r
279\r
280 Ext.Logger.warn('Unable to parse the JSON returned by the server');\r
281 this.fireEvent('exception', this, response, error);\r
282 return error;\r
283 }\r
284 },\r
285\r
286 buildExtractors : function() {\r
287 var me = this,\r
288 metaProp, rootProp;\r
289\r
290 // Will only return true if we need to build\r
291 if (me.callParent(arguments)) {\r
292 metaProp = me.getMetaProperty();\r
293 rootProp = me.getRootProperty();\r
294 if (rootProp) {\r
295 me.getRoot = me.getAccessor(rootProp);\r
296 } else {\r
297 me.getRoot = Ext.identityFn;\r
298 }\r
299 \r
300 if (metaProp) {\r
301 me.getMeta = me.getAccessor(metaProp);\r
302 }\r
303 }\r
304 },\r
305\r
306 /**\r
307 * @private\r
308 * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}\r
309 * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing\r
310 * @param {Object} root The JSON root node\r
311 * @param {Object} [readOptions] See {@link #read} for details.\r
312 * @return {Ext.data.Model[]} The records\r
313 */\r
314 extractData: function(root, readOptions) {\r
315 var recordName = this.getRecord(),\r
316 data = [],\r
317 length, i;\r
318\r
319 if (recordName) {\r
320 length = root.length;\r
321 \r
322 if (!length && Ext.isObject(root)) {\r
323 length = 1;\r
324 root = [root];\r
325 }\r
326\r
327 for (i = 0; i < length; i++) {\r
328 data[i] = root[i][recordName];\r
329 }\r
330 } else {\r
331 data = root;\r
332 }\r
333 return this.callParent([data, readOptions]);\r
334 },\r
335 \r
336 getModelData: function(raw) {\r
337 return this.getPreserveRawData() ? Ext.apply({}, raw) : raw; \r
338 },\r
339\r
340 /**\r
341 * @private\r
342 * @method\r
343 * Returns an accessor function for the given property string. Gives support for properties such as the following:\r
344 *\r
345 * - 'someProperty'\r
346 * - 'some.property'\r
347 * - '["someProperty"]'\r
348 * - 'values[0]'\r
349 * \r
350 * This is used by {@link #buildExtractors} to create optimized extractor functions for properties that are looked\r
351 * up directly on the source object (e.g. {@link #successProperty}, {@link #messageProperty}, etc.).\r
352 */\r
353 createAccessor: (function() {\r
354 var re = /[\[\.]/;\r
355\r
356 return function(expr) {\r
357 var me = this,\r
358 simple = me.getUseSimpleAccessors(),\r
359 operatorIndex, result,\r
360 current, parts, part, inExpr,\r
361 isDot, isLeft, isRight,\r
362 special, c, i, bracketed, len;\r
363\r
364 if (!(expr || expr === 0)) {\r
365 return;\r
366 }\r
367\r
368 if (typeof expr === 'function') {\r
369 return expr;\r
370 }\r
371 \r
372 if (!simple) {\r
373 operatorIndex = String(expr).search(re);\r
374 }\r
375 \r
376 if (simple === true || operatorIndex < 0) {\r
377 result = function(raw) {\r
378 return raw[expr];\r
379 };\r
380 } else {\r
381 // The purpose of this part is to generate a "safe" accessor for any complex \r
382 // json expression. For example 'foo.bar.baz' will get transformed:\r
383 // raw.foo && raw.foo.bar && raw.foo.bar.baz\r
384 current = 'raw';\r
385 parts = [];\r
386 part = '';\r
387 inExpr = 0;\r
388 len = expr.length;\r
389\r
390 // The <= is intentional here. We handle the last character\r
391 // being undefined so that we can append any final values at\r
392 // the end\r
393 for (i = 0; i <= len; ++i) {\r
394 c = expr[i];\r
395\r
396 isDot = c === '.';\r
397 isLeft = c === '[';\r
398 isRight = c === ']';\r
399\r
400 special = isDot || isLeft || isRight || !c;\r
401 // If either:\r
402 // a) Not a special char\r
403 // b) We're nested more than 1 deep, no single char can bring us out\r
404 // c) We are in an expr & it's not an ending brace\r
405 // Then just push the character on\r
406 if (!special || inExpr > 1 || (inExpr && !isRight)) {\r
407 part += c;\r
408 } else if (special) {\r
409 bracketed = false;\r
410 if (isLeft) {\r
411 ++inExpr;\r
412 } else if (isRight) {\r
413 --inExpr;\r
414 bracketed = true;\r
415 }\r
416\r
417 if (part) {\r
418 if (bracketed) {\r
419 part = '[' + part + ']';\r
420 } else {\r
421 part = '.' + part;\r
422 }\r
423 current += part;\r
424 // Concatting the empty string to the start fixes a very odd intermittent bug with IE9/10.\r
425 // On some occasions, without it, it will end up generating\r
426 // raw.foo.bar.baz && raw.foo.bar.baz && raw.foo.bar.baz\r
427 // At this point, not really sure why forcibly casting it to a string makes a difference\r
428 parts.push('' + current);\r
429 part = '';\r
430 }\r
431 }\r
432 }\r
433 result = parts.join(' && ');\r
434 result = Ext.functionFactory('raw', 'return ' + result);\r
435 }\r
436 return result;\r
437 };\r
438 }()),\r
439\r
440 /**\r
441 * @private\r
442 * @method\r
443 * Returns an accessor function for the passed Field. Gives support for properties such as the following:\r
444 * \r
445 * - 'someProperty'\r
446 * - 'some.property'\r
447 * - '["someProperty"]'\r
448 * - 'values[0]'\r
449 * \r
450 * This is used by {@link #buildExtractors} to create optimized extractor expressions when converting raw\r
451 * data into model instances. This method is used at the field level to dynamically map values to model fields.\r
452 */\r
453 createFieldAccessor: function(field) {\r
454 // Need to capture me for the extractor\r
455 var me = this,\r
456 mapping = field.mapping,\r
457 hasMap = mapping || mapping === 0,\r
458 map = hasMap ? mapping : field.name;\r
459 \r
460 if (hasMap) {\r
461 if (typeof map === 'function') {\r
462 return function(raw) {\r
463 return field.mapping(raw, me);\r
464 };\r
465 } else {\r
466 return me.createAccessor(map);\r
467 } \r
468 }\r
469 },\r
470\r
471 getAccessorKey: function(prop) {\r
472 var simple = this.getUseSimpleAccessors() ? 'simple' : '';\r
473 return this.$className + simple + prop;\r
474 },\r
475\r
476 privates: {\r
477 copyFrom: function(reader) {\r
478 this.callParent([reader]);\r
479 this.getRoot = reader.getRoot;\r
480 }\r
481 }\r
482});\r