345ebd32ac183b75869eb730078670a6d3308ec8
[roojs1] / Roo / bootstrap / Form.js
1 /*
2  * - LGPL
3  *
4  * form
5  * 
6  */
7
8 /**
9  * @class Roo.bootstrap.Form
10  * @extends Roo.bootstrap.Component
11  * Bootstrap Form class
12  * @cfg {String} method  GET | POST (default POST)
13  * @cfg {String} labelAlign top | left (default top)
14   * @cfg {String} align left  | right - for navbars
15
16  * 
17  * @constructor
18  * Create a new Form
19  * @param {Object} config The config object
20  */
21
22
23 Roo.bootstrap.Form = function(config){
24     Roo.bootstrap.Form.superclass.constructor.call(this, config);
25     this.addEvents({
26         /**
27          * @event clientvalidation
28          * If the monitorValid config option is true, this event fires repetitively to notify of valid state
29          * @param {Form} this
30          * @param {Boolean} valid true if the form has passed client-side validation
31          */
32         clientvalidation: true,
33         /**
34          * @event beforeaction
35          * Fires before any action is performed. Return false to cancel the action.
36          * @param {Form} this
37          * @param {Action} action The action to be performed
38          */
39         beforeaction: true,
40         /**
41          * @event actionfailed
42          * Fires when an action fails.
43          * @param {Form} this
44          * @param {Action} action The action that failed
45          */
46         actionfailed : true,
47         /**
48          * @event actioncomplete
49          * Fires when an action is completed.
50          * @param {Form} this
51          * @param {Action} action The action that completed
52          */
53         actioncomplete : true
54     });
55     
56 };
57
58 Roo.extend(Roo.bootstrap.Form, Roo.bootstrap.Component,  {
59       
60      /**
61      * @cfg {String} method
62      * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
63      */
64     method : 'POST',
65     /**
66      * @cfg {String} url
67      * The URL to use for form actions if one isn't supplied in the action options.
68      */
69     /**
70      * @cfg {Boolean} fileUpload
71      * Set to true if this form is a file upload.
72      */
73      
74     /**
75      * @cfg {Object} baseParams
76      * Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.
77      */
78       
79     /**
80      * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
81      */
82     timeout: 30,
83     /**
84      * @cfg {Sting} align (left|right) for navbar forms
85      */
86     align : 'left',
87
88     // private
89     activeAction : null,
90  
91     /**
92      * By default wait messages are displayed with Roo.MessageBox.wait. You can target a specific
93      * element by passing it or its id or mask the form itself by passing in true.
94      * @type Mixed
95      */
96     waitMsgTarget : false,
97     
98      
99     
100     /**
101      * By default wait messages are displayed with Roo.MessageBox.wait. You can target a specific
102      * element by passing it or its id or mask the form itself by passing in true.
103      * @type Mixed
104      */
105     
106     getAutoCreate : function(){
107         
108         var cfg = {
109             tag: 'form',
110             method : this.method || 'POST',
111             id : this.id || Roo.id(),
112             cls : ''
113         }
114         if (this.parent().xtype.match(/^Nav/)) {
115             cfg.cls = 'navbar-form navbar-' + this.align;
116             
117         }
118         
119         if (this.labelAlign == 'left' ) {
120             cfg.cls += ' form-horizontal';
121         }
122         
123         
124         return cfg;
125     },
126     initEvents : function()
127     {
128         this.el.on('submit', this.onSubmit, this);
129         
130         
131     },
132     // private
133     onSubmit : function(e){
134         e.stopEvent();
135     },
136     
137      /**
138      * Returns true if client-side validation on the form is successful.
139      * @return Boolean
140      */
141     isValid : function(){
142         var items = this.getItems();
143         var valid = true;
144         items.each(function(f){
145            if(!f.validate()){
146                valid = false;
147                
148            }
149         });
150         return valid;
151     },
152     /**
153      * Returns true if any fields in this form have changed since their original load.
154      * @return Boolean
155      */
156     isDirty : function(){
157         var dirty = false;
158         var items = this.getItems();
159         items.each(function(f){
160            if(f.isDirty()){
161                dirty = true;
162                return false;
163            }
164            return true;
165         });
166         return dirty;
167     },
168      /**
169      * Performs a predefined action (submit or load) or custom actions you define on this form.
170      * @param {String} actionName The name of the action type
171      * @param {Object} options (optional) The options to pass to the action.  All of the config options listed
172      * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
173      * accept other config options):
174      * <pre>
175 Property          Type             Description
176 ----------------  ---------------  ----------------------------------------------------------------------------------
177 url               String           The url for the action (defaults to the form's url)
178 method            String           The form method to use (defaults to the form's method, or POST if not defined)
179 params            String/Object    The params to pass (defaults to the form's baseParams, or none if not defined)
180 clientValidation  Boolean          Applies to submit only.  Pass true to call form.isValid() prior to posting to
181                                    validate the form on the client (defaults to false)
182      * </pre>
183      * @return {BasicForm} this
184      */
185     doAction : function(action, options){
186         if(typeof action == 'string'){
187             action = new Roo.form.Action.ACTION_TYPES[action](this, options);
188         }
189         Roo.log(action);
190         if(this.fireEvent('beforeaction', this, action) !== false){
191             Roo.log('in!!!');
192             this.beforeAction(action);
193             action.run.defer(100, action);
194         }
195         return this;
196     },
197     
198     // private
199     beforeAction : function(action){
200         var o = action.options;
201         
202         // not really supported yet.. ??
203         
204         //if(this.waitMsgTarget === true){
205             this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
206         //}else if(this.waitMsgTarget){
207         //    this.waitMsgTarget = Roo.get(this.waitMsgTarget);
208         //    this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
209         //}else {
210         //    Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
211        // }
212          
213     },
214
215     // private
216     afterAction : function(action, success){
217         this.activeAction = null;
218         var o = action.options;
219         
220         //if(this.waitMsgTarget === true){
221             this.el.unmask();
222         //}else if(this.waitMsgTarget){
223         //    this.waitMsgTarget.unmask();
224         //}else{
225         //    Roo.MessageBox.updateProgress(1);
226         //    Roo.MessageBox.hide();
227        // }
228         // 
229         if(success){
230             if(o.reset){
231                 this.reset();
232             }
233             Roo.callback(o.success, o.scope, [this, action]);
234             this.fireEvent('actioncomplete', this, action);
235             
236         }else{
237             Roo.log('go into the fail??');
238             // failure condition..
239             // we have a scenario where updates need confirming.
240             // eg. if a locking scenario exists..
241             // we look for { errors : { needs_confirm : true }} in the response.
242             if (
243                 (typeof(action.result) != 'undefined')  &&
244                 (typeof(action.result.errors) != 'undefined')  &&
245                 (typeof(action.result.errors.needs_confirm) != 'undefined')
246            ){
247                 var _t = this;
248                 Roo.log("not supported yet");
249                  /*
250                 
251                 Roo.MessageBox.confirm(
252                     "Change requires confirmation",
253                     action.result.errorMsg,
254                     function(r) {
255                         if (r != 'yes') {
256                             return;
257                         }
258                         _t.doAction('submit', { params :  { _submit_confirmed : 1 } }  );
259                     }
260                     
261                 );
262                 */
263                 
264                 
265                 return;
266             }
267             Roo.log(o.failure);
268             Roo.log(o.scope);
269             Roo.callback(o.failure, o.scope, [this, action]);
270             // show an error message if no failed handler is set..
271             if (!this.hasListener('actionfailed')) {
272                 Roo.log("need to add dialog support");
273                 /*
274                 Roo.MessageBox.alert("Error",
275                     (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
276                         action.result.errorMsg :
277                         "Saving Failed, please check your entries or try again"
278                 );
279                 */
280             }
281             Roo.log('got the end??!!');
282             this.fireEvent('actionfailed', this, action);
283         }
284         
285     },
286     /**
287      * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
288      * @param {String} id The value to search for
289      * @return Field
290      */
291     findField : function(id){
292         var items = this.getItems();
293         var field = items.get(id);
294         if(!field){
295              items.each(function(f){
296                 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
297                     field = f;
298                     return false;
299                 }
300                 return true;
301             });
302         }
303         return field || null;
304     },
305      /**
306      * Mark fields in this form invalid in bulk.
307      * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
308      * @return {BasicForm} this
309      */
310     markInvalid : function(errors){
311         if(errors instanceof Array){
312             for(var i = 0, len = errors.length; i < len; i++){
313                 var fieldError = errors[i];
314                 var f = this.findField(fieldError.id);
315                 if(f){
316                     f.markInvalid(fieldError.msg);
317                 }
318             }
319         }else{
320             var field, id;
321             for(id in errors){
322                 if(typeof errors[id] != 'function' && (field = this.findField(id))){
323                     field.markInvalid(errors[id]);
324                 }
325             }
326         }
327         //Roo.each(this.childForms || [], function (f) {
328         //    f.markInvalid(errors);
329         //});
330         
331         return this;
332     },
333
334     /**
335      * Set values for fields in this form in bulk.
336      * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
337      * @return {BasicForm} this
338      */
339     setValues : function(values){
340         if(values instanceof Array){ // array of objects
341             for(var i = 0, len = values.length; i < len; i++){
342                 var v = values[i];
343                 var f = this.findField(v.id);
344                 if(f){
345                     f.setValue(v.value);
346                     if(this.trackResetOnLoad){
347                         f.originalValue = f.getValue();
348                     }
349                 }
350             }
351         }else{ // object hash
352             var field, id;
353             for(id in values){
354                 if(typeof values[id] != 'function' && (field = this.findField(id))){
355                     
356                     if (field.setFromData && 
357                         field.valueField && 
358                         field.displayField &&
359                         // combos' with local stores can 
360                         // be queried via setValue()
361                         // to set their value..
362                         (field.store && !field.store.isLocal)
363                         ) {
364                         // it's a combo
365                         var sd = { };
366                         sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
367                         sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
368                         field.setFromData(sd);
369                         
370                     } else {
371                         field.setValue(values[id]);
372                     }
373                     
374                     
375                     if(this.trackResetOnLoad){
376                         field.originalValue = field.getValue();
377                     }
378                 }
379             }
380         }
381          
382         //Roo.each(this.childForms || [], function (f) {
383         //    f.setValues(values);
384         //});
385                 
386         return this;
387     },
388
389     /**
390      * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
391      * they are returned as an array.
392      * @param {Boolean} asString
393      * @return {Object}
394      */
395     getValues : function(asString){
396         //if (this.childForms) {
397             // copy values from the child forms
398         //    Roo.each(this.childForms, function (f) {
399         //        this.setValues(f.getValues());
400         //    }, this);
401         //}
402         
403         
404         
405         var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
406         if(asString === true){
407             return fs;
408         }
409         return Roo.urlDecode(fs);
410     },
411     
412     /**
413      * Returns the fields in this form as an object with key/value pairs. 
414      * This differs from getValues as it calls getValue on each child item, rather than using dom data.
415      * @return {Object}
416      */
417     getFieldValues : function(with_hidden)
418     {
419         var items = this.getItems();
420         var ret = {};
421         items.each(function(f){
422             if (!f.getName()) {
423                 return;
424             }
425             var v = f.getValue();
426             if (f.inputType =='radio') {
427                 if (typeof(ret[f.getName()]) == 'undefined') {
428                     ret[f.getName()] = ''; // empty..
429                 }
430                 
431                 if (!f.el.dom.checked) {
432                     return;
433                     
434                 }
435                 v = f.el.dom.value;
436                 
437             }
438             
439             // not sure if this supported any more..
440             if ((typeof(v) == 'object') && f.getRawValue) {
441                 v = f.getRawValue() ; // dates..
442             }
443             // combo boxes where name != hiddenName...
444             if (f.name != f.getName()) {
445                 ret[f.name] = f.getRawValue();
446             }
447             ret[f.getName()] = v;
448         });
449         
450         return ret;
451     },
452
453     /**
454      * Clears all invalid messages in this form.
455      * @return {BasicForm} this
456      */
457     clearInvalid : function(){
458         var items = this.getItems();
459         
460         items.each(function(f){
461            f.clearInvalid();
462         });
463         
464         
465         
466         return this;
467     },
468
469     /**
470      * Resets this form.
471      * @return {BasicForm} this
472      */
473     reset : function(){
474         var items = this.getItems();
475         items.each(function(f){
476             f.reset();
477         });
478         
479         Roo.each(this.childForms || [], function (f) {
480             f.reset();
481         });
482        
483         
484         return this;
485     },
486     getItems : function()
487     {
488         var r=new Roo.util.MixedCollection(false, function(o){
489             return o.id || (o.id = Roo.id());
490         });
491         var iter = function(el) {
492             if (el.inputEl) {
493                 r.add(el);
494             }
495             if (!el.items) {
496                 return;
497             }
498             Roo.each(el.items,function(e) {
499                 iter(e);
500             });
501             
502             
503         };
504         iter(this);
505         return r;
506         
507         
508         
509         
510     }
511     
512 });
513
514