9 * @class Roo.bootstrap.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)
21 * @param {Object} config The config object
25 Roo.bootstrap.Form = function(config){
27 Roo.bootstrap.Form.superclass.constructor.call(this, config);
29 Roo.bootstrap.Form.popover.apply();
33 * @event clientvalidation
34 * If the monitorValid config option is true, this event fires repetitively to notify of valid state
36 * @param {Boolean} valid true if the form has passed client-side validation
38 clientvalidation: true,
41 * Fires before any action is performed. Return false to cancel the action.
43 * @param {Action} action The action to be performed
48 * Fires when an action fails.
50 * @param {Action} action The action that failed
54 * @event actioncomplete
55 * Fires when an action is completed.
57 * @param {Action} action The action that completed
63 Roo.extend(Roo.bootstrap.Form, Roo.bootstrap.Component, {
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.
72 * The URL to use for form actions if one isn't supplied in the action options.
75 * @cfg {Boolean} fileUpload
76 * Set to true if this form is a file upload.
80 * @cfg {Object} baseParams
81 * Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.
85 * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
89 * @cfg {Sting} align (left|right) for navbar forms
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.
101 waitMsgTarget : false,
106 * @cfg {Boolean} errorMask (true|false) default false
111 * @cfg {Number} maskOffset Default 100
116 * @cfg {Boolean} maskBody
120 getAutoCreate : function(){
124 method : this.method || 'POST',
125 id : this.id || Roo.id(),
128 if (this.parent().xtype.match(/^Nav/)) {
129 cfg.cls = 'navbar-form form-inline navbar-' + this.align;
133 if (this.labelAlign == 'left' ) {
134 cfg.cls += ' form-horizontal';
140 initEvents : function()
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) {
148 // we might need to allow it for textareas.. and some other items.
149 // check e.getTarget().
151 if(e.getTarget().nodeName.toLowerCase() === 'textarea'){
155 Roo.log("keypress blocked");
163 onSubmit : function(e){
168 * Returns true if client-side validation on the form is successful.
171 isValid : function(){
172 var items = this.getItems();
176 items.each(function(f){
182 Roo.log('invalid field: ' + f.name);
186 if(!target && f.el.isVisible(true)){
192 if(this.errorMask && !valid){
193 Roo.bootstrap.Form.popover.mask(this, target);
200 * Returns true if any fields in this form have changed since their original load.
203 isDirty : function(){
205 var items = this.getItems();
206 items.each(function(f){
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):
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)
230 * @return {BasicForm} this
232 doAction : function(action, options){
233 if(typeof action == 'string'){
234 action = new Roo.form.Action.ACTION_TYPES[action](this, options);
236 if(this.fireEvent('beforeaction', this, action) !== false){
237 this.beforeAction(action);
238 action.run.defer(100, action);
244 beforeAction : function(action){
245 var o = action.options;
250 Roo.get(document.body).mask(o.waitMsg || "Sending", 'x-mask-loading')
252 this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
255 // not really supported yet.. ??
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');
263 // Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
269 afterAction : function(action, success){
270 this.activeAction = null;
271 var o = action.options;
276 Roo.get(document.body).unmask();
282 //if(this.waitMsgTarget === true){
284 //}else if(this.waitMsgTarget){
285 // this.waitMsgTarget.unmask();
287 // Roo.MessageBox.updateProgress(1);
288 // Roo.MessageBox.hide();
295 Roo.callback(o.success, o.scope, [this, action]);
296 this.fireEvent('actioncomplete', this, action);
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.
305 (typeof(action.result) != 'undefined') &&
306 (typeof(action.result.errors) != 'undefined') &&
307 (typeof(action.result.errors.needs_confirm) != 'undefined')
310 Roo.log("not supported yet");
313 Roo.MessageBox.confirm(
314 "Change requires confirmation",
315 action.result.errorMsg,
320 _t.doAction('submit', { params : { _submit_confirmed : 1 } } );
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");
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"
343 this.fireEvent('actionfailed', this, action);
348 * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
349 * @param {String} id The value to search for
352 findField : function(id){
353 var items = this.getItems();
354 var field = items.get(id);
356 items.each(function(f){
357 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
364 return field || null;
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
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);
377 f.markInvalid(fieldError.msg);
383 if(typeof errors[id] != 'function' && (field = this.findField(id))){
384 field.markInvalid(errors[id]);
388 //Roo.each(this.childForms || [], function (f) {
389 // f.markInvalid(errors);
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
400 setValues : function(values){
401 if(values instanceof Array){ // array of objects
402 for(var i = 0, len = values.length; i < len; i++){
404 var f = this.findField(v.id);
407 if(this.trackResetOnLoad){
408 f.originalValue = f.getValue();
412 }else{ // object hash
415 if(typeof values[id] != 'function' && (field = this.findField(id))){
417 if (field.setFromData &&
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)
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);
431 } else if(field.setFromData && (field.store && !field.store.isLocal)) {
433 field.setFromData(values);
436 field.setValue(values[id]);
440 if(this.trackResetOnLoad){
441 field.originalValue = field.getValue();
447 //Roo.each(this.childForms || [], function (f) {
448 // f.setValues(values);
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
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());
470 var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
471 if(asString === true){
474 return Roo.urlDecode(fs);
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.
482 getFieldValues : function(with_hidden)
484 var items = this.getItems();
486 items.each(function(f){
492 var v = f.getValue();
494 if (f.inputType =='radio') {
495 if (typeof(ret[f.getName()]) == 'undefined') {
496 ret[f.getName()] = ''; // empty..
499 if (!f.el.dom.checked) {
507 if(f.xtype == 'MoneyField'){
508 ret[f.currencyName] = f.getCurrency();
511 // not sure if this supported any more..
512 if ((typeof(v) == 'object') && f.getRawValue) {
513 v = f.getRawValue() ; // dates..
515 // combo boxes where name != hiddenName...
516 if (f.name !== false && f.name != '' && f.name != f.getName()) {
517 ret[f.name] = f.getRawValue();
519 ret[f.getName()] = v;
526 * Clears all invalid messages in this form.
527 * @return {BasicForm} this
529 clearInvalid : function(){
530 var items = this.getItems();
532 items.each(function(f){
541 * @return {BasicForm} this
544 var items = this.getItems();
545 items.each(function(f){
549 Roo.each(this.childForms || [], function (f) {
557 getItems : function()
559 var r=new Roo.util.MixedCollection(false, function(o){
560 return o.id || (o.id = Roo.id());
562 var iter = function(el) {
569 Roo.each(el.items,function(e) {
578 hideFields : function(items)
580 Roo.each(items, function(i){
582 var f = this.findField(i);
593 showFields : function(items)
595 Roo.each(items, function(i){
597 var f = this.findField(i);
610 Roo.apply(Roo.bootstrap.Form, {
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)
643 this.maskEl.top.enableDisplayMode("block");
644 this.maskEl.left.enableDisplayMode("block");
645 this.maskEl.bottom.enableDisplayMode("block");
646 this.maskEl.right.enableDisplayMode("block");
648 this.toolTip = new Roo.bootstrap.Tooltip({
649 cls : 'roo-form-error-popover',
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']
658 this.toolTip.render(Roo.get(document.body));
660 this.toolTip.el.enableDisplayMode("block");
662 Roo.get(document.body).on('click', function(){
666 Roo.get(document.body).on('touchstart', function(){
670 this.isApplied = true
673 mask : function(form, target)
677 this.target = target;
679 if(!this.form.errorMask || !target.el){
683 var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.modal', 100, true) || Roo.get(document.body);
687 var ot = this.target.el.calcOffsetsTo(scrollable);
689 var scrollTo = ot[1] - this.form.maskOffset;
691 scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
693 scrollable.scrollTo('top', scrollTo);
695 var box = this.target.el.getBox();
697 var zIndex = Roo.bootstrap.Modal.zIndex++;
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();
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();
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();
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();
728 this.toolTip.bindEl = this.target.el;
730 this.toolTip.el.setStyle('z-index', Roo.bootstrap.Modal.zIndex++);
732 var tip = this.target.blankText;
734 if(this.target.getValue() !== '' ) {
736 if (this.target.invalidText.length) {
737 tip = this.target.invalidText;
738 } else if (this.target.regexText.length){
739 tip = this.target.regexText;
743 this.toolTip.show(tip);
745 this.intervalID = window.setInterval(function() {
746 Roo.bootstrap.Form.popover.unmask();
749 window.onwheel = function(){ return false;};
751 (function(){ this.isMasked = true; }).defer(500, this);
757 if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
761 this.maskEl.top.setStyle('position', 'absolute');
762 this.maskEl.top.setSize(0, 0).setXY([0, 0]);
763 this.maskEl.top.hide();
765 this.maskEl.left.setStyle('position', 'absolute');
766 this.maskEl.left.setSize(0, 0).setXY([0, 0]);
767 this.maskEl.left.hide();
769 this.maskEl.bottom.setStyle('position', 'absolute');
770 this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
771 this.maskEl.bottom.hide();
773 this.maskEl.right.setStyle('position', 'absolute');
774 this.maskEl.right.setSize(0, 0).setXY([0, 0]);
775 this.maskEl.right.hide();
779 this.toolTip.el.hide();
781 window.onwheel = function(){ return true;};
784 window.clearInterval(this.intervalID);
785 this.intervalID = false;
788 this.isMasked = false;