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