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