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
114 getAutoCreate : function(){
118 method : this.method || 'POST',
119 id : this.id || Roo.id(),
122 if (this.parent().xtype.match(/^Nav/)) {
123 cfg.cls = 'navbar-form navbar-' + this.align;
127 if (this.labelAlign == 'left' ) {
128 cfg.cls += ' form-horizontal';
134 initEvents : function()
136 this.el.on('submit', this.onSubmit, this);
137 // this was added as random key presses on the form where triggering form submit.
138 this.el.on('keypress', function(e) {
139 if (e.getCharCode() != 13) {
142 // we might need to allow it for textareas.. and some other items.
143 // check e.getTarget().
145 if(e.getTarget().nodeName.toLowerCase() === 'textarea'){
149 Roo.log("keypress blocked");
157 onSubmit : function(e){
162 * Returns true if client-side validation on the form is successful.
165 isValid : function(){
166 var items = this.getItems();
170 items.each(function(f){
177 if(!target && f.el.isVisible(true)){
183 if(this.errorMask && !valid){
184 Roo.bootstrap.Form.popover.mask(this, target);
191 * Returns true if any fields in this form have changed since their original load.
194 isDirty : function(){
196 var items = this.getItems();
197 items.each(function(f){
207 * Performs a predefined action (submit or load) or custom actions you define on this form.
208 * @param {String} actionName The name of the action type
209 * @param {Object} options (optional) The options to pass to the action. All of the config options listed
210 * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
211 * accept other config options):
213 Property Type Description
214 ---------------- --------------- ----------------------------------------------------------------------------------
215 url String The url for the action (defaults to the form's url)
216 method String The form method to use (defaults to the form's method, or POST if not defined)
217 params String/Object The params to pass (defaults to the form's baseParams, or none if not defined)
218 clientValidation Boolean Applies to submit only. Pass true to call form.isValid() prior to posting to
219 validate the form on the client (defaults to false)
221 * @return {BasicForm} this
223 doAction : function(action, options){
224 if(typeof action == 'string'){
225 action = new Roo.form.Action.ACTION_TYPES[action](this, options);
227 if(this.fireEvent('beforeaction', this, action) !== false){
228 this.beforeAction(action);
229 action.run.defer(100, action);
235 beforeAction : function(action){
236 var o = action.options;
239 this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
241 // not really supported yet.. ??
243 //if(this.waitMsgTarget === true){
244 // this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
245 //}else if(this.waitMsgTarget){
246 // this.waitMsgTarget = Roo.get(this.waitMsgTarget);
247 // this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
249 // Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
255 afterAction : function(action, success){
256 this.activeAction = null;
257 var o = action.options;
259 //if(this.waitMsgTarget === true){
261 //}else if(this.waitMsgTarget){
262 // this.waitMsgTarget.unmask();
264 // Roo.MessageBox.updateProgress(1);
265 // Roo.MessageBox.hide();
272 Roo.callback(o.success, o.scope, [this, action]);
273 this.fireEvent('actioncomplete', this, action);
277 // failure condition..
278 // we have a scenario where updates need confirming.
279 // eg. if a locking scenario exists..
280 // we look for { errors : { needs_confirm : true }} in the response.
282 (typeof(action.result) != 'undefined') &&
283 (typeof(action.result.errors) != 'undefined') &&
284 (typeof(action.result.errors.needs_confirm) != 'undefined')
287 Roo.log("not supported yet");
290 Roo.MessageBox.confirm(
291 "Change requires confirmation",
292 action.result.errorMsg,
297 _t.doAction('submit', { params : { _submit_confirmed : 1 } } );
307 Roo.callback(o.failure, o.scope, [this, action]);
308 // show an error message if no failed handler is set..
309 if (!this.hasListener('actionfailed')) {
310 Roo.log("need to add dialog support");
312 Roo.MessageBox.alert("Error",
313 (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
314 action.result.errorMsg :
315 "Saving Failed, please check your entries or try again"
320 this.fireEvent('actionfailed', this, action);
325 * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
326 * @param {String} id The value to search for
329 findField : function(id){
330 var items = this.getItems();
331 var field = items.get(id);
333 items.each(function(f){
334 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
341 return field || null;
344 * Mark fields in this form invalid in bulk.
345 * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
346 * @return {BasicForm} this
348 markInvalid : function(errors){
349 if(errors instanceof Array){
350 for(var i = 0, len = errors.length; i < len; i++){
351 var fieldError = errors[i];
352 var f = this.findField(fieldError.id);
354 f.markInvalid(fieldError.msg);
360 if(typeof errors[id] != 'function' && (field = this.findField(id))){
361 field.markInvalid(errors[id]);
365 //Roo.each(this.childForms || [], function (f) {
366 // f.markInvalid(errors);
373 * Set values for fields in this form in bulk.
374 * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
375 * @return {BasicForm} this
377 setValues : function(values){
378 if(values instanceof Array){ // array of objects
379 for(var i = 0, len = values.length; i < len; i++){
381 var f = this.findField(v.id);
384 if(this.trackResetOnLoad){
385 f.originalValue = f.getValue();
389 }else{ // object hash
392 if(typeof values[id] != 'function' && (field = this.findField(id))){
394 if (field.setFromData &&
396 field.displayField &&
397 // combos' with local stores can
398 // be queried via setValue()
399 // to set their value..
400 (field.store && !field.store.isLocal)
404 sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
405 sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
406 field.setFromData(sd);
409 field.setValue(values[id]);
413 if(this.trackResetOnLoad){
414 field.originalValue = field.getValue();
420 //Roo.each(this.childForms || [], function (f) {
421 // f.setValues(values);
428 * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
429 * they are returned as an array.
430 * @param {Boolean} asString
433 getValues : function(asString){
434 //if (this.childForms) {
435 // copy values from the child forms
436 // Roo.each(this.childForms, function (f) {
437 // this.setValues(f.getValues());
443 var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
444 if(asString === true){
447 return Roo.urlDecode(fs);
451 * Returns the fields in this form as an object with key/value pairs.
452 * This differs from getValues as it calls getValue on each child item, rather than using dom data.
455 getFieldValues : function(with_hidden)
457 var items = this.getItems();
459 items.each(function(f){
463 var v = f.getValue();
464 if (f.inputType =='radio') {
465 if (typeof(ret[f.getName()]) == 'undefined') {
466 ret[f.getName()] = ''; // empty..
469 if (!f.el.dom.checked) {
477 // not sure if this supported any more..
478 if ((typeof(v) == 'object') && f.getRawValue) {
479 v = f.getRawValue() ; // dates..
481 // combo boxes where name != hiddenName...
482 if (f.name !== false && f.name != '' && f.name != f.getName()) {
483 ret[f.name] = f.getRawValue();
485 ret[f.getName()] = v;
492 * Clears all invalid messages in this form.
493 * @return {BasicForm} this
495 clearInvalid : function(){
496 var items = this.getItems();
498 items.each(function(f){
509 * @return {BasicForm} this
512 var items = this.getItems();
513 items.each(function(f){
517 Roo.each(this.childForms || [], function (f) {
524 getItems : function()
526 var r=new Roo.util.MixedCollection(false, function(o){
527 return o.id || (o.id = Roo.id());
529 var iter = function(el) {
536 Roo.each(el.items,function(e) {
553 Roo.apply(Roo.bootstrap.Form, {
580 top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
581 left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
582 bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
583 right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
586 this.maskEl.top.enableDisplayMode("block");
587 this.maskEl.left.enableDisplayMode("block");
588 this.maskEl.bottom.enableDisplayMode("block");
589 this.maskEl.right.enableDisplayMode("block");
591 this.toolTip = new Roo.bootstrap.Tooltip({
592 cls : 'roo-form-error-popover',
594 'left' : ['r-l', [-2,0], 'right'],
595 'right' : ['l-r', [2,0], 'left'],
596 'bottom' : ['tl-bl', [0,2], 'top'],
597 'top' : [ 'bl-tl', [0,-2], 'bottom']
601 this.toolTip.render(Roo.get(document.body));
603 this.toolTip.el.enableDisplayMode("block");
605 Roo.get(document.body).on('click', function(){
609 Roo.get(document.body).on('touchstart', function(){
613 this.isApplied = true
616 mask : function(form, target)
620 this.target = target;
622 if(!this.form.errorMask || !target.el){
626 var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.modal', 100, true) || Roo.get(document.body);
628 var ot = this.target.el.calcOffsetsTo(scrollable);
630 var scrollTo = ot[1] - this.form.maskOffset;
632 scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
634 scrollable.scrollTo('top', scrollTo);
636 var box = this.target.el.getBox();
638 var zIndex = Roo.bootstrap.Modal.zIndex++;
641 this.maskEl.top.setStyle('position', 'absolute');
642 this.maskEl.top.setStyle('z-index', zIndex);
643 this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding);
644 this.maskEl.top.setLeft(0);
645 this.maskEl.top.setTop(0);
646 this.maskEl.top.show();
648 this.maskEl.left.setStyle('position', 'absolute');
649 this.maskEl.left.setStyle('z-index', zIndex);
650 this.maskEl.left.setSize(box.x - this.padding, box.height + this.padding * 2);
651 this.maskEl.left.setLeft(0);
652 this.maskEl.left.setTop(box.y - this.padding);
653 this.maskEl.left.show();
655 this.maskEl.bottom.setStyle('position', 'absolute');
656 this.maskEl.bottom.setStyle('z-index', zIndex);
657 this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding);
658 this.maskEl.bottom.setLeft(0);
659 this.maskEl.bottom.setTop(box.bottom + this.padding);
660 this.maskEl.bottom.show();
662 this.maskEl.right.setStyle('position', 'absolute');
663 this.maskEl.right.setStyle('z-index', zIndex);
664 this.maskEl.right.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2);
665 this.maskEl.right.setLeft(box.right + this.padding);
666 this.maskEl.right.setTop(box.y - this.padding);
667 this.maskEl.right.show();
669 this.toolTip.bindEl = this.target.el;
671 this.toolTip.el.setStyle('z-index', Roo.bootstrap.Modal.zIndex++);
673 var tip = this.target.blankText;
675 if(this.target.getValue() !== '' ) {
677 if (this.target.invalidText.length) {
678 tip = this.target.invalidText;
679 } else if (this.target.regexText.length){
680 tip = this.target.regexText;
684 this.toolTip.show(tip);
686 this.intervalID = window.setInterval(function() {
687 Roo.bootstrap.Form.popover.unmask();
690 window.onwheel = function(){ return false;};
692 (function(){ this.isMasked = true; }).defer(500, this);
698 if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
702 this.maskEl.top.setStyle('position', 'absolute');
703 this.maskEl.top.setSize(0, 0).setXY([0, 0]);
704 this.maskEl.top.hide();
706 this.maskEl.left.setStyle('position', 'absolute');
707 this.maskEl.left.setSize(0, 0).setXY([0, 0]);
708 this.maskEl.left.hide();
710 this.maskEl.bottom.setStyle('position', 'absolute');
711 this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
712 this.maskEl.bottom.hide();
714 this.maskEl.right.setStyle('position', 'absolute');
715 this.maskEl.right.setSize(0, 0).setXY([0, 0]);
716 this.maskEl.right.hide();
720 this.toolTip.el.hide();
722 window.onwheel = function(){ return true;};
725 window.clearInterval(this.intervalID);
726 this.intervalID = false;
729 this.isMasked = false;