]>
Commit | Line | Data |
---|---|---|
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 | |
192 | Ext.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 |