sync
[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     
26     Roo.bootstrap.Form.superclass.constructor.call(this, config);
27     
28     Roo.bootstrap.Form.popover.apply();
29     
30     this.addEvents({
31         /**
32          * @event clientvalidation
33          * If the monitorValid config option is true, this event fires repetitively to notify of valid state
34          * @param {Form} this
35          * @param {Boolean} valid true if the form has passed client-side validation
36          */
37         clientvalidation: true,
38         /**
39          * @event beforeaction
40          * Fires before any action is performed. Return false to cancel the action.
41          * @param {Form} this
42          * @param {Action} action The action to be performed
43          */
44         beforeaction: true,
45         /**
46          * @event actionfailed
47          * Fires when an action fails.
48          * @param {Form} this
49          * @param {Action} action The action that failed
50          */
51         actionfailed : true,
52         /**
53          * @event actioncomplete
54          * Fires when an action is completed.
55          * @param {Form} this
56          * @param {Action} action The action that completed
57          */
58         actioncomplete : true
59     });
60 };
61
62 Roo.extend(Roo.bootstrap.Form, Roo.bootstrap.Component,  {
63
64      /**
65      * @cfg {String} method
66      * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
67      */
68     method : 'POST',
69     /**
70      * @cfg {String} url
71      * The URL to use for form actions if one isn't supplied in the action options.
72      */
73     /**
74      * @cfg {Boolean} fileUpload
75      * Set to true if this form is a file upload.
76      */
77
78     /**
79      * @cfg {Object} baseParams
80      * Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.
81      */
82
83     /**
84      * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
85      */
86     timeout: 30,
87     /**
88      * @cfg {Sting} align (left|right) for navbar forms
89      */
90     align : 'left',
91
92     // private
93     activeAction : null,
94
95     /**
96      * By default wait messages are displayed with Roo.MessageBox.wait. You can target a specific
97      * element by passing it or its id or mask the form itself by passing in true.
98      * @type Mixed
99      */
100     waitMsgTarget : false,
101
102     loadMask : true,
103     
104     /**
105      * @cfg {Boolean} errorMask (true|false) default false
106      */
107     errorMask : false,
108     
109     /**
110      * @cfg {Number} maskOffset Default 100
111      */
112     maskOffset : 100,
113     
114     /**
115      * @cfg {Boolean} maskBody
116      */
117     maskBody : false,
118
119     getAutoCreate : function(){
120
121         var cfg = {
122             tag: 'form',
123             method : this.method || 'POST',
124             id : this.id || Roo.id(),
125             cls : ''
126         };
127         if (this.parent().xtype.match(/^Nav/)) {
128             cfg.cls = 'navbar-form navbar-' + this.align;
129
130         }
131
132         if (this.labelAlign == 'left' ) {
133             cfg.cls += ' form-horizontal';
134         }
135
136
137         return cfg;
138     },
139     initEvents : function()
140     {
141         this.el.on('submit', this.onSubmit, this);
142         // this was added as random key presses on the form where triggering form submit.
143         this.el.on('keypress', function(e) {
144             if (e.getCharCode() != 13) {
145                 return true;
146             }
147             // we might need to allow it for textareas.. and some other items.
148             // check e.getTarget().
149
150             if(e.getTarget().nodeName.toLowerCase() === 'textarea'){
151                 return true;
152             }
153
154             Roo.log("keypress blocked");
155
156             e.preventDefault();
157             return false;
158         });
159         
160     },
161     // private
162     onSubmit : function(e){
163         e.stopEvent();
164     },
165
166      /**
167      * Returns true if client-side validation on the form is successful.
168      * @return Boolean
169      */
170     isValid : function(){
171         var items = this.getItems();
172         var valid = true;
173         var target = false;
174         
175         items.each(function(f){
176             
177             if(f.validate()){
178                 return;
179             }
180             
181             Roo.log('invalid field: ' + f.name);
182             
183             valid = false;
184
185             if(!target && f.el.isVisible(true)){
186                 target = f;
187             }
188            
189         });
190         
191         if(this.errorMask && !valid){
192             Roo.bootstrap.Form.popover.mask(this, target);
193         }
194         
195         return valid;
196     },
197     
198     /**
199      * Returns true if any fields in this form have changed since their original load.
200      * @return Boolean
201      */
202     isDirty : function(){
203         var dirty = false;
204         var items = this.getItems();
205         items.each(function(f){
206            if(f.isDirty()){
207                dirty = true;
208                return false;
209            }
210            return true;
211         });
212         return dirty;
213     },
214      /**
215      * Performs a predefined action (submit or load) or custom actions you define on this form.
216      * @param {String} actionName The name of the action type
217      * @param {Object} options (optional) The options to pass to the action.  All of the config options listed
218      * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
219      * accept other config options):
220      * <pre>
221 Property          Type             Description
222 ----------------  ---------------  ----------------------------------------------------------------------------------
223 url               String           The url for the action (defaults to the form's url)
224 method            String           The form method to use (defaults to the form's method, or POST if not defined)
225 params            String/Object    The params to pass (defaults to the form's baseParams, or none if not defined)
226 clientValidation  Boolean          Applies to submit only.  Pass true to call form.isValid() prior to posting to
227                                    validate the form on the client (defaults to false)
228      * </pre>
229      * @return {BasicForm} this
230      */
231     doAction : function(action, options){
232         if(typeof action == 'string'){
233             action = new Roo.form.Action.ACTION_TYPES[action](this, options);
234         }
235         if(this.fireEvent('beforeaction', this, action) !== false){
236             this.beforeAction(action);
237             action.run.defer(100, action);
238         }
239         return this;
240     },
241
242     // private
243     beforeAction : function(action){
244         var o = action.options;
245         
246         if(this.loadMask){
247             
248             if(this.maskBody){
249                 Roo.get(document.body).mask(o.waitMsg || "Sending", 'x-mask-loading')
250             } else {
251                 this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
252             }
253         }
254         // not really supported yet.. ??
255
256         //if(this.waitMsgTarget === true){
257         //  this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
258         //}else if(this.waitMsgTarget){
259         //    this.waitMsgTarget = Roo.get(this.waitMsgTarget);
260         //    this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
261         //}else {
262         //    Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
263        // }
264
265     },
266
267     // private
268     afterAction : function(action, success){
269         this.activeAction = null;
270         var o = action.options;
271
272         if(this.loadMask){
273             
274             if(this.maskBody){
275                 Roo.get(document.body).unmask();
276             } else {
277                 this.el.unmask();
278             }
279         }
280         
281         //if(this.waitMsgTarget === true){
282 //            this.el.unmask();
283         //}else if(this.waitMsgTarget){
284         //    this.waitMsgTarget.unmask();
285         //}else{
286         //    Roo.MessageBox.updateProgress(1);
287         //    Roo.MessageBox.hide();
288        // }
289         //
290         if(success){
291             if(o.reset){
292                 this.reset();
293             }
294             Roo.callback(o.success, o.scope, [this, action]);
295             this.fireEvent('actioncomplete', this, action);
296
297         }else{
298
299             // failure condition..
300             // we have a scenario where updates need confirming.
301             // eg. if a locking scenario exists..
302             // we look for { errors : { needs_confirm : true }} in the response.
303             if (
304                 (typeof(action.result) != 'undefined')  &&
305                 (typeof(action.result.errors) != 'undefined')  &&
306                 (typeof(action.result.errors.needs_confirm) != 'undefined')
307            ){
308                 var _t = this;
309                 Roo.log("not supported yet");
310                  /*
311
312                 Roo.MessageBox.confirm(
313                     "Change requires confirmation",
314                     action.result.errorMsg,
315                     function(r) {
316                         if (r != 'yes') {
317                             return;
318                         }
319                         _t.doAction('submit', { params :  { _submit_confirmed : 1 } }  );
320                     }
321
322                 );
323                 */
324
325
326                 return;
327             }
328
329             Roo.callback(o.failure, o.scope, [this, action]);
330             // show an error message if no failed handler is set..
331             if (!this.hasListener('actionfailed')) {
332                 Roo.log("need to add dialog support");
333                 /*
334                 Roo.MessageBox.alert("Error",
335                     (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
336                         action.result.errorMsg :
337                         "Saving Failed, please check your entries or try again"
338                 );
339                 */
340             }
341
342             this.fireEvent('actionfailed', this, action);
343         }
344
345     },
346     /**
347      * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
348      * @param {String} id The value to search for
349      * @return Field
350      */
351     findField : function(id){
352         var items = this.getItems();
353         var field = items.get(id);
354         if(!field){
355              items.each(function(f){
356                 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
357                     field = f;
358                     return false;
359                 }
360                 return true;
361             });
362         }
363         return field || null;
364     },
365      /**
366      * Mark fields in this form invalid in bulk.
367      * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
368      * @return {BasicForm} this
369      */
370     markInvalid : function(errors){
371         if(errors instanceof Array){
372             for(var i = 0, len = errors.length; i < len; i++){
373                 var fieldError = errors[i];
374                 var f = this.findField(fieldError.id);
375                 if(f){
376                     f.markInvalid(fieldError.msg);
377                 }
378             }
379         }else{
380             var field, id;
381             for(id in errors){
382                 if(typeof errors[id] != 'function' && (field = this.findField(id))){
383                     field.markInvalid(errors[id]);
384                 }
385             }
386         }
387         //Roo.each(this.childForms || [], function (f) {
388         //    f.markInvalid(errors);
389         //});
390
391         return this;
392     },
393
394     /**
395      * Set values for fields in this form in bulk.
396      * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
397      * @return {BasicForm} this
398      */
399     setValues : function(values){
400         if(values instanceof Array){ // array of objects
401             for(var i = 0, len = values.length; i < len; i++){
402                 var v = values[i];
403                 var f = this.findField(v.id);
404                 if(f){
405                     f.setValue(v.value);
406                     if(this.trackResetOnLoad){
407                         f.originalValue = f.getValue();
408                     }
409                 }
410             }
411         }else{ // object hash
412             var field, id;
413             for(id in values){
414                 if(typeof values[id] != 'function' && (field = this.findField(id))){
415
416                     if (field.setFromData &&
417                         field.valueField &&
418                         field.displayField &&
419                         // combos' with local stores can
420                         // be queried via setValue()
421                         // to set their value..
422                         (field.store && !field.store.isLocal)
423                         ) {
424                         // it's a combo
425                         var sd = { };
426                         sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
427                         sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
428                         field.setFromData(sd);
429
430                     } else if(field.setFromData && (field.store && !field.store.isLocal)) {
431                         
432                         field.setFromData(values);
433                         
434                     } else {
435                         field.setValue(values[id]);
436                     }
437
438
439                     if(this.trackResetOnLoad){
440                         field.originalValue = field.getValue();
441                     }
442                 }
443             }
444         }
445
446         //Roo.each(this.childForms || [], function (f) {
447         //    f.setValues(values);
448         //});
449
450         return this;
451     },
452
453     /**
454      * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
455      * they are returned as an array.
456      * @param {Boolean} asString
457      * @return {Object}
458      */
459     getValues : function(asString){
460         //if (this.childForms) {
461             // copy values from the child forms
462         //    Roo.each(this.childForms, function (f) {
463         //        this.setValues(f.getValues());
464         //    }, this);
465         //}
466
467
468
469         var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
470         if(asString === true){
471             return fs;
472         }
473         return Roo.urlDecode(fs);
474     },
475
476     /**
477      * Returns the fields in this form as an object with key/value pairs.
478      * This differs from getValues as it calls getValue on each child item, rather than using dom data.
479      * @return {Object}
480      */
481     getFieldValues : function(with_hidden)
482     {
483         var items = this.getItems();
484         var ret = {};
485         items.each(function(f){
486             
487             if (!f.getName()) {
488                 return;
489             }
490             
491             var v = f.getValue();
492             
493             if (f.inputType =='radio') {
494                 if (typeof(ret[f.getName()]) == 'undefined') {
495                     ret[f.getName()] = ''; // empty..
496                 }
497
498                 if (!f.el.dom.checked) {
499                     return;
500
501                 }
502                 v = f.el.dom.value;
503
504             }
505             
506             if(f.xtype == 'MoneyField'){
507                 ret[f.currencyName] = f.getCurrency();
508             }
509
510             // not sure if this supported any more..
511             if ((typeof(v) == 'object') && f.getRawValue) {
512                 v = f.getRawValue() ; // dates..
513             }
514             // combo boxes where name != hiddenName...
515             if (f.name !== false && f.name != '' && f.name != f.getName()) {
516                 ret[f.name] = f.getRawValue();
517             }
518             ret[f.getName()] = v;
519         });
520
521         return ret;
522     },
523
524     /**
525      * Clears all invalid messages in this form.
526      * @return {BasicForm} this
527      */
528     clearInvalid : function(){
529         var items = this.getItems();
530
531         items.each(function(f){
532            f.clearInvalid();
533         });
534
535         return this;
536     },
537
538     /**
539      * Resets this form.
540      * @return {BasicForm} this
541      */
542     reset : function(){
543         var items = this.getItems();
544         items.each(function(f){
545             f.reset();
546         });
547
548         Roo.each(this.childForms || [], function (f) {
549             f.reset();
550         });
551
552
553         return this;
554     },
555     
556     getItems : function()
557     {
558         var r=new Roo.util.MixedCollection(false, function(o){
559             return o.id || (o.id = Roo.id());
560         });
561         var iter = function(el) {
562             if (el.inputEl) {
563                 r.add(el);
564             }
565             if (!el.items) {
566                 return;
567             }
568             Roo.each(el.items,function(e) {
569                 iter(e);
570             });
571         };
572
573         iter(this);
574         return r;
575     },
576     
577     hideFields : function(items)
578     {
579         Roo.each(items, function(i){
580             
581             var f = this.findField(i);
582             
583             if(!f){
584                 return;
585             }
586             
587             if(f.xtype == 'DateField'){
588                 f.setVisible(false);
589                 return;
590             }
591             
592             f.hide();
593             
594         }, this);
595     },
596     
597     showFields : function(items)
598     {
599         Roo.each(items, function(i){
600             
601             var f = this.findField(i);
602             
603             if(!f){
604                 return;
605             }
606             
607             if(f.xtype == 'DateField'){
608                 f.setVisible(true);
609                 return;
610             }
611             
612             f.show();
613             
614         }, this);
615     }
616
617 });
618
619 Roo.apply(Roo.bootstrap.Form, {
620     
621     popover : {
622         
623         padding : 5,
624         
625         isApplied : false,
626         
627         isMasked : false,
628         
629         form : false,
630         
631         target : false,
632         
633         toolTip : false,
634         
635         intervalID : false,
636         
637         maskEl : false,
638         
639         apply : function()
640         {
641             if(this.isApplied){
642                 return;
643             }
644             
645             this.maskEl = {
646                 top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
647                 left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
648                 bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
649                 right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
650             };
651             
652             this.maskEl.top.enableDisplayMode("block");
653             this.maskEl.left.enableDisplayMode("block");
654             this.maskEl.bottom.enableDisplayMode("block");
655             this.maskEl.right.enableDisplayMode("block");
656             
657             this.toolTip = new Roo.bootstrap.Tooltip({
658                 cls : 'roo-form-error-popover',
659                 alignment : {
660                     'left' : ['r-l', [-2,0], 'right'],
661                     'right' : ['l-r', [2,0], 'left'],
662                     'bottom' : ['tl-bl', [0,2], 'top'],
663                     'top' : [ 'bl-tl', [0,-2], 'bottom']
664                 }
665             });
666             
667             this.toolTip.render(Roo.get(document.body));
668
669             this.toolTip.el.enableDisplayMode("block");
670             
671             Roo.get(document.body).on('click', function(){
672                 this.unmask();
673             }, this);
674             
675             Roo.get(document.body).on('touchstart', function(){
676                 this.unmask();
677             }, this);
678             
679             this.isApplied = true
680         },
681         
682         mask : function(form, target)
683         {
684             this.form = form;
685             
686             this.target = target;
687             
688             if(!this.form.errorMask || !target.el){
689                 return;
690             }
691             
692             var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.modal', 100, true) || Roo.get(document.body);
693             
694             Roo.log(scrollable);
695             
696             var ot = this.target.el.calcOffsetsTo(scrollable);
697             
698             var scrollTo = ot[1] - this.form.maskOffset;
699             
700             scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
701             
702             scrollable.scrollTo('top', scrollTo);
703             
704             var box = this.target.el.getBox();
705             Roo.log(box);
706             var zIndex = Roo.bootstrap.Modal.zIndex++;
707
708             
709             this.maskEl.top.setStyle('position', 'absolute');
710             this.maskEl.top.setStyle('z-index', zIndex);
711             this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding);
712             this.maskEl.top.setLeft(0);
713             this.maskEl.top.setTop(0);
714             this.maskEl.top.show();
715             
716             this.maskEl.left.setStyle('position', 'absolute');
717             this.maskEl.left.setStyle('z-index', zIndex);
718             this.maskEl.left.setSize(box.x - this.padding, box.height + this.padding * 2);
719             this.maskEl.left.setLeft(0);
720             this.maskEl.left.setTop(box.y - this.padding);
721             this.maskEl.left.show();
722
723             this.maskEl.bottom.setStyle('position', 'absolute');
724             this.maskEl.bottom.setStyle('z-index', zIndex);
725             this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding);
726             this.maskEl.bottom.setLeft(0);
727             this.maskEl.bottom.setTop(box.bottom + this.padding);
728             this.maskEl.bottom.show();
729
730             this.maskEl.right.setStyle('position', 'absolute');
731             this.maskEl.right.setStyle('z-index', zIndex);
732             this.maskEl.right.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2);
733             this.maskEl.right.setLeft(box.right + this.padding);
734             this.maskEl.right.setTop(box.y - this.padding);
735             this.maskEl.right.show();
736
737             this.toolTip.bindEl = this.target.el;
738
739             this.toolTip.el.setStyle('z-index', Roo.bootstrap.Modal.zIndex++);
740
741             var tip = this.target.blankText;
742
743             if(this.target.getValue() !== '' ) {
744                 
745                 if (this.target.invalidText.length) {
746                     tip = this.target.invalidText;
747                 } else if (this.target.regexText.length){
748                     tip = this.target.regexText;
749                 }
750             }
751
752             this.toolTip.show(tip);
753
754             this.intervalID = window.setInterval(function() {
755                 Roo.bootstrap.Form.popover.unmask();
756             }, 10000);
757
758             window.onwheel = function(){ return false;};
759             
760             (function(){ this.isMasked = true; }).defer(500, this);
761             
762         },
763         
764         unmask : function()
765         {
766             if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
767                 return;
768             }
769             
770             this.maskEl.top.setStyle('position', 'absolute');
771             this.maskEl.top.setSize(0, 0).setXY([0, 0]);
772             this.maskEl.top.hide();
773
774             this.maskEl.left.setStyle('position', 'absolute');
775             this.maskEl.left.setSize(0, 0).setXY([0, 0]);
776             this.maskEl.left.hide();
777
778             this.maskEl.bottom.setStyle('position', 'absolute');
779             this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
780             this.maskEl.bottom.hide();
781
782             this.maskEl.right.setStyle('position', 'absolute');
783             this.maskEl.right.setSize(0, 0).setXY([0, 0]);
784             this.maskEl.right.hide();
785             
786             this.toolTip.hide();
787             
788             this.toolTip.el.hide();
789             
790             window.onwheel = function(){ return true;};
791             
792             if(this.intervalID){
793                 window.clearInterval(this.intervalID);
794                 this.intervalID = false;
795             }
796             
797             this.isMasked = false;
798             
799         }
800         
801     }
802     
803 });
804