Upgrade to ExtJS 4.0.2 - Released 06/09/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  * @class Ext.form.Panel
17  * @extends Ext.panel.Panel
18
19 FormPanel provides a standard container for forms. It is essentially a standard {@link Ext.panel.Panel} which
20 automatically creates a {@link Ext.form.Basic BasicForm} for managing any {@link Ext.form.field.Field}
21 objects that are added as descendants of the panel. It also includes conveniences for configuring and
22 working with the BasicForm and the collection of Fields.
23
24 __Layout__
25
26 By default, FormPanel is configured with `{@link Ext.layout.container.Anchor layout:'anchor'}` for
27 the layout of its immediate child items. This can be changed to any of the supported container layouts.
28 The layout of sub-containers is configured in {@link Ext.container.Container#layout the standard way}.
29
30 __BasicForm__
31
32 Although **not listed** as configuration options of FormPanel, the FormPanel class accepts all
33 of the config options supported by the {@link Ext.form.Basic} class, and will pass them along to
34 the internal BasicForm when it is created.
35
36 **Note**: If subclassing FormPanel, any configuration options for the BasicForm must be applied to
37 the `initialConfig` property of the FormPanel. Applying {@link Ext.form.Basic BasicForm}
38 configuration settings to `this` will *not* affect the BasicForm's configuration.
39
40 The following events fired by the BasicForm will be re-fired by the FormPanel and can therefore be
41 listened for on the FormPanel itself:
42
43 - {@link Ext.form.Basic#beforeaction beforeaction}
44 - {@link Ext.form.Basic#actionfailed actionfailed}
45 - {@link Ext.form.Basic#actioncomplete actioncomplete}
46 - {@link Ext.form.Basic#validitychange validitychange}
47 - {@link Ext.form.Basic#dirtychange dirtychange}
48
49 __Field Defaults__
50
51 The {@link #fieldDefaults} config option conveniently allows centralized configuration of default values
52 for all fields added as descendants of the FormPanel. Any config option recognized by implementations
53 of {@link Ext.form.Labelable} may be included in this object. See the {@link #fieldDefaults} documentation
54 for details of how the defaults are applied.
55
56 __Form Validation__
57
58 With the default configuration, form fields are validated on-the-fly while the user edits their values.
59 This can be controlled on a per-field basis (or via the {@link #fieldDefaults} config) with the field
60 config properties {@link Ext.form.field.Field#validateOnChange} and {@link Ext.form.field.Base#checkChangeEvents},
61 and the FormPanel's config properties {@link #pollForChanges} and {@link #pollInterval}.
62
63 Any component within the FormPanel can be configured with `formBind: true`. This will cause that
64 component to be automatically disabled when the form is invalid, and enabled when it is valid. This is most
65 commonly used for Button components to prevent submitting the form in an invalid state, but can be used on
66 any component type.
67
68 For more information on form validation see the following:
69
70 - {@link Ext.form.field.Field#validateOnChange}
71 - {@link #pollForChanges} and {@link #pollInterval}
72 - {@link Ext.form.field.VTypes}
73 - {@link Ext.form.Basic#doAction BasicForm.doAction clientValidation notes}
74
75 __Form Submission__
76
77 By default, Ext Forms are submitted through Ajax, using {@link Ext.form.action.Action}. See the documentation for
78 {@link Ext.form.Basic} for details.
79 {@img Ext.form.FormPanel/Ext.form.FormPanel.png Ext.form.FormPanel FormPanel component}
80 __Example usage:__
81
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  * @markdown
137  * @docauthor Jason Johnston <jason@sencha.com>
138  */
139 Ext.define('Ext.form.Panel', {
140     extend:'Ext.panel.Panel',
141     mixins: {
142         fieldAncestor: 'Ext.form.FieldAncestor'
143     },
144     alias: 'widget.form',
145     alternateClassName: ['Ext.FormPanel', 'Ext.form.FormPanel'],
146     requires: ['Ext.form.Basic', 'Ext.util.TaskRunner'],
147
148     /**
149      * @cfg {Boolean} pollForChanges
150      * If set to <tt>true</tt>, sets up an interval task (using the {@link #pollInterval}) in which the 
151      * panel's fields are repeatedly checked for changes in their values. This is in addition to the normal detection
152      * each field does on its own input element, and is not needed in most cases. It does, however, provide a
153      * means to absolutely guarantee detection of all changes including some edge cases in some browsers which
154      * do not fire native events. Defaults to <tt>false</tt>.
155      */
156
157     /**
158      * @cfg {Number} pollInterval
159      * Interval in milliseconds at which the form's fields are checked for value changes. Only used if
160      * the {@link #pollForChanges} option is set to <tt>true</tt>. Defaults to 500 milliseconds.
161      */
162
163     /**
164      * @cfg {String} layout The {@link Ext.container.Container#layout} for the form panel's immediate child items.
165      * Defaults to <tt>'anchor'</tt>.
166      */
167     layout: 'anchor',
168
169     ariaRole: 'form',
170
171     initComponent: function() {
172         var me = this;
173         
174         if (me.frame) {
175             me.border = false;
176         }
177         
178         me.initFieldAncestor();
179         me.callParent();
180
181         me.relayEvents(me.form, [
182             'beforeaction',
183             'actionfailed',
184             'actioncomplete',
185             'validitychange',
186             'dirtychange'
187         ]);
188
189         // Start polling if configured
190         if (me.pollForChanges) {
191             me.startPolling(me.pollInterval || 500);
192         }
193     },
194
195     initItems: function() {
196         // Create the BasicForm
197         var me = this;
198         
199         me.form = me.createForm();
200         me.callParent();
201         me.form.initialize();
202     },
203
204     /**
205      * @private
206      */
207     createForm: function() {
208         return Ext.create('Ext.form.Basic', this, Ext.applyIf({listeners: {}}, this.initialConfig));
209     },
210
211     /**
212      * Provides access to the {@link Ext.form.Basic Form} which this Panel contains.
213      * @return {Ext.form.Basic} The {@link Ext.form.Basic Form} which this Panel contains.
214      */
215     getForm: function() {
216         return this.form;
217     },
218     
219     /**
220      * Loads an {@link Ext.data.Model} into this form (internally just calls {@link Ext.form.Basic#loadRecord})
221      * See also {@link #trackResetOnLoad}.
222      * @param {Ext.data.Model} record The record to load
223      * @return {Ext.form.Basic} The Ext.form.Basic attached to this FormPanel
224      */
225     loadRecord: function(record) {
226         return this.getForm().loadRecord(record);
227     },
228     
229     /**
230      * Returns the currently loaded Ext.data.Model instance if one was loaded via {@link #loadRecord}.
231      * @return {Ext.data.Model} The loaded instance
232      */
233     getRecord: function() {
234         return this.getForm().getRecord();
235     },
236     
237     /**
238      * Convenience function for fetching the current value of each field in the form. This is the same as calling
239      * {@link Ext.form.Basic#getValues this.getForm().getValues()}
240      * @return {Object} The current form field values, keyed by field name
241      */
242     getValues: function() {
243         return this.getForm().getValues();
244     },
245
246     beforeDestroy: function() {
247         this.stopPolling();
248         this.form.destroy();
249         this.callParent();
250     },
251
252     /**
253      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#load} call.
254      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#load} and
255      * {@link Ext.form.Basic#doAction} for details)
256      */
257     load: function(options) {
258         this.form.load(options);
259     },
260
261     /**
262      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#submit} call.
263      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#submit} and
264      * {@link Ext.form.Basic#doAction} for details)
265      */
266     submit: function(options) {
267         this.form.submit(options);
268     },
269
270     /*
271      * Inherit docs, not using onDisable because it only gets fired
272      * when the component is rendered.
273      */
274     disable: function(silent) {
275         this.callParent(arguments);
276         this.form.getFields().each(function(field) {
277             field.disable();
278         });
279     },
280
281     /*
282      * Inherit docs, not using onEnable because it only gets fired
283      * when the component is rendered.
284      */
285     enable: function(silent) {
286         this.callParent(arguments);
287         this.form.getFields().each(function(field) {
288             field.enable();
289         });
290     },
291
292     /**
293      * Start an interval task to continuously poll all the fields in the form for changes in their
294      * values. This is normally started automatically by setting the {@link #pollForChanges} config.
295      * @param {Number} interval The interval in milliseconds at which the check should run.
296      */
297     startPolling: function(interval) {
298         this.stopPolling();
299         var task = Ext.create('Ext.util.TaskRunner', interval);
300         task.start({
301             interval: 0,
302             run: this.checkChange,
303             scope: this
304         });
305         this.pollTask = task;
306     },
307
308     /**
309      * Stop a running interval task that was started by {@link #startPolling}.
310      */
311     stopPolling: function() {
312         var task = this.pollTask;
313         if (task) {
314             task.stopAll();
315             delete this.pollTask;
316         }
317     },
318
319     /**
320      * Forces each field within the form panel to 
321      * {@link Ext.form.field.Field#checkChange check if its value has changed}.
322      */
323     checkChange: function() {
324         this.form.getFields().each(function(field) {
325             field.checkChange();
326         });
327     }
328 });
329