/** * This class is used to write {@link Ext.data.Model} data to the server in a JSON format. * The {@link #allowSingle} configuration can be set to false to force the records to always * be encoded in an array, even if there is only a single record being sent. */Ext.define('Ext.data.writer.Json', { extend: 'Ext.data.writer.Writer', alternateClassName: 'Ext.data.JsonWriter', alias: 'writer.json', config: { /** * @cfg {String} rootProperty The HTTP parameter name by which JSON encoded records will be passed to the server if the * {@link #encode} option is `true`. */ rootProperty: undefined, /** * @cfg {Boolean} [encode=false] Configure `true` to send record data (all record fields if {@link #writeAllFields} is `true`) * as a JSON encoded HTTP parameter named by the {@link #rootProperty} configuration. * * The encode option should only be set to true when a {@link #rootProperty} is defined, because the values will be * sent as part of the request parameters as opposed to a raw post. The root will be the name of the parameter * sent to the server. */ encode: false, /** * @cfg {Boolean} [allowSingle=true] Configure with `false` to ensure that records are always wrapped in an array, even if there is only * one record being sent. When there is more than one record, they will always be encoded into an array. */ allowSingle: true, /** * @cfg {Boolean} [expandData=false] By default, when dot-delimited field {@link #nameProperty mappings} are * used (e.g. `name: 'myProperty', mapping: 'my.nested.property'`) the writer will simply output a flat data * object containing the mapping string literal as the property name (e.g. `{ 'my.nested.property': 'foo' }`). * * Mappings are used to map incoming nested JSON to flat Ext models. In many case, the data output by the * writer should preferrably match the original nested data format. Setting this config to `true` will ensure * that the output will instead look like `{ my: { nested: { property: 'foo' }}}`. The output is generated * by {@link #getExpandedData}, which can optionally be overridden to apply more customized logic. */ expandData: false }, //<debug> constructor: function(config) { if (config && config.hasOwnProperty('root')) { config = Ext.apply({}, config); config.rootProperty = config.root; delete config.root; Ext.log.warn('Ext.data.writer.Json: Using the deprecated "root" configuration. Use "rootProperty" instead.'); } this.callParent([config]); }, //</debug> /** * @protected * The Reader classes support dot-delimited data mappings for extracting nested raw data into fields, so the * writer must support converting the flat {@link Ext.data.Model} structure back into the original nested data * format. Using the same mappings when available, the Writer will simply split each delimiter into a nested * object in the output, which should exactly match the input format. For example, record data like this: * * my.nested.property: 'foo', * my.nested.another: 'bar', * my.somethingElse: 123 * * should write out as... * * my: { * nested: { * property: 'foo', * another: 'bar * }, * somethingElse: 123 * } * * This behavior is governed by the {@link #expandData} config. By default, this option is `false` for * compatibility reasons, and will output a flat structure matching the flat record format. Setting this config * to `true` will enable the expanded mapping behavior as shown here. This method could also be overridden * to provide an even more customized output data structure. */ getExpandedData: function(data) { var dataLength = data.length, i = 0, item, prop, nameParts, j, tempObj, toObject = function(name, value) { var o = {}; o[name] = value; return o; }; for (; i < dataLength; i++) { item = data[i]; for (prop in item) { if (item.hasOwnProperty(prop)) { // e.g. my.nested.property: 'foo' nameParts = prop.split('.'); j = nameParts.length - 1; if (j > 0) { // Initially this will be the value 'foo'. // Equivalent to rec['my.nested.property'] tempObj = item[prop]; for (; j > 0; j--) { // Starting with the value above, we loop inside out, assigning the // current object as the value for the parent name. Work all // the way up until only the root name is left to assign. tempObj = toObject(nameParts[j], tempObj); } // At this point we'll have all child properties rolled up into a single // object like `{ nested: { property: 'foo' }}`. Now add the root name // (e.g. 'my') to the record data if needed (do not overwrite existing): item[nameParts[0]] = item[nameParts[0]] || {}; // Since there could be duplicate names at any level of the nesting be sure // to merge rather than assign when setting the object as the value: Ext.Object.merge(item[nameParts[0]], tempObj); // Finally delete the original mapped property from the record delete item[prop]; } } } } return data; }, writeRecords: function(request, data) { var me = this, root = me.getRootProperty(), json, single, transform; if (me.getExpandData()) { data = me.getExpandedData(data); } if (me.getAllowSingle() && data.length === 1) { // convert to single object format data = data[0]; single = true; } transform = this.getTransform(); if (transform) { data = transform(data, request); } if (me.getEncode()) { if (root) { // sending as a param, need to encode request.setParam(root, Ext.encode(data)); } else { //<debug> Ext.raise('Must specify a root when using encode'); //</debug> } } else if (single || (data && data.length)) { // send as jsonData json = request.getJsonData() || {}; if (root) { json[root] = data; } else { json = data; } request.setJsonData(json); } return request; }});