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