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