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)
20 * @param {Object} config The config object
24 Roo.bootstrap.Form = function(config){
26 Roo.bootstrap.Form.superclass.constructor.call(this, config);
28 Roo.bootstrap.Form.popover.apply();
32 * @event clientvalidation
33 * If the monitorValid config option is true, this event fires repetitively to notify of valid state
35 * @param {Boolean} valid true if the form has passed client-side validation
37 clientvalidation: true,
40 * Fires before any action is performed. Return false to cancel the action.
42 * @param {Action} action The action to be performed
47 * Fires when an action fails.
49 * @param {Action} action The action that failed
53 * @event actioncomplete
54 * Fires when an action is completed.
56 * @param {Action} action The action that completed
62 Roo.extend(Roo.bootstrap.Form, Roo.bootstrap.Component, {
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.
71 * The URL to use for form actions if one isn't supplied in the action options.
74 * @cfg {Boolean} fileUpload
75 * Set to true if this form is a file upload.
79 * @cfg {Object} baseParams
80 * Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.
84 * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
88 * @cfg {Sting} align (left|right) for navbar forms
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.
100 waitMsgTarget : false,
105 * @cfg {Boolean} errorMask (true|false) default false
110 * @cfg {Number} maskOffset Default 100
115 * @cfg {Boolean} maskBody
119 getAutoCreate : function(){
123 method : this.method || 'POST',
124 id : this.id || Roo.id(),
127 if (this.parent().xtype.match(/^Nav/)) {
128 cfg.cls = 'navbar-form navbar-' + this.align;
132 if (this.labelAlign == 'left' ) {
133 cfg.cls += ' form-horizontal';
139 initEvents : function()
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) {
147 // we might need to allow it for textareas.. and some other items.
148 // check e.getTarget().
150 if(e.getTarget().nodeName.toLowerCase() === 'textarea'){
154 Roo.log("keypress blocked");
162 onSubmit : function(e){
167 * Returns true if client-side validation on the form is successful.
170 isValid : function(){
171 var items = this.getItems();
175 items.each(function(f){
181 Roo.log('invalid field: ' + f.name);
185 if(!target && f.el.isVisible(true)){
191 if(this.errorMask && !valid){
192 Roo.bootstrap.Form.popover.mask(this, target);
199 * Returns true if any fields in this form have changed since their original load.
202 isDirty : function(){
204 var items = this.getItems();
205 items.each(function(f){
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):
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)
229 * @return {BasicForm} this
231 doAction : function(action, options){
232 if(typeof action == 'string'){
233 action = new Roo.form.Action.ACTION_TYPES[action](this, options);
235 if(this.fireEvent('beforeaction', this, action) !== false){
236 this.beforeAction(action);
237 action.run.defer(100, action);
243 beforeAction : function(action){
244 var o = action.options;
249 Roo.get(document.body).mask(o.waitMsg || "Sending", 'x-mask-loading')
251 this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
254 // not really supported yet.. ??
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');
262 // Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
268 afterAction : function(action, success){
269 this.activeAction = null;
270 var o = action.options;
275 Roo.get(document.body).unmask();
281 //if(this.waitMsgTarget === true){
283 //}else if(this.waitMsgTarget){
284 // this.waitMsgTarget.unmask();
286 // Roo.MessageBox.updateProgress(1);
287 // Roo.MessageBox.hide();
294 Roo.callback(o.success, o.scope, [this, action]);
295 this.fireEvent('actioncomplete', this, action);
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.
304 (typeof(action.result) != 'undefined') &&
305 (typeof(action.result.errors) != 'undefined') &&
306 (typeof(action.result.errors.needs_confirm) != 'undefined')
309 Roo.log("not supported yet");
312 Roo.MessageBox.confirm(
313 "Change requires confirmation",
314 action.result.errorMsg,
319 _t.doAction('submit', { params : { _submit_confirmed : 1 } } );
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");
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"
342 this.fireEvent('actionfailed', this, action);
347 * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
348 * @param {String} id The value to search for
351 findField : function(id){
352 var items = this.getItems();
353 var field = items.get(id);
355 items.each(function(f){
356 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
363 return field || null;
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
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);
376 f.markInvalid(fieldError.msg);
382 if(typeof errors[id] != 'function' && (field = this.findField(id))){
383 field.markInvalid(errors[id]);
387 //Roo.each(this.childForms || [], function (f) {
388 // f.markInvalid(errors);
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
399 setValues : function(values){
400 if(values instanceof Array){ // array of objects
401 for(var i = 0, len = values.length; i < len; i++){
403 var f = this.findField(v.id);
406 if(this.trackResetOnLoad){
407 f.originalValue = f.getValue();
411 }else{ // object hash
414 if(typeof values[id] != 'function' && (field = this.findField(id))){
416 if (field.setFromData &&
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)
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);
430 } else if(field.setFromData && (field.store && !field.store.isLocal)) {
432 field.setFromData(values);
435 field.setValue(values[id]);
439 if(this.trackResetOnLoad){
440 field.originalValue = field.getValue();
446 //Roo.each(this.childForms || [], function (f) {
447 // f.setValues(values);
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
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());
469 var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
470 if(asString === true){
473 return Roo.urlDecode(fs);
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.
481 getFieldValues : function(with_hidden)
483 var items = this.getItems();
485 items.each(function(f){
491 var v = f.getValue();
493 if (f.inputType =='radio') {
494 if (typeof(ret[f.getName()]) == 'undefined') {
495 ret[f.getName()] = ''; // empty..
498 if (!f.el.dom.checked) {
506 if(f.xtype == 'MoneyField'){
507 ret[f.currencyName] = f.getCurrency();
510 // not sure if this supported any more..
511 if ((typeof(v) == 'object') && f.getRawValue) {
512 v = f.getRawValue() ; // dates..
514 // combo boxes where name != hiddenName...
515 if (f.name !== false && f.name != '' && f.name != f.getName()) {
516 ret[f.name] = f.getRawValue();
518 ret[f.getName()] = v;
525 * Clears all invalid messages in this form.
526 * @return {BasicForm} this
528 clearInvalid : function(){
529 var items = this.getItems();
531 items.each(function(f){
540 * @return {BasicForm} this
543 var items = this.getItems();
544 items.each(function(f){
548 Roo.each(this.childForms || [], function (f) {
556 getItems : function()
558 var r=new Roo.util.MixedCollection(false, function(o){
559 return o.id || (o.id = Roo.id());
561 var iter = function(el) {
568 Roo.each(el.items,function(e) {
577 hideFields : function(items)
579 Roo.each(items, function(i){
581 var f = this.findField(i);
587 if(f.xtype == 'DateField'){
597 showFields : function(items)
599 Roo.each(items, function(i){
601 var f = this.findField(i);
607 if(f.xtype == 'DateField'){
619 Roo.apply(Roo.bootstrap.Form, {
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)
652 this.maskEl.top.enableDisplayMode("block");
653 this.maskEl.left.enableDisplayMode("block");
654 this.maskEl.bottom.enableDisplayMode("block");
655 this.maskEl.right.enableDisplayMode("block");
657 this.toolTip = new Roo.bootstrap.Tooltip({
658 cls : 'roo-form-error-popover',
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']
667 this.toolTip.render(Roo.get(document.body));
669 this.toolTip.el.enableDisplayMode("block");
671 Roo.get(document.body).on('click', function(){
675 Roo.get(document.body).on('touchstart', function(){
679 this.isApplied = true
682 mask : function(form, target)
686 this.target = target;
688 if(!this.form.errorMask || !target.el){
692 var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.modal', 100, true) || Roo.get(document.body);
696 var ot = this.target.el.calcOffsetsTo(scrollable);
698 var scrollTo = ot[1] - this.form.maskOffset;
700 scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
702 scrollable.scrollTo('top', scrollTo);
704 var box = this.target.el.getBox();
706 var zIndex = Roo.bootstrap.Modal.zIndex++;
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();
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();
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();
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();
737 this.toolTip.bindEl = this.target.el;
739 this.toolTip.el.setStyle('z-index', Roo.bootstrap.Modal.zIndex++);
741 var tip = this.target.blankText;
743 if(this.target.getValue() !== '' ) {
745 if (this.target.invalidText.length) {
746 tip = this.target.invalidText;
747 } else if (this.target.regexText.length){
748 tip = this.target.regexText;
752 this.toolTip.show(tip);
754 this.intervalID = window.setInterval(function() {
755 Roo.bootstrap.Form.popover.unmask();
758 window.onwheel = function(){ return false;};
760 (function(){ this.isMasked = true; }).defer(500, this);
766 if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
770 this.maskEl.top.setStyle('position', 'absolute');
771 this.maskEl.top.setSize(0, 0).setXY([0, 0]);
772 this.maskEl.top.hide();
774 this.maskEl.left.setStyle('position', 'absolute');
775 this.maskEl.left.setSize(0, 0).setXY([0, 0]);
776 this.maskEl.left.hide();
778 this.maskEl.bottom.setStyle('position', 'absolute');
779 this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
780 this.maskEl.bottom.hide();
782 this.maskEl.right.setStyle('position', 'absolute');
783 this.maskEl.right.setSize(0, 0).setXY([0, 0]);
784 this.maskEl.right.hide();
788 this.toolTip.el.hide();
790 window.onwheel = function(){ return true;};
793 window.clearInterval(this.intervalID);
794 this.intervalID = false;
797 this.isMasked = false;