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