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){
25 Roo.bootstrap.Form.superclass.constructor.call(this, config);
27 Roo.bootstrap.Form.popover.apply();
31 * @event clientvalidation
32 * If the monitorValid config option is true, this event fires repetitively to notify of valid state
34 * @param {Boolean} valid true if the form has passed client-side validation
36 clientvalidation: true,
39 * Fires before any action is performed. Return false to cancel the action.
41 * @param {Action} action The action to be performed
46 * Fires when an action fails.
48 * @param {Action} action The action that failed
52 * @event actioncomplete
53 * Fires when an action is completed.
55 * @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 if(!target && f.el.isVisible(true)){
187 if(this.errorMask && !valid){
188 Roo.bootstrap.Form.popover.mask(this, target);
195 * Returns true if any fields in this form have changed since their original load.
198 isDirty : function(){
200 var items = this.getItems();
201 items.each(function(f){
211 * Performs a predefined action (submit or load) or custom actions you define on this form.
212 * @param {String} actionName The name of the action type
213 * @param {Object} options (optional) The options to pass to the action. All of the config options listed
214 * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
215 * accept other config options):
217 Property Type Description
218 ---------------- --------------- ----------------------------------------------------------------------------------
219 url String The url for the action (defaults to the form's url)
220 method String The form method to use (defaults to the form's method, or POST if not defined)
221 params String/Object The params to pass (defaults to the form's baseParams, or none if not defined)
222 clientValidation Boolean Applies to submit only. Pass true to call form.isValid() prior to posting to
223 validate the form on the client (defaults to false)
225 * @return {BasicForm} this
227 doAction : function(action, options){
228 if(typeof action == 'string'){
229 action = new Roo.form.Action.ACTION_TYPES[action](this, options);
231 if(this.fireEvent('beforeaction', this, action) !== false){
232 this.beforeAction(action);
233 action.run.defer(100, action);
239 beforeAction : function(action){
240 var o = action.options;
245 Roo.get(document.body).mask(o.waitMsg || "Sending", 'x-mask-loading')
247 this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
250 // not really supported yet.. ??
252 //if(this.waitMsgTarget === true){
253 // this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
254 //}else if(this.waitMsgTarget){
255 // this.waitMsgTarget = Roo.get(this.waitMsgTarget);
256 // this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
258 // Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
264 afterAction : function(action, success){
265 this.activeAction = null;
266 var o = action.options;
268 //if(this.waitMsgTarget === true){
269 Roo.get(document.body).unmask();
271 //}else if(this.waitMsgTarget){
272 // this.waitMsgTarget.unmask();
274 // Roo.MessageBox.updateProgress(1);
275 // Roo.MessageBox.hide();
282 Roo.callback(o.success, o.scope, [this, action]);
283 this.fireEvent('actioncomplete', this, action);
287 // failure condition..
288 // we have a scenario where updates need confirming.
289 // eg. if a locking scenario exists..
290 // we look for { errors : { needs_confirm : true }} in the response.
292 (typeof(action.result) != 'undefined') &&
293 (typeof(action.result.errors) != 'undefined') &&
294 (typeof(action.result.errors.needs_confirm) != 'undefined')
297 Roo.log("not supported yet");
300 Roo.MessageBox.confirm(
301 "Change requires confirmation",
302 action.result.errorMsg,
307 _t.doAction('submit', { params : { _submit_confirmed : 1 } } );
317 Roo.callback(o.failure, o.scope, [this, action]);
318 // show an error message if no failed handler is set..
319 if (!this.hasListener('actionfailed')) {
320 Roo.log("need to add dialog support");
322 Roo.MessageBox.alert("Error",
323 (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
324 action.result.errorMsg :
325 "Saving Failed, please check your entries or try again"
330 this.fireEvent('actionfailed', this, action);
335 * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
336 * @param {String} id The value to search for
339 findField : function(id){
340 var items = this.getItems();
341 var field = items.get(id);
343 items.each(function(f){
344 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
351 return field || null;
354 * Mark fields in this form invalid in bulk.
355 * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
356 * @return {BasicForm} this
358 markInvalid : function(errors){
359 if(errors instanceof Array){
360 for(var i = 0, len = errors.length; i < len; i++){
361 var fieldError = errors[i];
362 var f = this.findField(fieldError.id);
364 f.markInvalid(fieldError.msg);
370 if(typeof errors[id] != 'function' && (field = this.findField(id))){
371 field.markInvalid(errors[id]);
375 //Roo.each(this.childForms || [], function (f) {
376 // f.markInvalid(errors);
383 * Set values for fields in this form in bulk.
384 * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
385 * @return {BasicForm} this
387 setValues : function(values){
388 if(values instanceof Array){ // array of objects
389 for(var i = 0, len = values.length; i < len; i++){
391 var f = this.findField(v.id);
394 if(this.trackResetOnLoad){
395 f.originalValue = f.getValue();
399 }else{ // object hash
402 if(typeof values[id] != 'function' && (field = this.findField(id))){
404 if (field.setFromData &&
406 field.displayField &&
407 // combos' with local stores can
408 // be queried via setValue()
409 // to set their value..
410 (field.store && !field.store.isLocal)
414 sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
415 sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
416 field.setFromData(sd);
418 } else if(field.setFromData && (field.store && !field.store.isLocal)) {
420 field.setFromData(values);
423 field.setValue(values[id]);
427 if(this.trackResetOnLoad){
428 field.originalValue = field.getValue();
434 //Roo.each(this.childForms || [], function (f) {
435 // f.setValues(values);
442 * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
443 * they are returned as an array.
444 * @param {Boolean} asString
447 getValues : function(asString){
448 //if (this.childForms) {
449 // copy values from the child forms
450 // Roo.each(this.childForms, function (f) {
451 // this.setValues(f.getValues());
457 var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
458 if(asString === true){
461 return Roo.urlDecode(fs);
465 * Returns the fields in this form as an object with key/value pairs.
466 * This differs from getValues as it calls getValue on each child item, rather than using dom data.
469 getFieldValues : function(with_hidden)
471 var items = this.getItems();
473 items.each(function(f){
479 var v = f.getValue();
481 if (f.inputType =='radio') {
482 if (typeof(ret[f.getName()]) == 'undefined') {
483 ret[f.getName()] = ''; // empty..
486 if (!f.el.dom.checked) {
494 if(f.xtype == 'MoneyField'){
495 ret[f.currencyName] = f.getCurrency();
498 // not sure if this supported any more..
499 if ((typeof(v) == 'object') && f.getRawValue) {
500 v = f.getRawValue() ; // dates..
502 // combo boxes where name != hiddenName...
503 if (f.name !== false && f.name != '' && f.name != f.getName()) {
504 ret[f.name] = f.getRawValue();
506 ret[f.getName()] = v;
513 * Clears all invalid messages in this form.
514 * @return {BasicForm} this
516 clearInvalid : function(){
517 var items = this.getItems();
519 items.each(function(f){
530 * @return {BasicForm} this
533 var items = this.getItems();
534 items.each(function(f){
538 Roo.each(this.childForms || [], function (f) {
546 getItems : function()
548 var r=new Roo.util.MixedCollection(false, function(o){
549 return o.id || (o.id = Roo.id());
551 var iter = function(el) {
558 Roo.each(el.items,function(e) {
572 Roo.apply(Roo.bootstrap.Form, {
599 top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
600 left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
601 bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
602 right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
605 this.maskEl.top.enableDisplayMode("block");
606 this.maskEl.left.enableDisplayMode("block");
607 this.maskEl.bottom.enableDisplayMode("block");
608 this.maskEl.right.enableDisplayMode("block");
610 this.toolTip = new Roo.bootstrap.Tooltip({
611 cls : 'roo-form-error-popover',
613 'left' : ['r-l', [-2,0], 'right'],
614 'right' : ['l-r', [2,0], 'left'],
615 'bottom' : ['tl-bl', [0,2], 'top'],
616 'top' : [ 'bl-tl', [0,-2], 'bottom']
620 this.toolTip.render(Roo.get(document.body));
622 this.toolTip.el.enableDisplayMode("block");
624 Roo.get(document.body).on('click', function(){
628 Roo.get(document.body).on('touchstart', function(){
632 this.isApplied = true
635 mask : function(form, target)
639 this.target = target;
641 if(!this.form.errorMask || !target.el){
645 var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.modal', 100, true) || Roo.get(document.body);
649 var ot = this.target.el.calcOffsetsTo(scrollable);
651 var scrollTo = ot[1] - this.form.maskOffset;
653 scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
655 scrollable.scrollTo('top', scrollTo);
657 var box = this.target.el.getBox();
659 var zIndex = Roo.bootstrap.Modal.zIndex++;
662 this.maskEl.top.setStyle('position', 'absolute');
663 this.maskEl.top.setStyle('z-index', zIndex);
664 this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding);
665 this.maskEl.top.setLeft(0);
666 this.maskEl.top.setTop(0);
667 this.maskEl.top.show();
669 this.maskEl.left.setStyle('position', 'absolute');
670 this.maskEl.left.setStyle('z-index', zIndex);
671 this.maskEl.left.setSize(box.x - this.padding, box.height + this.padding * 2);
672 this.maskEl.left.setLeft(0);
673 this.maskEl.left.setTop(box.y - this.padding);
674 this.maskEl.left.show();
676 this.maskEl.bottom.setStyle('position', 'absolute');
677 this.maskEl.bottom.setStyle('z-index', zIndex);
678 this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding);
679 this.maskEl.bottom.setLeft(0);
680 this.maskEl.bottom.setTop(box.bottom + this.padding);
681 this.maskEl.bottom.show();
683 this.maskEl.right.setStyle('position', 'absolute');
684 this.maskEl.right.setStyle('z-index', zIndex);
685 this.maskEl.right.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2);
686 this.maskEl.right.setLeft(box.right + this.padding);
687 this.maskEl.right.setTop(box.y - this.padding);
688 this.maskEl.right.show();
690 this.toolTip.bindEl = this.target.el;
692 this.toolTip.el.setStyle('z-index', Roo.bootstrap.Modal.zIndex++);
694 var tip = this.target.blankText;
696 if(this.target.getValue() !== '' ) {
698 if (this.target.invalidText.length) {
699 tip = this.target.invalidText;
700 } else if (this.target.regexText.length){
701 tip = this.target.regexText;
705 this.toolTip.show(tip);
707 this.intervalID = window.setInterval(function() {
708 Roo.bootstrap.Form.popover.unmask();
711 window.onwheel = function(){ return false;};
713 (function(){ this.isMasked = true; }).defer(500, this);
719 if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
723 this.maskEl.top.setStyle('position', 'absolute');
724 this.maskEl.top.setSize(0, 0).setXY([0, 0]);
725 this.maskEl.top.hide();
727 this.maskEl.left.setStyle('position', 'absolute');
728 this.maskEl.left.setSize(0, 0).setXY([0, 0]);
729 this.maskEl.left.hide();
731 this.maskEl.bottom.setStyle('position', 'absolute');
732 this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
733 this.maskEl.bottom.hide();
735 this.maskEl.right.setStyle('position', 'absolute');
736 this.maskEl.right.setSize(0, 0).setXY([0, 0]);
737 this.maskEl.right.hide();
741 this.toolTip.el.hide();
743 window.onwheel = function(){ return true;};
746 window.clearInterval(this.intervalID);
747 this.intervalID = false;
750 this.isMasked = false;