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){
176 if(!target && f.el.isVisible(true)){
182 if(this.errorMask && !valid){
183 Roo.bootstrap.Form.popover.mask(this, target);
190 * Returns true if any fields in this form have changed since their original load.
193 isDirty : function(){
195 var items = this.getItems();
196 items.each(function(f){
206 * Performs a predefined action (submit or load) or custom actions you define on this form.
207 * @param {String} actionName The name of the action type
208 * @param {Object} options (optional) The options to pass to the action. All of the config options listed
209 * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
210 * accept other config options):
212 Property Type Description
213 ---------------- --------------- ----------------------------------------------------------------------------------
214 url String The url for the action (defaults to the form's url)
215 method String The form method to use (defaults to the form's method, or POST if not defined)
216 params String/Object The params to pass (defaults to the form's baseParams, or none if not defined)
217 clientValidation Boolean Applies to submit only. Pass true to call form.isValid() prior to posting to
218 validate the form on the client (defaults to false)
220 * @return {BasicForm} this
222 doAction : function(action, options){
223 if(typeof action == 'string'){
224 action = new Roo.form.Action.ACTION_TYPES[action](this, options);
226 if(this.fireEvent('beforeaction', this, action) !== false){
227 this.beforeAction(action);
228 action.run.defer(100, action);
234 beforeAction : function(action){
235 var o = action.options;
238 this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
240 // not really supported yet.. ??
242 //if(this.waitMsgTarget === true){
243 // this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
244 //}else if(this.waitMsgTarget){
245 // this.waitMsgTarget = Roo.get(this.waitMsgTarget);
246 // this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
248 // Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
254 afterAction : function(action, success){
255 this.activeAction = null;
256 var o = action.options;
258 //if(this.waitMsgTarget === true){
260 //}else if(this.waitMsgTarget){
261 // this.waitMsgTarget.unmask();
263 // Roo.MessageBox.updateProgress(1);
264 // Roo.MessageBox.hide();
271 Roo.callback(o.success, o.scope, [this, action]);
272 this.fireEvent('actioncomplete', this, action);
276 // failure condition..
277 // we have a scenario where updates need confirming.
278 // eg. if a locking scenario exists..
279 // we look for { errors : { needs_confirm : true }} in the response.
281 (typeof(action.result) != 'undefined') &&
282 (typeof(action.result.errors) != 'undefined') &&
283 (typeof(action.result.errors.needs_confirm) != 'undefined')
286 Roo.log("not supported yet");
289 Roo.MessageBox.confirm(
290 "Change requires confirmation",
291 action.result.errorMsg,
296 _t.doAction('submit', { params : { _submit_confirmed : 1 } } );
306 Roo.callback(o.failure, o.scope, [this, action]);
307 // show an error message if no failed handler is set..
308 if (!this.hasListener('actionfailed')) {
309 Roo.log("need to add dialog support");
311 Roo.MessageBox.alert("Error",
312 (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
313 action.result.errorMsg :
314 "Saving Failed, please check your entries or try again"
319 this.fireEvent('actionfailed', this, action);
324 * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
325 * @param {String} id The value to search for
328 findField : function(id){
329 var items = this.getItems();
330 var field = items.get(id);
332 items.each(function(f){
333 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
340 return field || null;
343 * Mark fields in this form invalid in bulk.
344 * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
345 * @return {BasicForm} this
347 markInvalid : function(errors){
348 if(errors instanceof Array){
349 for(var i = 0, len = errors.length; i < len; i++){
350 var fieldError = errors[i];
351 var f = this.findField(fieldError.id);
353 f.markInvalid(fieldError.msg);
359 if(typeof errors[id] != 'function' && (field = this.findField(id))){
360 field.markInvalid(errors[id]);
364 //Roo.each(this.childForms || [], function (f) {
365 // f.markInvalid(errors);
372 * Set values for fields in this form in bulk.
373 * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
374 * @return {BasicForm} this
376 setValues : function(values){
377 if(values instanceof Array){ // array of objects
378 for(var i = 0, len = values.length; i < len; i++){
380 var f = this.findField(v.id);
383 if(this.trackResetOnLoad){
384 f.originalValue = f.getValue();
388 }else{ // object hash
391 if(typeof values[id] != 'function' && (field = this.findField(id))){
393 if (field.setFromData &&
395 field.displayField &&
396 // combos' with local stores can
397 // be queried via setValue()
398 // to set their value..
399 (field.store && !field.store.isLocal)
403 sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
404 sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
405 field.setFromData(sd);
407 } else if(field.setFromData && (field.store && !field.store.isLocal)) {
409 field.setFromData(values);
412 field.setValue(values[id]);
416 if(this.trackResetOnLoad){
417 field.originalValue = field.getValue();
423 //Roo.each(this.childForms || [], function (f) {
424 // f.setValues(values);
431 * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
432 * they are returned as an array.
433 * @param {Boolean} asString
436 getValues : function(asString){
437 //if (this.childForms) {
438 // copy values from the child forms
439 // Roo.each(this.childForms, function (f) {
440 // this.setValues(f.getValues());
446 var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
447 if(asString === true){
450 return Roo.urlDecode(fs);
454 * Returns the fields in this form as an object with key/value pairs.
455 * This differs from getValues as it calls getValue on each child item, rather than using dom data.
458 getFieldValues : function(with_hidden)
460 var items = this.getItems();
462 items.each(function(f){
466 var v = f.getValue();
467 if (f.inputType =='radio') {
468 if (typeof(ret[f.getName()]) == 'undefined') {
469 ret[f.getName()] = ''; // empty..
472 if (!f.el.dom.checked) {
480 // not sure if this supported any more..
481 if ((typeof(v) == 'object') && f.getRawValue) {
482 v = f.getRawValue() ; // dates..
484 // combo boxes where name != hiddenName...
485 if (f.name !== false && f.name != '' && f.name != f.getName()) {
486 ret[f.name] = f.getRawValue();
488 ret[f.getName()] = v;
495 * Clears all invalid messages in this form.
496 * @return {BasicForm} this
498 clearInvalid : function(){
499 var items = this.getItems();
501 items.each(function(f){
512 * @return {BasicForm} this
515 var items = this.getItems();
516 items.each(function(f){
520 Roo.each(this.childForms || [], function (f) {
527 getItems : function()
529 var r=new Roo.util.MixedCollection(false, function(o){
530 return o.id || (o.id = Roo.id());
532 var iter = function(el) {
539 Roo.each(el.items,function(e) {
556 Roo.apply(Roo.bootstrap.Form, {
583 top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
584 left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
585 bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
586 right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
589 this.maskEl.top.enableDisplayMode("block");
590 this.maskEl.left.enableDisplayMode("block");
591 this.maskEl.bottom.enableDisplayMode("block");
592 this.maskEl.right.enableDisplayMode("block");
594 this.toolTip = new Roo.bootstrap.Tooltip({
595 cls : 'roo-form-error-popover',
597 'left' : ['r-l', [-2,0], 'right'],
598 'right' : ['l-r', [2,0], 'left'],
599 'bottom' : ['tl-bl', [0,2], 'top'],
600 'top' : [ 'bl-tl', [0,-2], 'bottom']
604 this.toolTip.render(Roo.get(document.body));
606 this.toolTip.el.enableDisplayMode("block");
608 Roo.get(document.body).on('click', function(){
612 Roo.get(document.body).on('touchstart', function(){
616 this.isApplied = true
619 mask : function(form, target)
623 this.target = target;
625 if(!this.form.errorMask || !target.el){
629 var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.modal', 100, true) || Roo.get(document.body);
633 var ot = this.target.el.calcOffsetsTo(scrollable);
635 var scrollTo = ot[1] - this.form.maskOffset;
637 scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
639 scrollable.scrollTo('top', scrollTo);
641 var box = this.target.el.getBox();
643 var zIndex = Roo.bootstrap.Modal.zIndex++;
646 this.maskEl.top.setStyle('position', 'absolute');
647 this.maskEl.top.setStyle('z-index', zIndex);
648 this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding);
649 this.maskEl.top.setLeft(0);
650 this.maskEl.top.setTop(0);
651 this.maskEl.top.show();
653 this.maskEl.left.setStyle('position', 'absolute');
654 this.maskEl.left.setStyle('z-index', zIndex);
655 this.maskEl.left.setSize(box.x - this.padding, box.height + this.padding * 2);
656 this.maskEl.left.setLeft(0);
657 this.maskEl.left.setTop(box.y - this.padding);
658 this.maskEl.left.show();
660 this.maskEl.bottom.setStyle('position', 'absolute');
661 this.maskEl.bottom.setStyle('z-index', zIndex);
662 this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding);
663 this.maskEl.bottom.setLeft(0);
664 this.maskEl.bottom.setTop(box.bottom + this.padding);
665 this.maskEl.bottom.show();
667 this.maskEl.right.setStyle('position', 'absolute');
668 this.maskEl.right.setStyle('z-index', zIndex);
669 this.maskEl.right.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2);
670 this.maskEl.right.setLeft(box.right + this.padding);
671 this.maskEl.right.setTop(box.y - this.padding);
672 this.maskEl.right.show();
674 this.toolTip.bindEl = this.target.el;
676 this.toolTip.el.setStyle('z-index', Roo.bootstrap.Modal.zIndex++);
678 var tip = this.target.blankText;
680 if(this.target.getValue() !== '' ) {
682 if (this.target.invalidText.length) {
683 tip = this.target.invalidText;
684 } else if (this.target.regexText.length){
685 tip = this.target.regexText;
689 this.toolTip.show(tip);
691 this.intervalID = window.setInterval(function() {
692 Roo.bootstrap.Form.popover.unmask();
695 window.onwheel = function(){ return false;};
697 (function(){ this.isMasked = true; }).defer(500, this);
703 if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
707 this.maskEl.top.setStyle('position', 'absolute');
708 this.maskEl.top.setSize(0, 0).setXY([0, 0]);
709 this.maskEl.top.hide();
711 this.maskEl.left.setStyle('position', 'absolute');
712 this.maskEl.left.setSize(0, 0).setXY([0, 0]);
713 this.maskEl.left.hide();
715 this.maskEl.bottom.setStyle('position', 'absolute');
716 this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
717 this.maskEl.bottom.hide();
719 this.maskEl.right.setStyle('position', 'absolute');
720 this.maskEl.right.setSize(0, 0).setXY([0, 0]);
721 this.maskEl.right.hide();
725 this.toolTip.el.hide();
727 window.onwheel = function(){ return true;};
730 window.clearInterval(this.intervalID);
731 this.intervalID = false;
734 this.isMasked = false;