/* This file is part of Ext JS 4.2 Copyright (c) 2011-2013 Sencha Inc Contact: http://www.sencha.com/contact GNU General Public License Usage This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact. Build date: 2013-05-16 14:36:50 (f9be68accb407158ba2b1be2c226a6ce1f649314) */ /** * Provides input field management, validation, submission, and form loading services for the collection * of {@link Ext.form.field.Field Field} instances within a {@link Ext.container.Container}. It is recommended * that you use a {@link Ext.form.Panel} as the form container, as that has logic to automatically * hook up an instance of {@link Ext.form.Basic} (plus other conveniences related to field configuration.) * * ## Form Actions * * The Basic class delegates the handling of form loads and submits to instances of {@link Ext.form.action.Action}. * See the various Action implementations for specific details of each one's functionality, as well as the * documentation for {@link #doAction} which details the configuration options that can be specified in * each action call. * * The default submit Action is {@link Ext.form.action.Submit}, which uses an Ajax request to submit the * form's values to a configured URL. To enable normal browser submission of an Ext form, use the * {@link #standardSubmit} config option. * * ## File uploads * * File uploads are not performed using normal 'Ajax' techniques; see the description for * {@link #hasUpload} for details. If you're using file uploads you should read the method description. * * ## Example usage: * * @example * Ext.create('Ext.form.Panel', { * title: 'Basic Form', * renderTo: Ext.getBody(), * bodyPadding: 5, * width: 350, * * // Any configuration items here will be automatically passed along to * // the Ext.form.Basic instance when it gets created. * * // The form will submit an AJAX request to this URL when submitted * url: 'save-form.php', * * items: [{ * xtype: 'textfield', * fieldLabel: 'Field', * name: 'theField' * }], * * buttons: [{ * text: 'Submit', * handler: function() { * // The getForm() method returns the Ext.form.Basic instance: * var form = this.up('form').getForm(); * if (form.isValid()) { * // Submit the Ajax request and handle the response * form.submit({ * success: function(form, action) { * Ext.Msg.alert('Success', action.result.message); * }, * failure: function(form, action) { * Ext.Msg.alert('Failed', action.result ? action.result.message : 'No response'); * } * }); * } * } * }] * }); * * @docauthor Jason Johnston */ Ext.define('Ext.form.Basic', { extend: 'Ext.util.Observable', alternateClassName: 'Ext.form.BasicForm', requires: ['Ext.util.MixedCollection', 'Ext.form.action.Load', 'Ext.form.action.Submit', 'Ext.window.MessageBox', 'Ext.data.Errors', 'Ext.util.DelayedTask'], /** * Creates new form. * @param {Ext.container.Container} owner The component that is the container for the form, usually a {@link Ext.form.Panel} * @param {Object} config Configuration options. These are normally specified in the config to the * {@link Ext.form.Panel} constructor, which passes them along to the BasicForm automatically. */ constructor: function(owner, config) { var me = this, reader; /** * @property {Ext.container.Container} owner * The container component to which this BasicForm is attached. */ me.owner = owner; me.checkValidityTask = new Ext.util.DelayedTask(me.checkValidity, me); me.checkDirtyTask = new Ext.util.DelayedTask(me.checkDirty, me); // We use the monitor here as opposed to event bubbling. The problem with bubbling is it doesn't // let us react to items being added/remove at different places in the hierarchy which may have an // impact on the dirty/valid state. me.monitor = new Ext.container.Monitor({ selector: '[isFormField]', scope: me, addHandler: me.onFieldAdd, removeHandler: me.onFieldRemove }); me.monitor.bind(owner); Ext.apply(me, config); // Normalize the paramOrder to an Array if (Ext.isString(me.paramOrder)) { me.paramOrder = me.paramOrder.split(/[\s,|]/); } reader = me.reader; if (reader && !reader.isReader) { if (typeof reader === 'string') { reader = { type: reader }; } me.reader = Ext.createByAlias('reader.' + reader.type, reader); } reader = me.errorReader; if (reader && !reader.isReader) { if (typeof reader === 'string') { reader = { type: reader }; } me.errorReader = Ext.createByAlias('reader.' + reader.type, reader); } me.addEvents( /** * @event beforeaction * Fires before any action is performed. Return false to cancel the action. * @param {Ext.form.Basic} this * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} to be performed */ 'beforeaction', /** * @event actionfailed * Fires when an action fails. * @param {Ext.form.Basic} this * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that failed */ 'actionfailed', /** * @event actioncomplete * Fires when an action is completed. * @param {Ext.form.Basic} this * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that completed */ 'actioncomplete', /** * @event validitychange * Fires when the validity of the entire form changes. * @param {Ext.form.Basic} this * @param {Boolean} valid `true` if the form is now valid, `false` if it is now invalid. */ 'validitychange', /** * @event dirtychange * Fires when the dirty state of the entire form changes. * @param {Ext.form.Basic} this * @param {Boolean} dirty `true` if the form is now dirty, `false` if it is no longer dirty. */ 'dirtychange' ); me.callParent(); }, /** * Do any post layout initialization * @private */ initialize : function() { this.initialized = true; this.onValidityChange(!this.hasInvalidField()); }, /** * @cfg {String} method * The request method to use (GET or POST) for form actions if one isn't supplied in the action options. */ /** * @cfg {Object/Ext.data.reader.Reader} reader * An Ext.data.reader.Reader (e.g. {@link Ext.data.reader.Xml}) instance or * configuration to be used to read data when executing 'load' actions. This * is optional as there is built-in support for processing JSON responses. */ /** * @cfg {Object/Ext.data.reader.Reader} errorReader * An Ext.data.reader.Reader (e.g. {@link Ext.data.reader.Xml}) instance or * configuration to be used to read field error messages returned from 'submit' actions. * This is optional as there is built-in support for processing JSON responses. * * The Records which provide messages for the invalid Fields must use the * Field name (or id) as the Record ID, and must contain a field called 'msg' * which contains the error message. * * The errorReader does not have to be a full-blown implementation of a * Reader. It simply needs to implement a `read(xhr)` function * which returns an Array of Records in an object with the following * structure: * * { * records: recordArray * } */ /** * @cfg {String} url * The URL to use for form actions if one isn't supplied in the * {@link #doAction doAction} options. */ /** * @cfg {Object} baseParams * Parameters to pass with all requests. e.g. baseParams: `{id: '123', foo: 'bar'}`. * * Parameters are encoded as standard HTTP parameters using {@link Ext.Object#toQueryString}. */ /** * @cfg {Number} timeout * Timeout for form actions in seconds. */ timeout: 30, /** * @cfg {Object} api * If specified, load and submit actions will be handled with {@link Ext.form.action.DirectLoad DirectLoad} * and {@link Ext.form.action.DirectSubmit DirectSubmit}. Methods which have been imported by * {@link Ext.direct.Manager} can be specified here to load and submit forms. API methods may also be * specified as strings. See {@link Ext.data.proxy.Direct#directFn}. Such as the following: * * api: { * load: App.ss.MyProfile.load, * submit: App.ss.MyProfile.submit * } * * Load actions can use {@link #paramOrder} or {@link #paramsAsHash} to customize how the load method * is invoked. Submit actions will always use a standard form submit. The `formHandler` configuration * (see Ext.direct.RemotingProvider#action) must be set on the associated server-side method which has * been imported by {@link Ext.direct.Manager}. */ /** * @cfg {String/String[]} paramOrder * A list of params to be executed server side. Only used for the {@link #api} `load` * configuration. * * Specify the params in the order in which they must be executed on the * server-side as either (1) an Array of String values, or (2) a String of params * delimited by either whitespace, comma, or pipe. For example, * any of the following would be acceptable: * * paramOrder: ['param1','param2','param3'] * paramOrder: 'param1 param2 param3' * paramOrder: 'param1,param2,param3' * paramOrder: 'param1|param2|param' */ /** * @cfg {Boolean} paramsAsHash * Only used for the {@link #api} `load` configuration. If true, parameters will be sent as a * single hash collection of named arguments. Providing a {@link #paramOrder} nullifies this * configuration. */ paramsAsHash: false, // /** * @cfg {String} waitTitle * The default title to show for the waiting message box */ waitTitle: 'Please Wait...', // /** * @cfg {Boolean} trackResetOnLoad * If set to true, {@link #reset}() resets to the last loaded or {@link #setValues}() data instead of * when the form was first created. */ trackResetOnLoad: false, /** * @cfg {Boolean} standardSubmit * If set to true, a standard HTML form submit is used instead of a XHR (Ajax) style form submission. * All of the field values, plus any additional params configured via {@link #baseParams} * and/or the `options` to {@link #submit}, will be included in the values submitted in the form. */ /** * @cfg {Boolean} jsonSubmit * If set to true, the field values are sent as JSON in the request body. * All of the field values, plus any additional params configured via {@link #baseParams} * and/or the `options` to {@link #submit}, will be included in the values POSTed in the body of the request. */ /** * @cfg {String/HTMLElement/Ext.Element} waitMsgTarget * By default wait messages are displayed with Ext.MessageBox.wait. You can target a specific * element by passing it or its id or mask the form itself by passing in true. */ // Private wasDirty: false, /** * Destroys this object. */ destroy: function() { var me = this, mon = me.monitor; if (mon) { mon.unbind(); me.monitor = null; } me.clearListeners(); me.checkValidityTask.cancel(); me.checkDirtyTask.cancel(); }, onFieldAdd: function(field){ var me = this; me.mon(field, 'validitychange', me.checkValidityDelay, me); me.mon(field, 'dirtychange', me.checkDirtyDelay, me); if (me.initialized) { me.checkValidityDelay(); } }, onFieldRemove: function(field){ var me = this; me.mun(field, 'validitychange', me.checkValidityDelay, me); me.mun(field, 'dirtychange', me.checkDirtyDelay, me); if (me.initialized) { me.checkValidityDelay(); } }, /** * Return all the {@link Ext.form.field.Field} components in the owner container. * @return {Ext.util.MixedCollection} Collection of the Field objects */ getFields: function() { return this.monitor.getItems(); }, /** * @private * Finds and returns the set of all items bound to fields inside this form * @return {Ext.util.MixedCollection} The set of all bound form field items */ getBoundItems: function() { var boundItems = this._boundItems; if (!boundItems || boundItems.getCount() === 0) { boundItems = this._boundItems = new Ext.util.MixedCollection(); boundItems.addAll(this.owner.query('[formBind]')); } return boundItems; }, /** * Returns true if the form contains any invalid fields. No fields will be marked as invalid * as a result of calling this; to trigger marking of fields use {@link #isValid} instead. */ hasInvalidField: function() { return !!this.getFields().findBy(function(field) { var preventMark = field.preventMark, isValid; field.preventMark = true; isValid = field.isValid(); field.preventMark = preventMark; return !isValid; }); }, /** * Returns true if client-side validation on the form is successful. Any invalid fields will be * marked as invalid. If you only want to determine overall form validity without marking anything, * use {@link #hasInvalidField} instead. * @return {Boolean} */ isValid: function() { var me = this, invalid; Ext.suspendLayouts(); invalid = me.getFields().filterBy(function(field) { return !field.validate(); }); Ext.resumeLayouts(true); return invalid.length < 1; }, /** * Check whether the validity of the entire form has changed since it was last checked, and * if so fire the {@link #validitychange validitychange} event. This is automatically invoked * when an individual field's validity changes. */ checkValidity: function() { var me = this, valid = !me.hasInvalidField(); if (valid !== me.wasValid) { me.onValidityChange(valid); me.fireEvent('validitychange', me, valid); me.wasValid = valid; } }, checkValidityDelay: function(){ this.checkValidityTask.delay(10); }, /** * @private * Handle changes in the form's validity. If there are any sub components with * `formBind=true` then they are enabled/disabled based on the new validity. * @param {Boolean} valid */ onValidityChange: function(valid) { var boundItems = this.getBoundItems(), items, i, iLen, cmp; if (boundItems) { items = boundItems.items; iLen = items.length; for (i = 0; i < iLen; i++) { cmp = items[i]; if (cmp.disabled === valid) { cmp.setDisabled(!valid); } } } }, /** * Returns `true` if any fields in this form have changed from their original values. * * Note that if this BasicForm was configured with {@link Ext.form.Basic#trackResetOnLoad * trackResetOnLoad} then the Fields' *original values* are updated when the values are * loaded by {@link Ext.form.Basic#setValues setValues} or {@link #loadRecord}. * * @return {Boolean} */ isDirty: function() { return !!this.getFields().findBy(function(f) { return f.isDirty(); }); }, checkDirtyDelay: function(){ this.checkDirtyTask.delay(10); }, /** * Check whether the dirty state of the entire form has changed since it was last checked, and * if so fire the {@link #dirtychange dirtychange} event. This is automatically invoked * when an individual field's `dirty` state changes. */ checkDirty: function() { var dirty = this.isDirty(); if (dirty !== this.wasDirty) { this.fireEvent('dirtychange', this, dirty); this.wasDirty = dirty; } }, /** * Returns `true` if the form contains a file upload field. This is used to determine the method for submitting the * form: File uploads are not performed using normal 'Ajax' techniques, that is they are **not** performed using * XMLHttpRequests. Instead a hidden `
` element containing all the fields is created temporarily and submitted * with its [target][1] set to refer to a dynamically generated, hidden `