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