Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / form / Panel.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 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.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @docauthor Jason Johnston <jason@sencha.com>
17  * 
18  * FormPanel provides a standard container for forms. It is essentially a standard {@link Ext.panel.Panel} which
19  * automatically creates a {@link Ext.form.Basic BasicForm} for managing any {@link Ext.form.field.Field}
20  * objects that are added as descendants of the panel. It also includes conveniences for configuring and
21  * working with the BasicForm and the collection of Fields.
22  * 
23  * # Layout
24  * 
25  * By default, FormPanel is configured with `{@link Ext.layout.container.Anchor layout:'anchor'}` for
26  * the layout of its immediate child items. This can be changed to any of the supported container layouts.
27  * The layout of sub-containers is configured in {@link Ext.container.Container#layout the standard way}.
28  * 
29  * # BasicForm
30  * 
31  * Although **not listed** as configuration options of FormPanel, the FormPanel class accepts all
32  * of the config options supported by the {@link Ext.form.Basic} class, and will pass them along to
33  * the internal BasicForm when it is created.
34  * 
35  * **Note**: If subclassing FormPanel, any configuration options for the BasicForm must be applied to
36  * the `initialConfig` property of the FormPanel. Applying {@link Ext.form.Basic BasicForm}
37  * configuration settings to `this` will *not* affect the BasicForm's configuration.
38  * 
39  * The following events fired by the BasicForm will be re-fired by the FormPanel and can therefore be
40  * listened for on the FormPanel itself:
41  * 
42  * - {@link Ext.form.Basic#beforeaction beforeaction}
43  * - {@link Ext.form.Basic#actionfailed actionfailed}
44  * - {@link Ext.form.Basic#actioncomplete actioncomplete}
45  * - {@link Ext.form.Basic#validitychange validitychange}
46  * - {@link Ext.form.Basic#dirtychange dirtychange}
47  * 
48  * # Field Defaults
49  * 
50  * The {@link #fieldDefaults} config option conveniently allows centralized configuration of default values
51  * for all fields added as descendants of the FormPanel. Any config option recognized by implementations
52  * of {@link Ext.form.Labelable} may be included in this object. See the {@link #fieldDefaults} documentation
53  * for details of how the defaults are applied.
54  * 
55  * # Form Validation
56  * 
57  * With the default configuration, form fields are validated on-the-fly while the user edits their values.
58  * This can be controlled on a per-field basis (or via the {@link #fieldDefaults} config) with the field
59  * config properties {@link Ext.form.field.Field#validateOnChange} and {@link Ext.form.field.Base#checkChangeEvents},
60  * and the FormPanel's config properties {@link #pollForChanges} and {@link #pollInterval}.
61  * 
62  * Any component within the FormPanel can be configured with `formBind: true`. This will cause that
63  * component to be automatically disabled when the form is invalid, and enabled when it is valid. This is most
64  * commonly used for Button components to prevent submitting the form in an invalid state, but can be used on
65  * any component type.
66  * 
67  * For more information on form validation see the following:
68  * 
69  * - {@link Ext.form.field.Field#validateOnChange}
70  * - {@link #pollForChanges} and {@link #pollInterval}
71  * - {@link Ext.form.field.VTypes}
72  * - {@link Ext.form.Basic#doAction BasicForm.doAction clientValidation notes}
73  * 
74  * # Form Submission
75  * 
76  * By default, Ext Forms are submitted through Ajax, using {@link Ext.form.action.Action}. See the documentation for
77  * {@link Ext.form.Basic} for details.
78  *
79  * # Example usage
80  * 
81  *     @example
82  *     Ext.create('Ext.form.Panel', {
83  *         title: 'Simple Form',
84  *         bodyPadding: 5,
85  *         width: 350,
86  * 
87  *         // The form will submit an AJAX request to this URL when submitted
88  *         url: 'save-form.php',
89  * 
90  *         // Fields will be arranged vertically, stretched to full width
91  *         layout: 'anchor',
92  *         defaults: {
93  *             anchor: '100%'
94  *         },
95  * 
96  *         // The fields
97  *         defaultType: 'textfield',
98  *         items: [{
99  *             fieldLabel: 'First Name',
100  *             name: 'first',
101  *             allowBlank: false
102  *         },{
103  *             fieldLabel: 'Last Name',
104  *             name: 'last',
105  *             allowBlank: false
106  *         }],
107  * 
108  *         // Reset and Submit buttons
109  *         buttons: [{
110  *             text: 'Reset',
111  *             handler: function() {
112  *                 this.up('form').getForm().reset();
113  *             }
114  *         }, {
115  *             text: 'Submit',
116  *             formBind: true, //only enabled once the form is valid
117  *             disabled: true,
118  *             handler: function() {
119  *                 var form = this.up('form').getForm();
120  *                 if (form.isValid()) {
121  *                     form.submit({
122  *                         success: function(form, action) {
123  *                            Ext.Msg.alert('Success', action.result.msg);
124  *                         },
125  *                         failure: function(form, action) {
126  *                             Ext.Msg.alert('Failed', action.result.msg);
127  *                         }
128  *                     });
129  *                 }
130  *             }
131  *         }],
132  *         renderTo: Ext.getBody()
133  *     });
134  *
135  */
136 Ext.define('Ext.form.Panel', {
137     extend:'Ext.panel.Panel',
138     mixins: {
139         fieldAncestor: 'Ext.form.FieldAncestor'
140     },
141     alias: 'widget.form',
142     alternateClassName: ['Ext.FormPanel', 'Ext.form.FormPanel'],
143     requires: ['Ext.form.Basic', 'Ext.util.TaskRunner'],
144
145     /**
146      * @cfg {Boolean} pollForChanges
147      * If set to `true`, sets up an interval task (using the {@link #pollInterval}) in which the
148      * panel's fields are repeatedly checked for changes in their values. This is in addition to the normal detection
149      * each field does on its own input element, and is not needed in most cases. It does, however, provide a
150      * means to absolutely guarantee detection of all changes including some edge cases in some browsers which
151      * do not fire native events. Defaults to `false`.
152      */
153
154     /**
155      * @cfg {Number} pollInterval
156      * Interval in milliseconds at which the form's fields are checked for value changes. Only used if
157      * the {@link #pollForChanges} option is set to `true`. Defaults to 500 milliseconds.
158      */
159
160     /**
161      * @cfg {String} layout
162      * The {@link Ext.container.Container#layout} for the form panel's immediate child items.
163      * Defaults to `'anchor'`.
164      */
165     layout: 'anchor',
166
167     ariaRole: 'form',
168
169     initComponent: function() {
170         var me = this;
171
172         if (me.frame) {
173             me.border = false;
174         }
175
176         me.initFieldAncestor();
177         me.callParent();
178
179         me.relayEvents(me.form, [
180             'beforeaction',
181             'actionfailed',
182             'actioncomplete',
183             'validitychange',
184             'dirtychange'
185         ]);
186
187         // Start polling if configured
188         if (me.pollForChanges) {
189             me.startPolling(me.pollInterval || 500);
190         }
191     },
192
193     initItems: function() {
194         // Create the BasicForm
195         var me = this;
196
197         me.form = me.createForm();
198         me.callParent();
199         me.form.initialize();
200     },
201
202     /**
203      * @private
204      */
205     createForm: function() {
206         return Ext.create('Ext.form.Basic', this, Ext.applyIf({listeners: {}}, this.initialConfig));
207     },
208
209     /**
210      * Provides access to the {@link Ext.form.Basic Form} which this Panel contains.
211      * @return {Ext.form.Basic} The {@link Ext.form.Basic Form} which this Panel contains.
212      */
213     getForm: function() {
214         return this.form;
215     },
216
217     /**
218      * Loads an {@link Ext.data.Model} into this form (internally just calls {@link Ext.form.Basic#loadRecord})
219      * See also {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}.
220      * @param {Ext.data.Model} record The record to load
221      * @return {Ext.form.Basic} The Ext.form.Basic attached to this FormPanel
222      */
223     loadRecord: function(record) {
224         return this.getForm().loadRecord(record);
225     },
226
227     /**
228      * Returns the currently loaded Ext.data.Model instance if one was loaded via {@link #loadRecord}.
229      * @return {Ext.data.Model} The loaded instance
230      */
231     getRecord: function() {
232         return this.getForm().getRecord();
233     },
234
235     /**
236      * Convenience function for fetching the current value of each field in the form. This is the same as calling
237      * {@link Ext.form.Basic#getValues this.getForm().getValues()}
238      * @return {Object} The current form field values, keyed by field name
239      */
240     getValues: function() {
241         return this.getForm().getValues();
242     },
243
244     beforeDestroy: function() {
245         this.stopPolling();
246         this.form.destroy();
247         this.callParent();
248     },
249
250     /**
251      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#load} call.
252      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#load} and
253      * {@link Ext.form.Basic#doAction} for details)
254      */
255     load: function(options) {
256         this.form.load(options);
257     },
258
259     /**
260      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#submit} call.
261      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#submit} and
262      * {@link Ext.form.Basic#doAction} for details)
263      */
264     submit: function(options) {
265         this.form.submit(options);
266     },
267
268     /*
269      * Inherit docs, not using onDisable because it only gets fired
270      * when the component is rendered.
271      */
272     disable: function(silent) {
273         this.callParent(arguments);
274         this.form.getFields().each(function(field) {
275             field.disable();
276         });
277     },
278
279     /*
280      * Inherit docs, not using onEnable because it only gets fired
281      * when the component is rendered.
282      */
283     enable: function(silent) {
284         this.callParent(arguments);
285         this.form.getFields().each(function(field) {
286             field.enable();
287         });
288     },
289
290     /**
291      * Start an interval task to continuously poll all the fields in the form for changes in their
292      * values. This is normally started automatically by setting the {@link #pollForChanges} config.
293      * @param {Number} interval The interval in milliseconds at which the check should run.
294      */
295     startPolling: function(interval) {
296         this.stopPolling();
297         var task = Ext.create('Ext.util.TaskRunner', interval);
298         task.start({
299             interval: 0,
300             run: this.checkChange,
301             scope: this
302         });
303         this.pollTask = task;
304     },
305
306     /**
307      * Stop a running interval task that was started by {@link #startPolling}.
308      */
309     stopPolling: function() {
310         var task = this.pollTask;
311         if (task) {
312             task.stopAll();
313             delete this.pollTask;
314         }
315     },
316
317     /**
318      * Forces each field within the form panel to
319      * {@link Ext.form.field.Field#checkChange check if its value has changed}.
320      */
321     checkChange: function() {
322         this.form.getFields().each(function(field) {
323             field.checkChange();
324         });
325     }
326 });
327