Upgrade to ExtJS 3.0.0 - Released 07/06/2009
[extjs.git] / src / widgets / form / BasicForm.js
1 /*!
2  * Ext JS Library 3.0.0
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.form.BasicForm
9  * @extends Ext.util.Observable
10  * <p>Encapsulates the DOM &lt;form> element at the heart of the {@link Ext.form.FormPanel FormPanel} class, and provides
11  * input field management, validation, submission, and form loading services.</p>
12  * <p>By default, Ext Forms are submitted through Ajax, using an instance of {@link Ext.form.Action.Submit}.
13  * To enable normal browser submission of an Ext Form, use the {@link #standardSubmit} config option.</p>
14  * <p><b><u>File Uploads</u></b></p>
15  * <p>{@link #fileUpload File uploads} are not performed using Ajax submission, that
16  * is they are <b>not</b> performed using XMLHttpRequests. Instead the form is submitted in the standard
17  * manner with the DOM <tt>&lt;form></tt> element temporarily modified to have its
18  * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
19  * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
20  * but removed after the return data has been gathered.</p>
21  * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
22  * server is using JSON to send the return object, then the
23  * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
24  * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
25  * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
26  * "&lt;" as "&amp;lt;", "&amp;" as "&amp;amp;" etc.</p>
27  * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
28  * is created containing a <tt>responseText</tt> property in order to conform to the
29  * requirements of event handlers and callbacks.</p>
30  * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
31  * and some server technologies (notably JEE) may require some custom processing in order to
32  * retrieve parameter names and parameter values from the packet content.</p>
33  * @constructor
34  * @param {Mixed} el The form element or its id
35  * @param {Object} config Configuration options
36  */
37 Ext.form.BasicForm = function(el, config){
38     Ext.apply(this, config);
39     if(Ext.isString(this.paramOrder)){
40         this.paramOrder = this.paramOrder.split(/[\s,|]/);
41     }
42     /*
43      * @property items
44      * A {@link Ext.util.MixedCollection MixedCollection) containing all the Ext.form.Fields in this form.
45      * @type MixedCollection
46      */
47     this.items = new Ext.util.MixedCollection(false, function(o){
48         return o.itemId || o.id || (o.id = Ext.id());
49     });
50     this.addEvents(
51         /**
52          * @event beforeaction
53          * Fires before any action is performed. Return false to cancel the action.
54          * @param {Form} this
55          * @param {Action} action The {@link Ext.form.Action} to be performed
56          */
57         'beforeaction',
58         /**
59          * @event actionfailed
60          * Fires when an action fails.
61          * @param {Form} this
62          * @param {Action} action The {@link Ext.form.Action} that failed
63          */
64         'actionfailed',
65         /**
66          * @event actioncomplete
67          * Fires when an action is completed.
68          * @param {Form} this
69          * @param {Action} action The {@link Ext.form.Action} that completed
70          */
71         'actioncomplete'
72     );
73
74     if(el){
75         this.initEl(el);
76     }
77     Ext.form.BasicForm.superclass.constructor.call(this);
78 };
79
80 Ext.extend(Ext.form.BasicForm, Ext.util.Observable, {
81     /**
82      * @cfg {String} method
83      * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
84      */
85     /**
86      * @cfg {DataReader} reader
87      * An Ext.data.DataReader (e.g. {@link Ext.data.XmlReader}) to be used to read
88      * data when executing 'load' actions. This is optional as there is built-in
89      * support for processing JSON.  For additional information on using an XMLReader
90      * see the example provided in examples/form/xml-form.html.
91      */
92     /**
93      * @cfg {DataReader} errorReader
94      * <p>An Ext.data.DataReader (e.g. {@link Ext.data.XmlReader}) to be used to
95      * read field error messages returned from 'submit' actions. This is optional
96      * as there is built-in support for processing JSON.</p>
97      * <p>The Records which provide messages for the invalid Fields must use the
98      * Field name (or id) as the Record ID, and must contain a field called 'msg'
99      * which contains the error message.</p>
100      * <p>The errorReader does not have to be a full-blown implementation of a
101      * DataReader. It simply needs to implement a <tt>read(xhr)</tt> function
102      * which returns an Array of Records in an object with the following
103      * structure:</p><pre><code>
104 {
105     records: recordArray
106 }
107 </code></pre>
108      */
109     /**
110      * @cfg {String} url
111      * The URL to use for form actions if one isn't supplied in the
112      * <code>{@link #doAction doAction} options</code>.
113      */
114     /**
115      * @cfg {Boolean} fileUpload
116      * Set to true if this form is a file upload.
117      * <p>File uploads are not performed using normal 'Ajax' techniques, that is they are <b>not</b>
118      * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
119      * DOM <tt>&lt;form></tt> element temporarily modified to have its
120      * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
121      * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
122      * but removed after the return data has been gathered.</p>
123      * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
124      * server is using JSON to send the return object, then the
125      * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
126      * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
127      * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
128      * "&lt;" as "&amp;lt;", "&amp;" as "&amp;amp;" etc.</p>
129      * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
130      * is created containing a <tt>responseText</tt> property in order to conform to the
131      * requirements of event handlers and callbacks.</p>
132      * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
133      * and some server technologies (notably JEE) may require some custom processing in order to
134      * retrieve parameter names and parameter values from the packet content.</p>
135      */
136     /**
137      * @cfg {Object} baseParams
138      * <p>Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.</p>
139      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p>
140      */
141     /**
142      * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
143      */
144     timeout: 30,
145
146     /**
147      * @cfg {Object} api (Optional) If specified load and submit actions will be handled
148      * with {@link Ext.form.Action.DirectLoad} and {@link Ext.form.Action.DirectSubmit}.
149      * Methods which have been imported by Ext.Direct can be specified here to load and submit
150      * forms.
151      * Such as the following:<pre><code>
152 api: {
153     load: App.ss.MyProfile.load,
154     submit: App.ss.MyProfile.submit
155 }
156 </code></pre>
157      * <p>Load actions can use <code>{@link #paramOrder}</code> or <code>{@link #paramsAsHash}</code>
158      * to customize how the load method is invoked.
159      * Submit actions will always use a standard form submit. The formHandler configuration must
160      * be set on the associated server-side method which has been imported by Ext.Direct</p>
161      */
162
163     /**
164      * @cfg {Array/String} paramOrder <p>A list of params to be executed server side.
165      * Defaults to <tt>undefined</tt>. Only used for the <code>{@link #api}</code>
166      * <code>load</code> configuration.</p>
167      * <br><p>Specify the params in the order in which they must be executed on the
168      * server-side as either (1) an Array of String values, or (2) a String of params
169      * delimited by either whitespace, comma, or pipe. For example,
170      * any of the following would be acceptable:</p><pre><code>
171 paramOrder: ['param1','param2','param3']
172 paramOrder: 'param1 param2 param3'
173 paramOrder: 'param1,param2,param3'
174 paramOrder: 'param1|param2|param'
175      </code></pre>
176      */
177     paramOrder: undefined,
178
179     /**
180      * @cfg {Boolean} paramsAsHash Only used for the <code>{@link #api}</code>
181      * <code>load</code> configuration. Send parameters as a collection of named
182      * arguments (defaults to <tt>false</tt>). Providing a
183      * <tt>{@link #paramOrder}</tt> nullifies this configuration.
184      */
185     paramsAsHash: false,
186
187
188     // private
189     activeAction : null,
190
191     /**
192      * @cfg {Boolean} trackResetOnLoad If set to <tt>true</tt>, {@link #reset}() resets to the last loaded
193      * or {@link #setValues}() data instead of when the form was first created.  Defaults to <tt>false</tt>.
194      */
195     trackResetOnLoad : false,
196
197     /**
198      * @cfg {Boolean} standardSubmit If set to true, standard HTML form submits are used instead of XHR (Ajax) style
199      * form submissions. (defaults to false)<br>
200      * <p><b>Note:</b> When using standardSubmit, the options to {@link #submit} are ignored because Ext's
201      * Ajax infrastracture is bypassed. To pass extra parameters (baseParams and params), you will need to
202      * create hidden fields within the form.</p>
203      * <p>The url config option is also bypassed, so set the action as well:</p>
204      * <pre><code>
205 PANEL.getForm().getEl().dom.action = 'URL'
206      * </code></pre>
207      * An example encapsulating the above:
208      * <pre><code>
209 new Ext.FormPanel({
210     standardSubmit: true,
211     baseParams: {
212         foo: 'bar'
213     },
214     url: 'myProcess.php',
215     items: [{
216         xtype: 'textfield',
217         name: 'userName'
218     }],
219     buttons: [{
220         text: 'Save',
221         handler: function(){
222             var O = this.ownerCt;
223             if (O.getForm().isValid()) {
224                 if (O.url)
225                     O.getForm().getEl().dom.action = O.url;
226                 if (O.baseParams) {
227                     for (i in O.baseParams) {
228                         O.add({
229                             xtype: 'hidden',
230                             name: i,
231                             value: O.baseParams[i]
232                         })
233                     }
234                     O.doLayout();
235                 }
236                 O.getForm().submit();
237             }
238         }
239     }]
240 });
241      * </code></pre>
242      */
243     /**
244      * By default wait messages are displayed with Ext.MessageBox.wait. You can target a specific
245      * element by passing it or its id or mask the form itself by passing in true.
246      * @type Mixed
247      * @property waitMsgTarget
248      */
249
250     // private
251     initEl : function(el){
252         this.el = Ext.get(el);
253         this.id = this.el.id || Ext.id();
254         if(!this.standardSubmit){
255             this.el.on('submit', this.onSubmit, this);
256         }
257         this.el.addClass('x-form');
258     },
259
260     /**
261      * Get the HTML form Element
262      * @return Ext.Element
263      */
264     getEl: function(){
265         return this.el;
266     },
267
268     // private
269     onSubmit : function(e){
270         e.stopEvent();
271     },
272
273     // private
274     destroy: function() {
275         this.items.each(function(f){
276             Ext.destroy(f);
277         });
278         if(this.el){
279             this.el.removeAllListeners();
280             this.el.remove();
281         }
282         this.purgeListeners();
283     },
284
285     /**
286      * Returns true if client-side validation on the form is successful.
287      * @return Boolean
288      */
289     isValid : function(){
290         var valid = true;
291         this.items.each(function(f){
292            if(!f.validate()){
293                valid = false;
294            }
295         });
296         return valid;
297     },
298
299     /**
300      * <p>Returns true if any fields in this form have changed from their original values.</p>
301      * <p>Note that if this BasicForm was configured with {@link #trackResetOnLoad} then the
302      * Fields' <i>original values</i> are updated when the values are loaded by {@link #setValues}
303      * or {@link #loadRecord}.</p>
304      * @return Boolean
305      */
306     isDirty : function(){
307         var dirty = false;
308         this.items.each(function(f){
309            if(f.isDirty()){
310                dirty = true;
311                return false;
312            }
313         });
314         return dirty;
315     },
316
317     /**
318      * Performs a predefined action ({@link Ext.form.Action.Submit} or
319      * {@link Ext.form.Action.Load}) or a custom extension of {@link Ext.form.Action}
320      * to perform application-specific processing.
321      * @param {String/Object} actionName The name of the predefined action type,
322      * or instance of {@link Ext.form.Action} to perform.
323      * @param {Object} options (optional) The options to pass to the {@link Ext.form.Action}.
324      * All of the config options listed below are supported by both the
325      * {@link Ext.form.Action.Submit submit} and {@link Ext.form.Action.Load load}
326      * actions unless otherwise noted (custom actions could also accept
327      * other config options):<ul>
328      *
329      * <li><b>url</b> : String<div class="sub-desc">The url for the action (defaults
330      * to the form's {@link #url}.)</div></li>
331      *
332      * <li><b>method</b> : String<div class="sub-desc">The form method to use (defaults
333      * to the form's method, or POST if not defined)</div></li>
334      *
335      * <li><b>params</b> : String/Object<div class="sub-desc"><p>The params to pass
336      * (defaults to the form's baseParams, or none if not defined)</p>
337      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p></div></li>
338      *
339      * <li><b>headers</b> : Object<div class="sub-desc">Request headers to set for the action
340      * (defaults to the form's default headers)</div></li>
341      *
342      * <li><b>success</b> : Function<div class="sub-desc">The callback that will
343      * be invoked after a successful response (see top of
344      * {@link Ext.form.Action.Submit submit} and {@link Ext.form.Action.Load load}
345      * for a description of what constitutes a successful response).
346      * The function is passed the following parameters:<ul>
347      * <li><tt>form</tt> : Ext.form.BasicForm<div class="sub-desc">The form that requested the action</div></li>
348      * <li><tt>action</tt> : The {@link Ext.form.Action Action} object which performed the operation.
349      * <div class="sub-desc">The action object contains these properties of interest:<ul>
350      * <li><tt>{@link Ext.form.Action#response response}</tt></li>
351      * <li><tt>{@link Ext.form.Action#result result}</tt> : interrogate for custom postprocessing</li>
352      * <li><tt>{@link Ext.form.Action#type type}</tt></li>
353      * </ul></div></li></ul></div></li>
354      *
355      * <li><b>failure</b> : Function<div class="sub-desc">The callback that will be invoked after a
356      * failed transaction attempt. The function is passed the following parameters:<ul>
357      * <li><tt>form</tt> : The {@link Ext.form.BasicForm} that requested the action.</li>
358      * <li><tt>action</tt> : The {@link Ext.form.Action Action} object which performed the operation.
359      * <div class="sub-desc">The action object contains these properties of interest:<ul>
360      * <li><tt>{@link Ext.form.Action#failureType failureType}</tt></li>
361      * <li><tt>{@link Ext.form.Action#response response}</tt></li>
362      * <li><tt>{@link Ext.form.Action#result result}</tt> : interrogate for custom postprocessing</li>
363      * <li><tt>{@link Ext.form.Action#type type}</tt></li>
364      * </ul></div></li></ul></div></li>
365      *
366      * <li><b>scope</b> : Object<div class="sub-desc">The scope in which to call the
367      * callback functions (The <tt>this</tt> reference for the callback functions).</div></li>
368      *
369      * <li><b>clientValidation</b> : Boolean<div class="sub-desc">Submit Action only.
370      * Determines whether a Form's fields are validated in a final call to
371      * {@link Ext.form.BasicForm#isValid isValid} prior to submission. Set to <tt>false</tt>
372      * to prevent this. If undefined, pre-submission field validation is performed.</div></li></ul>
373      *
374      * @return {BasicForm} this
375      */
376     doAction : function(action, options){
377         if(Ext.isString(action)){
378             action = new Ext.form.Action.ACTION_TYPES[action](this, options);
379         }
380         if(this.fireEvent('beforeaction', this, action) !== false){
381             this.beforeAction(action);
382             action.run.defer(100, action);
383         }
384         return this;
385     },
386
387     /**
388      * Shortcut to {@link #doAction do} a {@link Ext.form.Action.Submit submit action}.
389      * @param {Object} options The options to pass to the action (see {@link #doAction} for details).<br>
390      * <p><b>Note:</b> this is ignored when using the {@link #standardSubmit} option.</p>
391      * <p>The following code:</p><pre><code>
392 myFormPanel.getForm().submit({
393     clientValidation: true,
394     url: 'updateConsignment.php',
395     params: {
396         newStatus: 'delivered'
397     },
398     success: function(form, action) {
399        Ext.Msg.alert('Success', action.result.msg);
400     },
401     failure: function(form, action) {
402         switch (action.failureType) {
403             case Ext.form.Action.CLIENT_INVALID:
404                 Ext.Msg.alert('Failure', 'Form fields may not be submitted with invalid values');
405                 break;
406             case Ext.form.Action.CONNECT_FAILURE:
407                 Ext.Msg.alert('Failure', 'Ajax communication failed');
408                 break;
409             case Ext.form.Action.SERVER_INVALID:
410                Ext.Msg.alert('Failure', action.result.msg);
411        }
412     }
413 });
414 </code></pre>
415      * would process the following server response for a successful submission:<pre><code>
416 {
417     "success":true, // note this is Boolean, not string
418     "msg":"Consignment updated"
419 }
420 </code></pre>
421      * and the following server response for a failed submission:<pre><code>
422 {
423     "success":false, // note this is Boolean, not string
424     "msg":"You do not have permission to perform this operation"
425 }
426 </code></pre>
427      * @return {BasicForm} this
428      */
429     submit : function(options){
430         if(this.standardSubmit){
431             var v = this.isValid();
432             if(v){
433                 this.el.dom.submit();
434             }
435             return v;
436         }
437         var submitAction = String.format('{0}submit', this.api ? 'direct' : '');
438         this.doAction(submitAction, options);
439         return this;
440     },
441
442     /**
443      * Shortcut to {@link #doAction do} a {@link Ext.form.Action.Load load action}.
444      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
445      * @return {BasicForm} this
446      */
447     load : function(options){
448         var loadAction = String.format('{0}load', this.api ? 'direct' : '');
449         this.doAction(loadAction, options);
450         return this;
451     },
452
453     /**
454      * Persists the values in this form into the passed {@link Ext.data.Record} object in a beginEdit/endEdit block.
455      * @param {Record} record The record to edit
456      * @return {BasicForm} this
457      */
458     updateRecord : function(record){
459         record.beginEdit();
460         var fs = record.fields;
461         fs.each(function(f){
462             var field = this.findField(f.name);
463             if(field){
464                 record.set(f.name, field.getValue());
465             }
466         }, this);
467         record.endEdit();
468         return this;
469     },
470
471     /**
472      * Loads an {@link Ext.data.Record} into this form by calling {@link #setValues} with the
473      * {@link Ext.data.Record#data record data}.
474      * See also {@link #trackResetOnLoad}.
475      * @param {Record} record The record to load
476      * @return {BasicForm} this
477      */
478     loadRecord : function(record){
479         this.setValues(record.data);
480         return this;
481     },
482
483     // private
484     beforeAction : function(action){
485         var o = action.options;
486         if(o.waitMsg){
487             if(this.waitMsgTarget === true){
488                 this.el.mask(o.waitMsg, 'x-mask-loading');
489             }else if(this.waitMsgTarget){
490                 this.waitMsgTarget = Ext.get(this.waitMsgTarget);
491                 this.waitMsgTarget.mask(o.waitMsg, 'x-mask-loading');
492             }else{
493                 Ext.MessageBox.wait(o.waitMsg, o.waitTitle || this.waitTitle || 'Please Wait...');
494             }
495         }
496     },
497
498     // private
499     afterAction : function(action, success){
500         this.activeAction = null;
501         var o = action.options;
502         if(o.waitMsg){
503             if(this.waitMsgTarget === true){
504                 this.el.unmask();
505             }else if(this.waitMsgTarget){
506                 this.waitMsgTarget.unmask();
507             }else{
508                 Ext.MessageBox.updateProgress(1);
509                 Ext.MessageBox.hide();
510             }
511         }
512         if(success){
513             if(o.reset){
514                 this.reset();
515             }
516             Ext.callback(o.success, o.scope, [this, action]);
517             this.fireEvent('actioncomplete', this, action);
518         }else{
519             Ext.callback(o.failure, o.scope, [this, action]);
520             this.fireEvent('actionfailed', this, action);
521         }
522     },
523
524     /**
525      * Find a {@link Ext.form.Field} in this form.
526      * @param {String} id The value to search for (specify either a {@link Ext.Component#id id},
527      * {@link Ext.grid.Column#dataIndex dataIndex}, {@link Ext.form.Field#getName name or hiddenName}).
528      * @return Field
529      */
530     findField : function(id){
531         var field = this.items.get(id);
532         if(!Ext.isObject(field)){
533             this.items.each(function(f){
534                 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
535                     field = f;
536                     return false;
537                 }
538             });
539         }
540         return field || null;
541     },
542
543
544     /**
545      * Mark fields in this form invalid in bulk.
546      * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
547      * @return {BasicForm} this
548      */
549     markInvalid : function(errors){
550         if(Ext.isArray(errors)){
551             for(var i = 0, len = errors.length; i < len; i++){
552                 var fieldError = errors[i];
553                 var f = this.findField(fieldError.id);
554                 if(f){
555                     f.markInvalid(fieldError.msg);
556                 }
557             }
558         }else{
559             var field, id;
560             for(id in errors){
561                 if(!Ext.isFunction(errors[id]) && (field = this.findField(id))){
562                     field.markInvalid(errors[id]);
563                 }
564             }
565         }
566         return this;
567     },
568
569     /**
570      * Set values for fields in this form in bulk.
571      * @param {Array/Object} values Either an array in the form:<pre><code>
572 [{id:'clientName', value:'Fred. Olsen Lines'},
573  {id:'portOfLoading', value:'FXT'},
574  {id:'portOfDischarge', value:'OSL'} ]</code></pre>
575      * or an object hash of the form:<pre><code>
576 {
577     clientName: 'Fred. Olsen Lines',
578     portOfLoading: 'FXT',
579     portOfDischarge: 'OSL'
580 }</code></pre>
581      * @return {BasicForm} this
582      */
583     setValues : function(values){
584         if(Ext.isArray(values)){ // array of objects
585             for(var i = 0, len = values.length; i < len; i++){
586                 var v = values[i];
587                 var f = this.findField(v.id);
588                 if(f){
589                     f.setValue(v.value);
590                     if(this.trackResetOnLoad){
591                         f.originalValue = f.getValue();
592                     }
593                 }
594             }
595         }else{ // object hash
596             var field, id;
597             for(id in values){
598                 if(!Ext.isFunction(values[id]) && (field = this.findField(id))){
599                     field.setValue(values[id]);
600                     if(this.trackResetOnLoad){
601                         field.originalValue = field.getValue();
602                     }
603                 }
604             }
605         }
606         return this;
607     },
608
609     /**
610      * <p>Returns the fields in this form as an object with key/value pairs as they would be submitted using a standard form submit.
611      * If multiple fields exist with the same name they are returned as an array.</p>
612      * <p><b>Note:</b> The values are collected from all enabled HTML input elements within the form, <u>not</u> from
613      * the Ext Field objects. This means that all returned values are Strings (or Arrays of Strings) and that the
614      * value can potentially be the emptyText of a field.</p>
615      * @param {Boolean} asString (optional) Pass true to return the values as a string. (defaults to false, returning an Object)
616      * @return {String/Object}
617      */
618     getValues : function(asString){
619         var fs = Ext.lib.Ajax.serializeForm(this.el.dom);
620         if(asString === true){
621             return fs;
622         }
623         return Ext.urlDecode(fs);
624     },
625
626     getFieldValues : function(){
627         var o = {};
628         this.items.each(function(f){
629            o[f.getName()] = f.getValue();
630         });
631         return o;
632     },
633
634     /**
635      * Clears all invalid messages in this form.
636      * @return {BasicForm} this
637      */
638     clearInvalid : function(){
639         this.items.each(function(f){
640            f.clearInvalid();
641         });
642         return this;
643     },
644
645     /**
646      * Resets this form.
647      * @return {BasicForm} this
648      */
649     reset : function(){
650         this.items.each(function(f){
651             f.reset();
652         });
653         return this;
654     },
655
656     /**
657      * Add Ext.form Components to this form's Collection. This does not result in rendering of
658      * the passed Component, it just enables the form to validate Fields, and distribute values to
659      * Fields.
660      * <p><b>You will not usually call this function. In order to be rendered, a Field must be added
661      * to a {@link Ext.Container Container}, usually an {@link Ext.form.FormPanel FormPanel}.
662      * The FormPanel to which the field is added takes care of adding the Field to the BasicForm's
663      * collection.</b></p>
664      * @param {Field} field1
665      * @param {Field} field2 (optional)
666      * @param {Field} etc (optional)
667      * @return {BasicForm} this
668      */
669     add : function(){
670         this.items.addAll(Array.prototype.slice.call(arguments, 0));
671         return this;
672     },
673
674
675     /**
676      * Removes a field from the items collection (does NOT remove its markup).
677      * @param {Field} field
678      * @return {BasicForm} this
679      */
680     remove : function(field){
681         this.items.remove(field);
682         return this;
683     },
684
685     /**
686      * Iterates through the {@link Ext.form.Field Field}s which have been {@link #add add}ed to this BasicForm,
687      * checks them for an id attribute, and calls {@link Ext.form.Field#applyToMarkup} on the existing dom element with that id.
688      * @return {BasicForm} this
689      */
690     render : function(){
691         this.items.each(function(f){
692             if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
693                 f.applyToMarkup(f.id);
694             }
695         });
696         return this;
697     },
698
699     /**
700      * Calls {@link Ext#apply} for all fields in this form with the passed object.
701      * @param {Object} values
702      * @return {BasicForm} this
703      */
704     applyToFields : function(o){
705         this.items.each(function(f){
706            Ext.apply(f, o);
707         });
708         return this;
709     },
710
711     /**
712      * Calls {@link Ext#applyIf} for all field in this form with the passed object.
713      * @param {Object} values
714      * @return {BasicForm} this
715      */
716     applyIfToFields : function(o){
717         this.items.each(function(f){
718            Ext.applyIf(f, o);
719         });
720         return this;
721     },
722
723     callFieldMethod : function(fnName, args){
724         args = args || [];
725         this.items.each(function(f){
726             if(Ext.isFunction(f[fnName])){
727                 f[fnName].apply(f, args);
728             }
729         });
730         return this;
731     }
732 });
733
734 // back compat
735 Ext.BasicForm = Ext.form.BasicForm;