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){
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;
243 this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
245 // not really supported yet.. ??
247 //if(this.waitMsgTarget === true){
248 // this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
249 //}else if(this.waitMsgTarget){
250 // this.waitMsgTarget = Roo.get(this.waitMsgTarget);
251 // this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
253 // Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
259 afterAction : function(action, success){
260 this.activeAction = null;
261 var o = action.options;
263 //if(this.waitMsgTarget === true){
265 //}else if(this.waitMsgTarget){
266 // this.waitMsgTarget.unmask();
268 // Roo.MessageBox.updateProgress(1);
269 // Roo.MessageBox.hide();
276 Roo.callback(o.success, o.scope, [this, action]);
277 this.fireEvent('actioncomplete', this, action);
281 // failure condition..
282 // we have a scenario where updates need confirming.
283 // eg. if a locking scenario exists..
284 // we look for { errors : { needs_confirm : true }} in the response.
286 (typeof(action.result) != 'undefined') &&
287 (typeof(action.result.errors) != 'undefined') &&
288 (typeof(action.result.errors.needs_confirm) != 'undefined')
291 Roo.log("not supported yet");
294 Roo.MessageBox.confirm(
295 "Change requires confirmation",
296 action.result.errorMsg,
301 _t.doAction('submit', { params : { _submit_confirmed : 1 } } );
311 Roo.callback(o.failure, o.scope, [this, action]);
312 // show an error message if no failed handler is set..
313 if (!this.hasListener('actionfailed')) {
314 Roo.log("need to add dialog support");
316 Roo.MessageBox.alert("Error",
317 (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
318 action.result.errorMsg :
319 "Saving Failed, please check your entries or try again"
324 this.fireEvent('actionfailed', this, action);
329 * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
330 * @param {String} id The value to search for
333 findField : function(id){
334 var items = this.getItems();
335 var field = items.get(id);
337 items.each(function(f){
338 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
345 return field || null;
348 * Mark fields in this form invalid in bulk.
349 * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
350 * @return {BasicForm} this
352 markInvalid : function(errors){
353 if(errors instanceof Array){
354 for(var i = 0, len = errors.length; i < len; i++){
355 var fieldError = errors[i];
356 var f = this.findField(fieldError.id);
358 f.markInvalid(fieldError.msg);
364 if(typeof errors[id] != 'function' && (field = this.findField(id))){
365 field.markInvalid(errors[id]);
369 //Roo.each(this.childForms || [], function (f) {
370 // f.markInvalid(errors);
377 * Set values for fields in this form in bulk.
378 * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
379 * @return {BasicForm} this
381 setValues : function(values){
382 if(values instanceof Array){ // array of objects
383 for(var i = 0, len = values.length; i < len; i++){
385 var f = this.findField(v.id);
388 if(this.trackResetOnLoad){
389 f.originalValue = f.getValue();
393 }else{ // object hash
396 if(typeof values[id] != 'function' && (field = this.findField(id))){
398 if (field.setFromData &&
400 field.displayField &&
401 // combos' with local stores can
402 // be queried via setValue()
403 // to set their value..
404 (field.store && !field.store.isLocal)
408 sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
409 sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
410 field.setFromData(sd);
413 field.setValue(values[id]);
417 if(this.trackResetOnLoad){
418 field.originalValue = field.getValue();
424 //Roo.each(this.childForms || [], function (f) {
425 // f.setValues(values);
432 * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
433 * they are returned as an array.
434 * @param {Boolean} asString
437 getValues : function(asString){
438 //if (this.childForms) {
439 // copy values from the child forms
440 // Roo.each(this.childForms, function (f) {
441 // this.setValues(f.getValues());
447 var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
448 if(asString === true){
451 return Roo.urlDecode(fs);
455 * Returns the fields in this form as an object with key/value pairs.
456 * This differs from getValues as it calls getValue on each child item, rather than using dom data.
459 getFieldValues : function(with_hidden)
461 var items = this.getItems();
463 items.each(function(f){
467 var v = f.getValue();
468 if (f.inputType =='radio') {
469 if (typeof(ret[f.getName()]) == 'undefined') {
470 ret[f.getName()] = ''; // empty..
473 if (!f.el.dom.checked) {
481 // not sure if this supported any more..
482 if ((typeof(v) == 'object') && f.getRawValue) {
483 v = f.getRawValue() ; // dates..
485 // combo boxes where name != hiddenName...
486 if (f.name !== false && f.name != '' && f.name != f.getName()) {
487 ret[f.name] = f.getRawValue();
489 ret[f.getName()] = v;
496 * Clears all invalid messages in this form.
497 * @return {BasicForm} this
499 clearInvalid : function(){
500 var items = this.getItems();
502 items.each(function(f){
513 * @return {BasicForm} this
516 var items = this.getItems();
517 items.each(function(f){
521 Roo.each(this.childForms || [], function (f) {
528 getItems : function()
530 var r=new Roo.util.MixedCollection(false, function(o){
531 return o.id || (o.id = Roo.id());
533 var iter = function(el) {
540 Roo.each(el.items,function(e) {
557 Roo.apply(Roo.bootstrap.Form, {
584 top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
585 left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
586 bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
587 right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
590 this.maskEl.top.enableDisplayMode("block");
591 this.maskEl.left.enableDisplayMode("block");
592 this.maskEl.bottom.enableDisplayMode("block");
593 this.maskEl.right.enableDisplayMode("block");
595 this.toolTip = new Roo.bootstrap.Tooltip({
596 cls : 'roo-form-error-popover',
598 'left' : ['r-l', [-2,0], 'right'],
599 'right' : ['l-r', [2,0], 'left'],
600 'bottom' : ['tl-bl', [0,2], 'top'],
601 'top' : [ 'bl-tl', [0,-2], 'bottom']
605 this.toolTip.render(Roo.get(document.body));
607 this.toolTip.el.enableDisplayMode("block");
609 Roo.get(document.body).on('click', function(){
613 Roo.get(document.body).on('touchstart', function(){
617 this.isApplied = true
620 mask : function(form, target)
624 this.target = target;
626 if(!this.form.errorMask || !target.el){
630 var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.modal', 100, true) || Roo.get(document.body);
632 var ot = this.target.el.calcOffsetsTo(scrollable);
634 var scrollTo = ot[1] - this.form.maskOffset;
636 scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
638 scrollable.scrollTo('top', scrollTo);
640 var box = this.target.el.getBox();
642 var zIndex = Roo.bootstrap.Modal.zIndex++;
645 this.maskEl.top.setStyle('position', 'absolute');
646 this.maskEl.top.setStyle('z-index', zIndex);
647 this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding);
648 this.maskEl.top.setLeft(0);
649 this.maskEl.top.setTop(0);
650 this.maskEl.top.show();
652 this.maskEl.left.setStyle('position', 'absolute');
653 this.maskEl.left.setStyle('z-index', zIndex);
654 this.maskEl.left.setSize(box.x - this.padding, box.height + this.padding * 2);
655 this.maskEl.left.setLeft(0);
656 this.maskEl.left.setTop(box.y - this.padding);
657 this.maskEl.left.show();
659 this.maskEl.bottom.setStyle('position', 'absolute');
660 this.maskEl.bottom.setStyle('z-index', zIndex);
661 this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding);
662 this.maskEl.bottom.setLeft(0);
663 this.maskEl.bottom.setTop(box.bottom + this.padding);
664 this.maskEl.bottom.show();
666 this.maskEl.right.setStyle('position', 'absolute');
667 this.maskEl.right.setStyle('z-index', zIndex);
668 this.maskEl.right.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2);
669 this.maskEl.right.setLeft(box.right + this.padding);
670 this.maskEl.right.setTop(box.y - this.padding);
671 this.maskEl.right.show();
673 this.toolTip.bindEl = this.target.el;
675 this.toolTip.el.setStyle('z-index', Roo.bootstrap.Modal.zIndex++);
677 var tip = this.target.blankText;
679 if(this.target.getValue() !== '' ) {
680 if (this.target.errorText.length) {
681 tip = this.target.errorText;
682 } else if (this.target.regexText.length){
683 tip = this.target.regexText;
687 this.toolTip.show(tip);
689 this.intervalID = window.setInterval(function() {
690 Roo.bootstrap.Form.popover.unmask();
693 window.onwheel = function(){ return false;};
695 (function(){ this.isMasked = true; }).defer(500, this);
701 if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
705 this.maskEl.top.setStyle('position', 'absolute');
706 this.maskEl.top.setSize(0, 0).setXY([0, 0]);
707 this.maskEl.top.hide();
709 this.maskEl.left.setStyle('position', 'absolute');
710 this.maskEl.left.setSize(0, 0).setXY([0, 0]);
711 this.maskEl.left.hide();
713 this.maskEl.bottom.setStyle('position', 'absolute');
714 this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
715 this.maskEl.bottom.hide();
717 this.maskEl.right.setStyle('position', 'absolute');
718 this.maskEl.right.setSize(0, 0).setXY([0, 0]);
719 this.maskEl.right.hide();
723 this.toolTip.el.hide();
725 window.onwheel = function(){ return true;};
728 window.clearInterval(this.intervalID);
729 this.intervalID = false;
732 this.isMasked = false;