major doc changes
[roojs1] / Roo / form / Form.js
1 /*
2  * Based on:
3  * Ext JS Library 1.1.1
4  * Copyright(c) 2006-2007, Ext JS, LLC.
5  *
6  * Originally Released Under LGPL - original licence link has changed is not relivant.
7  *
8  * Fork - LGPL
9  * <script type="text/javascript">
10  */
11
12 /**
13  * @class Roo.form.Form
14  * @extends Roo.form.BasicForm
15  * @children Roo.form.Column Roo.form.FieldSet Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem
16  * Adds the ability to dynamically render forms with JavaScript to {@link Roo.form.BasicForm}.
17  * @constructor
18  * @param {Object} config Configuration options
19  */
20 Roo.form.Form = function(config){
21     var xitems =  [];
22     if (config.items) {
23         xitems = config.items;
24         delete config.items;
25     }
26    
27     
28     Roo.form.Form.superclass.constructor.call(this, null, config);
29     this.url = this.url || this.action;
30     if(!this.root){
31         this.root = new Roo.form.Layout(Roo.applyIf({
32             id: Roo.id()
33         }, config));
34     }
35     this.active = this.root;
36     /**
37      * Array of all the buttons that have been added to this form via {@link addButton}
38      * @type Array
39      */
40     this.buttons = [];
41     this.allItems = [];
42     this.addEvents({
43         /**
44          * @event clientvalidation
45          * If the monitorValid config option is true, this event fires repetitively to notify of valid state
46          * @param {Form} this
47          * @param {Boolean} valid true if the form has passed client-side validation
48          */
49         clientvalidation: true,
50         /**
51          * @event rendered
52          * Fires when the form is rendered
53          * @param {Roo.form.Form} form
54          */
55         rendered : true
56     });
57     
58     if (this.progressUrl) {
59             // push a hidden field onto the list of fields..
60             this.addxtype( {
61                     xns: Roo.form, 
62                     xtype : 'Hidden', 
63                     name : 'UPLOAD_IDENTIFIER' 
64             });
65         }
66         
67     
68     Roo.each(xitems, this.addxtype, this);
69     
70 };
71
72 Roo.extend(Roo.form.Form, Roo.form.BasicForm, {
73      /**
74      * @cfg {Roo.Button} buttons[] buttons at bottom of form
75      */
76     
77     /**
78      * @cfg {Number} labelWidth The width of labels. This property cascades to child containers.
79      */
80     /**
81      * @cfg {String} itemCls A css class to apply to the x-form-item of fields. This property cascades to child containers.
82      */
83     /**
84      * @cfg {String} buttonAlign Valid values are "left," "center" and "right" (defaults to "center")
85      */
86     buttonAlign:'center',
87
88     /**
89      * @cfg {Number} minButtonWidth Minimum width of all buttons in pixels (defaults to 75)
90      */
91     minButtonWidth:75,
92
93     /**
94      * @cfg {String} labelAlign Valid values are "left," "top" and "right" (defaults to "left").
95      * This property cascades to child containers if not set.
96      */
97     labelAlign:'left',
98
99     /**
100      * @cfg {Boolean} monitorValid If true the form monitors its valid state <b>client-side</b> and
101      * fires a looping event with that state. This is required to bind buttons to the valid
102      * state using the config value formBind:true on the button.
103      */
104     monitorValid : false,
105
106     /**
107      * @cfg {Number} monitorPoll The milliseconds to poll valid state, ignored if monitorValid is not true (defaults to 200)
108      */
109     monitorPoll : 200,
110     
111     /**
112      * @cfg {String} progressUrl - Url to return progress data 
113      */
114     
115     progressUrl : false,
116     /**
117      * @cfg {boolean|FormData} formData - true to use new 'FormData' post, or set to a new FormData({dom form}) Object, if
118      * sending a formdata with extra parameters - eg uploaded elements.
119      */
120     
121     formData : false,
122     
123     /**
124      * Opens a new {@link Roo.form.Column} container in the layout stack. If fields are passed after the config, the
125      * fields are added and the column is closed. If no fields are passed the column remains open
126      * until end() is called.
127      * @param {Object} config The config to pass to the column
128      * @param {Field} field1 (optional)
129      * @param {Field} field2 (optional)
130      * @param {Field} etc (optional)
131      * @return Column The column container object
132      */
133     column : function(c){
134         var col = new Roo.form.Column(c);
135         this.start(col);
136         if(arguments.length > 1){ // duplicate code required because of Opera
137             this.add.apply(this, Array.prototype.slice.call(arguments, 1));
138             this.end();
139         }
140         return col;
141     },
142
143     /**
144      * Opens a new {@link Roo.form.FieldSet} container in the layout stack. If fields are passed after the config, the
145      * fields are added and the fieldset is closed. If no fields are passed the fieldset remains open
146      * until end() is called.
147      * @param {Object} config The config to pass to the fieldset
148      * @param {Field} field1 (optional)
149      * @param {Field} field2 (optional)
150      * @param {Field} etc (optional)
151      * @return FieldSet The fieldset container object
152      */
153     fieldset : function(c){
154         var fs = new Roo.form.FieldSet(c);
155         this.start(fs);
156         if(arguments.length > 1){ // duplicate code required because of Opera
157             this.add.apply(this, Array.prototype.slice.call(arguments, 1));
158             this.end();
159         }
160         return fs;
161     },
162
163     /**
164      * Opens a new {@link Roo.form.Layout} container in the layout stack. If fields are passed after the config, the
165      * fields are added and the container is closed. If no fields are passed the container remains open
166      * until end() is called.
167      * @param {Object} config The config to pass to the Layout
168      * @param {Field} field1 (optional)
169      * @param {Field} field2 (optional)
170      * @param {Field} etc (optional)
171      * @return Layout The container object
172      */
173     container : function(c){
174         var l = new Roo.form.Layout(c);
175         this.start(l);
176         if(arguments.length > 1){ // duplicate code required because of Opera
177             this.add.apply(this, Array.prototype.slice.call(arguments, 1));
178             this.end();
179         }
180         return l;
181     },
182
183     /**
184      * Opens the passed container in the layout stack. The container can be any {@link Roo.form.Layout} or subclass.
185      * @param {Object} container A Roo.form.Layout or subclass of Layout
186      * @return {Form} this
187      */
188     start : function(c){
189         // cascade label info
190         Roo.applyIf(c, {'labelAlign': this.active.labelAlign, 'labelWidth': this.active.labelWidth, 'itemCls': this.active.itemCls});
191         this.active.stack.push(c);
192         c.ownerCt = this.active;
193         this.active = c;
194         return this;
195     },
196
197     /**
198      * Closes the current open container
199      * @return {Form} this
200      */
201     end : function(){
202         if(this.active == this.root){
203             return this;
204         }
205         this.active = this.active.ownerCt;
206         return this;
207     },
208
209     /**
210      * Add Roo.form components to the current open container (e.g. column, fieldset, etc.).  Fields added via this method
211      * can also be passed with an additional property of fieldLabel, which if supplied, will provide the text to display
212      * as the label of the field.
213      * @param {Field} field1
214      * @param {Field} field2 (optional)
215      * @param {Field} etc. (optional)
216      * @return {Form} this
217      */
218     add : function(){
219         this.active.stack.push.apply(this.active.stack, arguments);
220         this.allItems.push.apply(this.allItems,arguments);
221         var r = [];
222         for(var i = 0, a = arguments, len = a.length; i < len; i++) {
223             if(a[i].isFormField){
224                 r.push(a[i]);
225             }
226         }
227         if(r.length > 0){
228             Roo.form.Form.superclass.add.apply(this, r);
229         }
230         return this;
231     },
232     
233
234     
235     
236     
237      /**
238      * Find any element that has been added to a form, using it's ID or name
239      * This can include framesets, columns etc. along with regular fields..
240      * @param {String} id - id or name to find.
241      
242      * @return {Element} e - or false if nothing found.
243      */
244     findbyId : function(id)
245     {
246         var ret = false;
247         if (!id) {
248             return ret;
249         }
250         Roo.each(this.allItems, function(f){
251             if (f.id == id || f.name == id ){
252                 ret = f;
253                 return false;
254             }
255         });
256         return ret;
257     },
258
259     
260     
261     /**
262      * Render this form into the passed container. This should only be called once!
263      * @param {String/HTMLElement/Element} container The element this component should be rendered into
264      * @return {Form} this
265      */
266     render : function(ct)
267     {
268         
269         
270         
271         ct = Roo.get(ct);
272         var o = this.autoCreate || {
273             tag: 'form',
274             method : this.method || 'POST',
275             id : this.id || Roo.id()
276         };
277         this.initEl(ct.createChild(o));
278
279         this.root.render(this.el);
280         
281        
282              
283         this.items.each(function(f){
284             f.render('x-form-el-'+f.id);
285         });
286
287         if(this.buttons.length > 0){
288             // tables are required to maintain order and for correct IE layout
289             var tb = this.el.createChild({cls:'x-form-btns-ct', cn: {
290                 cls:"x-form-btns x-form-btns-"+this.buttonAlign,
291                 html:'<table cellspacing="0"><tbody><tr></tr></tbody></table><div class="x-clear"></div>'
292             }}, null, true);
293             var tr = tb.getElementsByTagName('tr')[0];
294             for(var i = 0, len = this.buttons.length; i < len; i++) {
295                 var b = this.buttons[i];
296                 var td = document.createElement('td');
297                 td.className = 'x-form-btn-td';
298                 b.render(tr.appendChild(td));
299             }
300         }
301         if(this.monitorValid){ // initialize after render
302             this.startMonitoring();
303         }
304         this.fireEvent('rendered', this);
305         return this;
306     },
307
308     /**
309      * Adds a button to the footer of the form - this <b>must</b> be called before the form is rendered.
310      * @param {String/Object} config A string becomes the button text, an object can either be a Button config
311      * object or a valid Roo.DomHelper element config
312      * @param {Function} handler The function called when the button is clicked
313      * @param {Object} scope (optional) The scope of the handler function
314      * @return {Roo.Button}
315      */
316     addButton : function(config, handler, scope){
317         var bc = {
318             handler: handler,
319             scope: scope,
320             minWidth: this.minButtonWidth,
321             hideParent:true
322         };
323         if(typeof config == "string"){
324             bc.text = config;
325         }else{
326             Roo.apply(bc, config);
327         }
328         var btn = new Roo.Button(null, bc);
329         this.buttons.push(btn);
330         return btn;
331     },
332
333      /**
334      * Adds a series of form elements (using the xtype property as the factory method.
335      * Valid xtypes are:  TextField, TextArea .... Button, Layout, FieldSet, Column, (and 'end' to close a block)
336      * @param {Object} config 
337      */
338     
339     addxtype : function()
340     {
341         var ar = Array.prototype.slice.call(arguments, 0);
342         var ret = false;
343         for(var i = 0; i < ar.length; i++) {
344             if (!ar[i]) {
345                 continue; // skip -- if this happends something invalid got sent, we 
346                 // should ignore it, as basically that interface element will not show up
347                 // and that should be pretty obvious!!
348             }
349             
350             if (Roo.form[ar[i].xtype]) {
351                 ar[i].form = this;
352                 var fe = Roo.factory(ar[i], Roo.form);
353                 if (!ret) {
354                     ret = fe;
355                 }
356                 fe.form = this;
357                 if (fe.store) {
358                     fe.store.form = this;
359                 }
360                 if (fe.isLayout) {  
361                          
362                     this.start(fe);
363                     this.allItems.push(fe);
364                     if (fe.items && fe.addxtype) {
365                         fe.addxtype.apply(fe, fe.items);
366                         delete fe.items;
367                     }
368                      this.end();
369                     continue;
370                 }
371                 
372                 
373                  
374                 this.add(fe);
375               //  console.log('adding ' + ar[i].xtype);
376             }
377             if (ar[i].xtype == 'Button') {  
378                 //console.log('adding button');
379                 //console.log(ar[i]);
380                 this.addButton(ar[i]);
381                 this.allItems.push(fe);
382                 continue;
383             }
384             
385             if (ar[i].xtype == 'end') { // so we can add fieldsets... / layout etc.
386                 alert('end is not supported on xtype any more, use items');
387             //    this.end();
388             //    //console.log('adding end');
389             }
390             
391         }
392         return ret;
393     },
394     
395     /**
396      * Starts monitoring of the valid state of this form. Usually this is done by passing the config
397      * option "monitorValid"
398      */
399     startMonitoring : function(){
400         if(!this.bound){
401             this.bound = true;
402             Roo.TaskMgr.start({
403                 run : this.bindHandler,
404                 interval : this.monitorPoll || 200,
405                 scope: this
406             });
407         }
408     },
409
410     /**
411      * Stops monitoring of the valid state of this form
412      */
413     stopMonitoring : function(){
414         this.bound = false;
415     },
416
417     // private
418     bindHandler : function(){
419         if(!this.bound){
420             return false; // stops binding
421         }
422         var valid = true;
423         this.items.each(function(f){
424             if(!f.isValid(true)){
425                 valid = false;
426                 return false;
427             }
428         });
429         for(var i = 0, len = this.buttons.length; i < len; i++){
430             var btn = this.buttons[i];
431             if(btn.formBind === true && btn.disabled === valid){
432                 btn.setDisabled(!valid);
433             }
434         }
435         this.fireEvent('clientvalidation', this, valid);
436     }
437     
438     
439     
440     
441     
442     
443     
444     
445 });
446
447
448 // back compat
449 Roo.Form = Roo.form.Form;