/** * This mixin is used by {@link Ext.field.Panel fieldpanel} to provide field management * methods. * * ## Setting Form Data * * The {@link #setValues} can be used to populate fields from a data object. * * form.setValues({ * name: 'Peter', * email: '[email protected]', * password: 'secret' * }); * * It's also easy to load {@link Ext.data.Model Model} instances into a form - let's say * we have a User model and want to load a particular instance into our form: * * Ext.define('MyApp.model.User', { * extend: 'Ext.data.Model', * config: { * fields: ['name', 'email', 'password'] * } * }); * * var ed = Ext.create('MyApp.model.User', { * name: 'Peter', * email: '[email protected]', * password: 'secret' * }); * * form.setRecord(ed); * * ## Setting multiple errors on fields * * While you can call {@link Ext.field.Field#setError} and * {@link Ext.field.Field#setError setError(null)} on each field, in your form, FormPanel provides a * {@link #setErrors} method that will apply an Object of error states to multiple fields * with one call: * * panel.setErrors({ * field1: 'field1 is in error', * name2: 'field2 is in error', * fieldname3: null, // clear any errors * fieldname4: [ 'one', 'two', 'three' }, // multiple errors * ... * }); * * While you can call {@link Ext.field.Field#getError} on each field in the form to query * the form for errors, you can call {@link #getError} on the form to get an array of * error states, suitable to pass into {@link #setErrors}. * * NOTE: these methods will only work on fields with a {@link Ext.field.Field#cfg!name name} * config specified. * @protected * @since 6.5.0 */Ext.define('Ext.field.Manager', { mixinId: 'fieldmanager', requires: ['Ext.Ajax'], /** * Set the fields of the provided `record` from the {@link Ext.field.Field#cfg!name named} * fields. * @param {Ext.data.Model} record * @return {Ext.field.Manager} this */ fillRecord: function(record) { var values, name; if (record) { values = this.getValues(); for (name in values) { if (values.hasOwnProperty(name) && record.getField(name)) { record.set(name, values[name]); } } } return this; }, consumeRecord: function(record) { var data = record && record.data; if (data) { this.setValues(data); } }, /** * Sets the values of form fields in bulk. Example usage: * * myForm.setValues({ * name: 'Bill', * crazy: true, * username: 'bill.preston' * }); * * If there groups of checkbox fields with the same name, pass their values in an * array. For example: * * myForm.setValues({ * name: 'Ted', * crazy: false, * hobbies: [ * 'reading', * 'cooking', * 'gaming' * ] * }); * * @param {Object} values field name => value mapping object. * @return {Ext.field.Manager} this */ setValues: function(values) { var fields = this.getFields(), name, field, value, ln, i, f; values = values || {}; for (name in values) { if (values.hasOwnProperty(name)) { field = fields[name]; value = values[name]; if (field) { // If there are multiple fields with the same name. Checkboxes, radio // fields and maybe event just normal fields.. if (Ext.isArray(field)) { ln = field.length; // Loop through each of the fields for (i = 0; i < ln; i++) { f = field[i]; if (f.isRadio) { // If it is a radio field just use setGroupValue which // will handle all of the radio fields f.setGroupValue(value); break; } else if (f.isCheckbox) { if (Ext.isArray(value)) { f.setChecked(value.indexOf(f._value) !== -1); } else { f.setChecked(value === f._value); } } else { // If it is a bunch of fields with the same name, check // if the value is also an array, so we can map it to // each field if (Ext.isArray(value)) { f.setValue(value[i]); } } } } else { if (field.isRadio || field.isCheckbox) { // If the field is a radio or a checkbox field.setChecked(value); } else { // If just a normal field field.setValue(value); } } if (this.getTrackResetOnLoad && this.getTrackResetOnLoad()) { field.resetOriginalValue(); } } } } return this; }, /** * Returns an object containing the value of each field in the form, keyed to the * field's name. * * For groups of checkbox fields with the same name, it will be arrays of values. * For example: * * { * name: "Bill", // From a TextField * favorites: [ * 'pizza', * 'noodle', * 'cake' * ] * } * * @param {Object/Boolean} [options] An object containing options to control the values * retrieved. If a boolean is passed, this is the same as passing the `enabled` property * on the object. * @param {Boolean} [options.enabled] `true` to return only enabled fields. * @param {Boolean} [options.nameless] `true` to return all fields even if they don't * have a {@link Ext.field.Field#name name} configured. * @param {Boolean} [options.serialize] Pass `true` to return serialized values. * @return {Object} Object mapping field name to its value. */ getValues: function(options, nameless) { var fields = this.getFields(), values = {}, isArray = Ext.isArray, enabled = options, field, value, addValue, bucket, name, ln, i, serialize; if (Ext.isObject(options)) { enabled = options.enabled; nameless = options.nameless; serialize = options.serialize; } // Function which you give a field and a name, and it will add it into the values // object accordingly addValue = function(field, name) { if ((!nameless && (!name || name === 'null')) || field.isFile) { return; } // Checkboxes have a "value" but it is behaves differently than regular // fields. When the checkbox is checked, its value is returned and otherwise // it returns null. By default, that value is true. This is handled by // serialize/getSubmitValue so we always need to call it for checkboxes. value = (serialize || field.isCheckbox) ? field.serialize() : field.getValue(); if (!(enabled && field.getDisabled())) { // RadioField is a special case where the value returned is the fields valUE // ONLY if it is checked if (field.isRadio) { if (field.isChecked()) { values[name] = value; } } else { // Check if the value already exists bucket = values[name]; if (!Ext.isEmpty(bucket)) { if (!field.isCheckbox || field.isChecked()) { // if it does and it isn't an array, we need to make it into an array // so we can push more if (!isArray(bucket)) { bucket = values[name] = [bucket]; } // Check if it is an array if (isArray(value)) { // Concat it into the other values bucket = values[name] = bucket.concat(value); } else { // If it isn't an array, just pushed more values bucket.push(value); } } } else { values[name] = value; } } } }; // Loop through each of the fields, and add the values for those fields. for (name in fields) { if (fields.hasOwnProperty(name)) { field = fields[name]; if (isArray(field)) { ln = field.length; for (i = 0; i < ln; i++) { addValue(field[i], name); } } else { addValue(field, name); } } } return values; }, /** * Convenience method which calls getValues, passing `serialize: true`. * @param {Object} [options] (Optional) The `options` parameter * passed to {@link #method-getValues} */ getSubmitValues: function(options) { return this.getValues(Ext.applyIf({ serialize: true }, options)); }, /** * Resets all fields in the form back to their original values. * @param {boolean} clearInvalid If `true` will clear any invalid UI state for the fields * as well. * @return {Ext.field.Manager} this */ reset: function(clearInvalid) { this.getFields(false).forEach(function(field) { field.reset(); if (clearInvalid) { field.setError(null); } }); return this; }, /** * A convenient method to disable all fields in this form. * @return {Ext.field.Manager} this */ updateDisabled: function(newDisabled) { this.getFields(false).forEach(function(field) { field.setDisabled(newDisabled); }); return this; }, /** * Marks multiple fields valid or invalid based upon an Object of error states * * Each key of the error states Object is the name of the field whose validity status * is to be affected. Each value of the error states Object is either a string or array * of strings to set as the field's invalid message(s). If the value is null or an * empty array, the field is marked valid instead of invalid. * * @param {Object} errors The errors to set child fields with. * @return {Ext.field.Manager} this */ setErrors: function(errors) { var setError = function(field, fieldname) { if (field) { messages = errors[fieldname]; if (messages === null || (Ext.isArray(messages) && messages.length === 0)) { field.setError(null); } else { field.setError(messages); } } }, fieldname, field, messages, i, length; //<debug> if (!Ext.isObject(errors)) { Ext.raise('setErrors requires an Object parameter'); } //</debug> for (fieldname in errors) { field = this.lookupName(fieldname) || this.lookup(fieldname); if (Ext.isArray(field)) { for (i = 0, length = field.length; i < length; i++) { setError(field[i], fieldname); } } else { setError(field, fieldname); } } return this; }, /** * Marks all named fields as valid by calling setError() on each. * * @return {Ext.field.Manager} this */ clearErrors: function() { var fields = this.getFields(false), i, length, field; for (i = 0, length = fields.length; i < length; i++) { field = fields[i]; if (field.getName() && field.setError) { field.setError(null); } } return this; }, /** * Gets error state for all named fields of the form. * * The Object returned is exactly the same as one that can be passed to {@link #setErrors}. */ getErrors: function() { var errors = {}, fields, i, length, field, error; fields = this.getFields(false).filter(function(field) { return field.getName(); }); for (i = 0, length = fields.length; i < length; i++) { field = fields[i]; error = field.getError(); if (!error || !error.length) { error = null; } errors[field.getName()] = error; } return errors; }, /** * Test to see if the form has any invalid fields. * * **NOTE** This method does not validate the fields, it only returns * `true` if any field is already marked invalid using the field's * {@link Ext.field.Field#isValid isValid}. If field values need to * be validated, {@link #validate} should be used instead. * * @return {Boolean} `true` if all fields are currently valid. */ isValid: function() { var fields = this.getFields(false), i, length; for (i = 0, length = fields.length; i < length; i++) { if (!fields[i].isValid()) { return false; } } return true; }, /** * Calls {@link Ext.field.Field#validate validate} on each field in the form. * * **Note** This will validate the field's current value. If you want to check if * all the fields are currently valid without validating the fields value, * {@link #isValid} should be used instead. * * @param {Boolean} [skipLazy] (private) Pass `true` to skip validators marked as `lazy`. * @return {Boolean} `true` if all fields in the form are valid, false if * any one (or more) of the fields is invalid. */ validate: function(skipLazy) { var fields = this.getFields(false), valid = true, i, length; for (i = 0, length = fields.length; i < length; i++) { if (!fields[i].validate(skipLazy)) { // don't stop the loop, need to validate all fields // so all fields can show validation status valid = false; } } return valid; }, /** * Returns all {@link Ext.field.Field field} instances inside this form. * @param {String} byName Return an array of fields that match the given name. * If only one field matches the name, only that instance will be returned. * @param {Boolean} deep `false` will prevent it from getting fields * in child containers. * @return {Object/Ext.field.Field/Ext.field.Field[]} All field instances, mapped by field name; * or an array if `byName` is passed. */ getFields: function(byName, deep) { var relation = deep === false ? '>' : '', selector = relation + 'field' + (byName ? '[name=' + byName + ']' : ''), fields = this.query(selector), asArray = byName === false, obj, i, length, field, name, bucket; if (!fields && asArray) { // no fields were found but we want an array return []; } else if (fields && !asArray) { // we weren't told to always return an array if (!byName) { // no name was provided so we need to build a map // of field names to instance(s) obj = {}; for (i = 0, length = fields.length; i < length; i++) { field = fields[i]; name = field.getName(); bucket = obj[name]; if (bucket) { if (Ext.isArray(bucket)) { bucket.push(field); } else { obj[name] = [bucket, field]; } } else { obj[name] = field; } } return obj; } else if (fields.length < 2) { // since asArray is falsy and there is 1 or less fields // if length is 0, will return undefined return fields[0]; } } return fields; }, /** * Returns the currently focused field * @return {Ext.field.Field} The currently focused field, if one is focused or `null`. * @private */ getFocusedField: function() { var fields = this.getFields(false), ln = fields.length, field, i; for (i = 0; i < ln; i++) { field = fields[i]; if (field.hasFocus) { return field; } } return null; }, /** * @return {Boolean/Ext.field.Field} The next field if one exists, or `false`. * @private */ getNextField: function() { var fields = this.getFields(false), focusedField = this.getFocusedField(), index; if (focusedField) { index = fields.indexOf(focusedField); if (index !== fields.length - 1) { index++; return fields[index]; } } return false; }, /** * Tries to focus the next field in the form, if there is currently a focused field. * @return {Boolean/Ext.field.Field} The next field that was focused, or `false`. * @private */ focusNextField: function() { var field = this.getNextField(); if (field) { field.focus(); return field; } return false; }, /** * @private * @return {Boolean/Ext.field.Field} The next field if one exists, or `false`. */ getPreviousField: function() { var fields = this.getFields(false), focusedField = this.getFocusedField(), index; if (focusedField) { index = fields.indexOf(focusedField); if (index !== 0) { index--; return fields[index]; } } return false; }, /** * Tries to focus the previous field in the form, if there is currently a focused field. * @return {Boolean/Ext.field.Field} The previous field that was focused, or `false`. * @private */ focusPreviousField: function() { var field = this.getPreviousField(); if (field) { field.focus(); return field; } return false; }});