/** * FormPanel provides a standard container for forms. It is essentially a standard {@link Ext.panel.Panel} which * automatically creates a {@link Ext.form.Basic BasicForm} for managing any {@link Ext.form.field.Field} * objects that are added as descendants of the panel. It also includes conveniences for configuring and * working with the BasicForm and the collection of Fields. * * # Layout * * By default, FormPanel is configured with `{@link Ext.layout.container.Anchor layout:'anchor'}` for * the layout of its immediate child items. This can be changed to any of the supported container layouts. * The layout of sub-containers is configured in {@link Ext.container.Container#layout the standard way}. * * # BasicForm * * FormPanel class accepts all * of the config options supported by the {@link Ext.form.Basic} class, and will pass them along to * the internal BasicForm when it is created. * * The following events fired by the BasicForm will be re-fired by the FormPanel and can therefore be * listened for on the FormPanel itself: * * - {@link Ext.form.Basic#beforeaction beforeaction} * - {@link Ext.form.Basic#actionfailed actionfailed} * - {@link Ext.form.Basic#actioncomplete actioncomplete} * - {@link Ext.form.Basic#validitychange validitychange} * - {@link Ext.form.Basic#dirtychange dirtychange} * * # Field Defaults * * The {@link #fieldDefaults} config option conveniently allows centralized configuration of default values * for all fields added as descendants of the FormPanel. Any config option recognized by implementations * of {@link Ext.form.Labelable} may be included in this object. See the {@link #fieldDefaults} documentation * for details of how the defaults are applied. * * # Form Validation * * With the default configuration, form fields are validated on-the-fly while the user edits their values. * This can be controlled on a per-field basis (or via the {@link #fieldDefaults} config) with the field * config properties {@link Ext.form.field.Field#validateOnChange} and {@link Ext.form.field.Base#checkChangeEvents}, * and the FormPanel's config properties {@link #pollForChanges} and {@link #pollInterval}. * * Any component within the FormPanel can be configured with `formBind: true`. This will cause that * component to be automatically disabled when the form is invalid, and enabled when it is valid. This is most * commonly used for Button components to prevent submitting the form in an invalid state, but can be used on * any component type. * * For more information on form validation see the following: * * - {@link Ext.form.field.Field#validateOnChange} * - {@link #pollForChanges} and {@link #pollInterval} * - {@link Ext.form.field.VTypes} * - {@link Ext.form.Basic#doAction BasicForm.doAction clientValidation notes} * * # Form Submission * * By default, Ext Forms are submitted through Ajax, using {@link Ext.form.action.Action}. See the documentation for * {@link Ext.form.Basic} for details. * * # Example usage * * @example * Ext.create('Ext.form.Panel', { * title: 'Simple Form', * bodyPadding: 5, * width: 350, * * // The form will submit an AJAX request to this URL when submitted * url: 'save-form.php', * * // Fields will be arranged vertically, stretched to full width * layout: 'anchor', * defaults: { * anchor: '100%' * }, * * // The fields * defaultType: 'textfield', * items: [{ * fieldLabel: 'First Name', * name: 'first', * allowBlank: false * },{ * fieldLabel: 'Last Name', * name: 'last', * allowBlank: false * }], * * // Reset and Submit buttons * buttons: [{ * text: 'Reset', * handler: function() { * this.up('form').getForm().reset(); * } * }, { * text: 'Submit', * formBind: true, //only enabled once the form is valid * disabled: true, * handler: function() { * var form = this.up('form').getForm(); * if (form.isValid()) { * form.submit({ * success: function(form, action) { * Ext.Msg.alert('Success', action.result.msg); * }, * failure: function(form, action) { * Ext.Msg.alert('Failed', action.result.msg); * } * }); * } * } * }], * renderTo: Ext.getBody() * }); * */Ext.define('Ext.form.Panel', { extend:'Ext.panel.Panel', mixins: { fieldAncestor: 'Ext.form.FieldAncestor' }, alias: 'widget.form', alternateClassName: ['Ext.FormPanel', 'Ext.form.FormPanel'], requires: ['Ext.form.Basic', 'Ext.util.TaskRunner'], /** * @cfg {Boolean} pollForChanges * If set to `true`, sets up an interval task (using the {@link #pollInterval}) in which the * panel's fields are repeatedly checked for changes in their values. This is in addition to the normal detection * each field does on its own input element, and is not needed in most cases. It does, however, provide a * means to absolutely guarantee detection of all changes including some edge cases in some browsers which * do not fire native events. Defaults to `false`. */ /** * @cfg {Number} pollInterval * Interval in milliseconds at which the form's fields are checked for value changes. Only used if * the {@link #pollForChanges} option is set to `true`. Defaults to 500 milliseconds. */ /** * @cfg {Ext.enums.Layout/Object} layout * The {@link Ext.container.Container#layout} for the form panel's immediate child items. */ layout: 'anchor', bodyAriaRole: 'form', basicFormConfigs: [ /** * @cfg {Object} api * @inheritdoc Ext.form.Basic#cfg!api */ 'api', /** * @cfg {Object} baseParams * @inheritdoc Ext.form.Basic#cfg!baseParams */ 'baseParams', /** * @cfg {Object/Ext.data.reader.Reader} errorReader * @inheritdoc Ext.form.Basic#cfg!errorReader */ 'errorReader', /** * @cfg {Boolean} jsonSubmit * @inheritdoc Ext.form.Basic#cfg!jsonSubmit */ 'jsonSubmit', /** * @cfg {String} method * @inheritdoc Ext.form.Basic#cfg!method */ 'method', /** * @cfg {String/String[]} paramOrder * @inheritdoc Ext.form.Basic#cfg!paramOrder */ 'paramOrder', /** * @cfg {Boolean} paramsAsHash * @inheritdoc Ext.form.Basic#cfg!paramsAsHash */ 'paramsAsHash', /** * @cfg {Object/Ext.data.reader.Reader} reader * @inheritdoc Ext.form.Basic#cfg!reader */ 'reader', /** * @cfg {Boolean} standardSubmit * @inheritdoc Ext.form.Basic#cfg!standardSubmit */ 'standardSubmit', /** * @cfg {Number} timeout * @inheritdoc Ext.form.Basic#cfg!timeout */ 'timeout', /** * @cfg {Boolean} trackResetOnLoad * @inheritdoc Ext.form.Basic#cfg!trackResetOnLoad */ 'trackResetOnLoad', /** * @cfg {String} url * @inheritdoc Ext.form.Basic#cfg!url */ 'url', /** * @cfg {String/HTMLElement/Ext.dom.Element} waitMsgTarget * @inheritdoc Ext.form.Basic#cfg!waitMsgTarget */ 'waitMsgTarget', /** * @cfg {String} waitTitle * @inheritdoc Ext.form.Basic#cfg!waitTitle */ 'waitTitle' ], initComponent: function() { var me = this; if (me.frame) { me.border = false; } me.initFieldAncestor(); me.callParent(); me.relayEvents(me.form, [ /** * @event beforeaction * @inheritdoc Ext.form.Basic#beforeaction */ 'beforeaction', /** * @event actionfailed * @inheritdoc Ext.form.Basic#actionfailed */ 'actionfailed', /** * @event actioncomplete * @inheritdoc Ext.form.Basic#actioncomplete */ 'actioncomplete', /** * @event validitychange * @inheritdoc Ext.form.Basic#validitychange */ 'validitychange', /** * @event dirtychange * @inheritdoc Ext.form.Basic#dirtychange */ 'dirtychange' ]); // Start polling if configured if (me.pollForChanges) { me.startPolling(me.pollInterval || 500); } }, initItems: function() { // Create the BasicForm this.callParent(); this.initMonitor(); this.form = this.createForm(); }, // Initialize the BasicForm after all layouts have been completed. afterFirstLayout: function() { this.callParent(arguments); this.form.initialize(); }, /** * @private */ createForm: function() { var cfg = {}, props = this.basicFormConfigs, len = props.length, i = 0, prop; for (; i < len; ++i) { prop = props[i]; cfg[prop] = this[prop]; } return new Ext.form.Basic(this, cfg); }, /** * Provides access to the {@link Ext.form.Basic Form} which this Panel contains. * @return {Ext.form.Basic} The {@link Ext.form.Basic Form} which this Panel contains. */ getForm: function() { return this.form; }, /** * Loads an {@link Ext.data.Model} into this form (internally just calls {@link Ext.form.Basic#loadRecord}) * See also {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}. The fields in the model are mapped to * fields in the form by matching either the {@link Ext.form.field.Base#name} or {@link Ext.Component#itemId}. * @param {Ext.data.Model} record The record to load * @return {Ext.form.Basic} The Ext.form.Basic attached to this FormPanel */ loadRecord: function(record) { return this.getForm().loadRecord(record); }, /** * Returns the currently loaded Ext.data.Model instance if one was loaded via {@link #loadRecord}. * @return {Ext.data.Model} The loaded instance */ getRecord: function() { return this.getForm().getRecord(); }, /** * Persists the values in this form into the passed {@link Ext.data.Model} object in a beginEdit/endEdit block. * If the record is not specified, it will attempt to update (if it exists) the record provided to {@link #loadRecord}. * @param {Ext.data.Model} [record] The record to edit * @return {Ext.form.Basic} The Ext.form.Basic attached to this FormPanel */ updateRecord: function(record) { return this.getForm().updateRecord(record); }, /** * Convenience function for fetching the current value of each field in the form. This is the same as calling * {@link Ext.form.Basic#getValues this.getForm().getValues()}. * * @inheritdoc Ext.form.Basic#getValues */ getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues) { return this.getForm().getValues(asString, dirtyOnly, includeEmptyText, useDataValues); }, /** * Convenience function to check if the form has any dirty fields. This is the same as calling * {@link Ext.form.Basic#isDirty this.getForm().isDirty()}. * * @inheritdoc Ext.form.Basic#isDirty */ isDirty: function () { return this.form.isDirty(); }, /** * Convenience function to check if the form has all valid fields. This is the same as calling * {@link Ext.form.Basic#isValid this.getForm().isValid()}. * * @inheritdoc Ext.form.Basic#isValid */ isValid: function () { return this.form.isValid(); }, /** * Convenience function reset the form. This is the same as calling * {@link Ext.form.Basic#reset this.getForm().reset()}. * * @inheritdoc Ext.form.Basic#reset */ reset: function(resetRecord) { return this.form.reset(resetRecord); }, /** * Convenience function to check if the form has any invalid fields. This is the same as calling * {@link Ext.form.Basic#hasInvalidField this.getForm().hasInvalidField()}. * * @inheritdoc Ext.form.Basic#hasInvalidField */ hasInvalidField: function () { return this.form.hasInvalidField(); }, doDestroy: function() { this.stopPolling(); this.form.destroy(); this.callParent(); }, /** * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#load} call. * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#load} and * {@link Ext.form.Basic#doAction} for details) */ load: function(options) { this.form.load(options); }, /** * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#submit} call. * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#submit} and * {@link Ext.form.Basic#doAction} for details) */ submit: function(options) { this.form.submit(options); }, /** * Start an interval task to continuously poll all the fields in the form for changes in their * values. This is normally started automatically by setting the {@link #pollForChanges} config. * @param {Number} interval The interval in milliseconds at which the check should run. */ startPolling: function(interval) { this.stopPolling(); var task = new Ext.util.TaskRunner(interval); task.start({ interval: 0, run: this.checkChange, scope: this }); this.pollTask = task; }, /** * Stop a running interval task that was started by {@link #startPolling}. */ stopPolling: function() { var task = this.pollTask; if (task) { task.stopAll(); delete this.pollTask; } }, /** * Forces each field within the form panel to * {@link Ext.form.field.Field#checkChange check if its value has changed}. */ checkChange: function() { var fields = this.form.getFields().items, f, fLen = fields.length; for (f = 0; f < fLen; f++) { fields[f].checkChange(); } }});