/** * Base Writer class used by most subclasses of {@link Ext.data.proxy.Server}. This class * is responsible for taking a set of {@link Ext.data.operation.Operation} objects and a * {@link Ext.data.Request} object and modifying that request based on the Operations. * * For example a Ext.data.writer.Json would format the Operations and their * {@link Ext.data.Model} instances based on the config options passed to the JsonWriter's * constructor. * * Writers are not needed for any kind of local storage - whether via a * {@link Ext.data.proxy.WebStorage Web Storage proxy} (see * {@link Ext.data.proxy.LocalStorage localStorage} and * {@link Ext.data.proxy.SessionStorage sessionStorage}) * or just in memory via a {@link Ext.data.proxy.Memory MemoryProxy}. * * # Dates * * Before sending dates to the server, they can be formatted using an {@link Ext.Date} * format. These formats can be specified both on the field and the writer itself. In terms * of precedence, from highest to lowest: * * - {@link #dateFormat Writer.dateFormat} The writer `dateFormat` will always have the * highest precedence. * - {@link Ext.data.field.Date#dateWriteFormat} The `dateWriteFormat` given to the field * instance. This is handled by {@link Ext.data.field.Date#method-serialize}. * - {@link Ext.data.field.Date#dateFormat Field.dateFormat} This is handled by the field's * `serialize` method. * - {@link Ext.data.field.Date#dateReadFormat Field.dateReadFormat} Also handled by the * field's `serialize` method. */Ext.define('Ext.data.writer.Writer', { mixins: [ 'Ext.mixin.Factoryable' ], alias: 'writer.base', factoryConfig: { defaultType: null }, alternateClassName: [ 'Ext.data.DataWriter', 'Ext.data.Writer' ], config: { /** * @cfg {String} clientIdProperty * When specified this property causes the `{@link Ext.data.Model#idProperty}` of * newly created records to be sent to the server as this name instead of the * value of the `idProperty`. * * For example, by default, the following code: * * Ext.define('Person', { * idProperty: 'id', // this is the default value (for clarity) * * fields: [ 'name' ] * }); * * var person = new Person({ * // no id provided, so one is generated * name: 'Clark Kent' * }); * * Will send this to the server: * * { * id: 'Person-1', * name: 'Clark Kent' * } * * This can be an issue if the server expects an integer for the "id" property. * You can use `{@link Ext.data.Model#identifier}` to produce identifiers that * the server would recognize or use this config to send the client's id in a * different property. * * Ext.define('Person', { * idProperty: 'id', // this is the default value (for clarity) * * proxy: { * writer: { * clientIdProperty: 'clientId' * } * }, * * fields: [ 'name' ] * }); * * Given the above, the server is sent this data now: * * { * clientId: 'Person-1', * name: 'Clark Kent' * } * * While this config provides the behavior of `{@link Ext.data.Model#clientIdProperty}` * from previous releases, this property is not as useful as a suitable * `{@link Ext.data.Model#identifier}` due to id's appearing in foreign-key fields * and in `{@link Ext.data.Model#manyToMany}` associations. * * See `{@link Ext.data.Model#identifier}` for more on id generation. */ clientIdProperty: null, /** * @cfg {Object} allDataOptions * This object contains the options passed to `{@link Ext.data.Model#getData}` when * writing `{@link Ext.data.Model#phantom}` records or when `writeAllFields` is set * to `true`. * * *NOTE:* The `serialize` option cannot be used here. */ allDataOptions: { persist: true }, /** * @cfg {Object} partialDataOptions * This object contains the options passed to `{@link Ext.data.Model#getData}` when * writing non `{@link Ext.data.Model#phantom}` records or when `writeAllFields` is * set to `false`. * * *NOTE:* The `serialize` option cannot be used here. */ partialDataOptions: { changes: true, critical: true }, /** * @cfg {Boolean} writeAllFields `true` to write all fields from the record to the * server. If set to `false` it will only send the fields that were modified. Note * that any fields that have `{@link Ext.data.field.Field#persist}` set to `false` * will still be ignored while those with `{@link Ext.data.field.Field#critical}` * set to `true` will be included. * * The exact set of fields written is determined by `allDataOptions` (when `true`) * or `partialDataOptions` (when `false`). This option is ignored and treated as * `true` when writing `{@link Ext.data.Model#phantom}` records. * * It is seldom a good idea to use this config. Rather use `allDataOptions` or * `partialDataOptions` to control what fields are sent for records based on their * `{@link Ext.data.Model#phantom}` state. * * In the previous release, this was default `true`. */ writeAllFields: false, /** * @cfg {String} dateFormat * This is used for each field of type date in the model to format the value before * it is sent to the server. */ dateFormat: null, /** * @cfg {String} nameProperty * This property is used to read the key for each value that will be sent to the * server. * * For example: * * Ext.define('Person', { * extend: 'Ext.data.Model', * fields: [{ * name: 'first', * mapping: 'firstName' * }, { * name: 'last', * mapping: 'lastName' * }, { * name: 'age' * }] * }); * * new Ext.data.writer.Writer({ * nameProperty: 'mapping' * }); * * // This will be sent to the server * { * firstName: 'first name value', * lastName: 'last name value', * age: 1 * } * * If the value is not present, the field name will always be used. */ nameProperty: 'name', /** * @cfg {Boolean} [writeRecordId] * By default, each record's id is always included in the output for non-phantom * records since in most cases the id will be required on the server to process * the record action. This is helpful since the id will normally not be modified, * and so would not be sent to the server unless {@link #writeAllFields} was * explicitly enabled. * * However, there are cases where it is not desirable for the record id to be passed * in the data directly. For example, when using a RESTful API the record id would * typically be appended to the url instead. */ writeRecordId: true, /** * @cfg {Function|Object} [transform] * If a transform function is set, it will be invoked just before {@link #writeRecords} * executes. It is passed the unserialized data object and the {@link Ext.data.Request request} * object. The transform function returns a data object, which can be a modified version of the original * data object, or a completely new data object. The transform can be a function, or an object * with a 'fn' key and an optional 'scope' key. Example usage: * * Ext.create('Ext.data.Store', { * model: 'User', * proxy: { * type: 'ajax', * url : 'users.json', * writer: { * type: 'json', * transform: { * fn: function(data, request) { * // do some manipulation of the unserialized data object * return data; * }, * scope: this * } * } * }, * }); * */ transform: null }, /** * @property {Boolean} isWriter * `true` in this class to identify an object as an instantiated Writer, or subclass thereof. */ isWriter: true, /** * Creates new Writer. * @param {Object} [config] Config object. */ constructor: function(config) { this.initConfig(config); }, applyTransform: function(transform) { if (transform) { if (Ext.isFunction(transform)) { transform = {fn:transform}; } return transform.fn.bind(transform.scope || this); } return transform; }, /** * Prepares a Proxy's Ext.data.Request object. * @param {Ext.data.Request} request The request object. * @return {Ext.data.Request} The modified request object. */ write: function(request) { var operation = request.getOperation(), records = operation.getRecords() || [], len = records.length, data = [], i; for (i = 0; i < len; i++) { data.push(this.getRecordData(records[i], operation)); } return this.writeRecords(request, data); }, /** * @method * * Write the record data to the request in the appropriate format. * @protected * @param {Ext.data.Request} request The request. * @param {Array} data An array of objects containing data. * @return {Ext.data.Request} The request. */ writeRecords: Ext.emptyFn, /** * Formats the data for each record before sending it to the server. This method should * be overridden to format the data in a way that differs from the default. * * @param {Ext.data.Model} record The record that we are writing to the server. * @param {Ext.data.operation.Operation} [operation] An operation object. * @return {Object} An object literal of name/value keys to be written to the server. * By default this method returns the data property on the record. */ getRecordData: function (record, operation) { var me = this, nameProperty = me.getNameProperty(), mapping = nameProperty !== 'name', idField = record.self.idField, key = idField[nameProperty] || idField.name, // setup for idField first value = record.id, writeAll = me.getWriteAllFields(), ret, dateFormat, phantom, options, clientIdProperty, fieldsMap, data, field; if (idField.serialize) { value = idField.serialize(value); } if (!writeAll && operation && operation.isDestroyOperation) { ret = {}; ret[key] = value; } else { dateFormat = me.getDateFormat(); phantom = record.phantom; options = (phantom || writeAll) ? me.getAllDataOptions() : me.getPartialDataOptions(); clientIdProperty = phantom && me.getClientIdProperty(); fieldsMap = record.getFieldsMap(); options.serialize = false; // we must take over this here data = record.getData(options); // If we are mapping we need to pour data into a new object, otherwise we do // our work in-place: ret = mapping ? {} : data; if (clientIdProperty) { // if (phantom and have clientIdProperty) ret[clientIdProperty] = value; // must read data and write ret delete data[key]; // in case ret === data (must not send "id") } else if (!me.getWriteRecordId()) { delete data[key]; } for (key in data) { value = data[key]; if (!(field = fieldsMap[key])) { // No defined field, so clearly no nameProperty to look up for this field // but if we are mapping we need to copy over the value. Also there is no // serializer to call in this case. if (mapping) { ret[key] = value; } } else { // Allow this Writer to take over formatting date values if it has a // dateFormat specified. Only check isDate on fields declared as dates // for efficiency. if (field.isDateField && dateFormat && Ext.isDate(value)) { value = Ext.Date.format(value, dateFormat); } else if (field.serialize) { value = field.serialize(value, record); } if (mapping) { key = field[nameProperty] || key; } ret[key] = value; } } } return ret; }});