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 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;
271 Roo.get(document.body).unmask();
277 //if(this.waitMsgTarget === true){
279 //}else if(this.waitMsgTarget){
280 // this.waitMsgTarget.unmask();
282 // Roo.MessageBox.updateProgress(1);
283 // Roo.MessageBox.hide();
290 Roo.callback(o.success, o.scope, [this, action]);
291 this.fireEvent('actioncomplete', this, action);
295 // failure condition..
296 // we have a scenario where updates need confirming.
297 // eg. if a locking scenario exists..
298 // we look for { errors : { needs_confirm : true }} in the response.
300 (typeof(action.result) != 'undefined') &&
301 (typeof(action.result.errors) != 'undefined') &&
302 (typeof(action.result.errors.needs_confirm) != 'undefined')
305 Roo.log("not supported yet");
308 Roo.MessageBox.confirm(
309 "Change requires confirmation",
310 action.result.errorMsg,
315 _t.doAction('submit', { params : { _submit_confirmed : 1 } } );
325 Roo.callback(o.failure, o.scope, [this, action]);
326 // show an error message if no failed handler is set..
327 if (!this.hasListener('actionfailed')) {
328 Roo.log("need to add dialog support");
330 Roo.MessageBox.alert("Error",
331 (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
332 action.result.errorMsg :
333 "Saving Failed, please check your entries or try again"
338 this.fireEvent('actionfailed', this, action);
343 * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
344 * @param {String} id The value to search for
347 findField : function(id){
348 var items = this.getItems();
349 var field = items.get(id);
351 items.each(function(f){
352 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
359 return field || null;
362 * Mark fields in this form invalid in bulk.
363 * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
364 * @return {BasicForm} this
366 markInvalid : function(errors){
367 if(errors instanceof Array){
368 for(var i = 0, len = errors.length; i < len; i++){
369 var fieldError = errors[i];
370 var f = this.findField(fieldError.id);
372 f.markInvalid(fieldError.msg);
378 if(typeof errors[id] != 'function' && (field = this.findField(id))){
379 field.markInvalid(errors[id]);
383 //Roo.each(this.childForms || [], function (f) {
384 // f.markInvalid(errors);
391 * Set values for fields in this form in bulk.
392 * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
393 * @return {BasicForm} this
395 setValues : function(values){
396 if(values instanceof Array){ // array of objects
397 for(var i = 0, len = values.length; i < len; i++){
399 var f = this.findField(v.id);
402 if(this.trackResetOnLoad){
403 f.originalValue = f.getValue();
407 }else{ // object hash
410 if(typeof values[id] != 'function' && (field = this.findField(id))){
412 if (field.setFromData &&
414 field.displayField &&
415 // combos' with local stores can
416 // be queried via setValue()
417 // to set their value..
418 (field.store && !field.store.isLocal)
422 sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
423 sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
424 field.setFromData(sd);
426 } else if(field.setFromData && (field.store && !field.store.isLocal)) {
428 field.setFromData(values);
431 field.setValue(values[id]);
435 if(this.trackResetOnLoad){
436 field.originalValue = field.getValue();
442 //Roo.each(this.childForms || [], function (f) {
443 // f.setValues(values);
450 * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
451 * they are returned as an array.
452 * @param {Boolean} asString
455 getValues : function(asString){
456 //if (this.childForms) {
457 // copy values from the child forms
458 // Roo.each(this.childForms, function (f) {
459 // this.setValues(f.getValues());
465 var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
466 if(asString === true){
469 return Roo.urlDecode(fs);
473 * Returns the fields in this form as an object with key/value pairs.
474 * This differs from getValues as it calls getValue on each child item, rather than using dom data.
477 getFieldValues : function(with_hidden)
479 var items = this.getItems();
481 items.each(function(f){
487 var v = f.getValue();
489 if (f.inputType =='radio') {
490 if (typeof(ret[f.getName()]) == 'undefined') {
491 ret[f.getName()] = ''; // empty..
494 if (!f.el.dom.checked) {
502 if(f.xtype == 'MoneyField'){
503 ret[f.currencyName] = f.getCurrency();
506 // not sure if this supported any more..
507 if ((typeof(v) == 'object') && f.getRawValue) {
508 v = f.getRawValue() ; // dates..
510 // combo boxes where name != hiddenName...
511 if (f.name !== false && f.name != '' && f.name != f.getName()) {
512 ret[f.name] = f.getRawValue();
514 ret[f.getName()] = v;
521 * Clears all invalid messages in this form.
522 * @return {BasicForm} this
524 clearInvalid : function(){
525 var items = this.getItems();
527 items.each(function(f){
536 * @return {BasicForm} this
539 var items = this.getItems();
540 items.each(function(f){
544 Roo.each(this.childForms || [], function (f) {
552 getItems : function()
554 var r=new Roo.util.MixedCollection(false, function(o){
555 return o.id || (o.id = Roo.id());
557 var iter = function(el) {
564 Roo.each(el.items,function(e) {
573 hideFields : function(items)
575 Roo.each(items, function(i){
577 var f = this.findField(i);
583 if(f.xtype == 'DateField'){
593 showFields : function(items)
595 Roo.each(items, function(i){
597 var f = this.findField(i);
603 if(f.xtype == 'DateField'){
615 Roo.apply(Roo.bootstrap.Form, {
642 top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
643 left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
644 bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
645 right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
648 this.maskEl.top.enableDisplayMode("block");
649 this.maskEl.left.enableDisplayMode("block");
650 this.maskEl.bottom.enableDisplayMode("block");
651 this.maskEl.right.enableDisplayMode("block");
653 this.toolTip = new Roo.bootstrap.Tooltip({
654 cls : 'roo-form-error-popover',
656 'left' : ['r-l', [-2,0], 'right'],
657 'right' : ['l-r', [2,0], 'left'],
658 'bottom' : ['tl-bl', [0,2], 'top'],
659 'top' : [ 'bl-tl', [0,-2], 'bottom']
663 this.toolTip.render(Roo.get(document.body));
665 this.toolTip.el.enableDisplayMode("block");
667 Roo.get(document.body).on('click', function(){
671 Roo.get(document.body).on('touchstart', function(){
675 this.isApplied = true
678 mask : function(form, target)
682 this.target = target;
684 if(!this.form.errorMask || !target.el){
688 var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.modal', 100, true) || Roo.get(document.body);
692 var ot = this.target.el.calcOffsetsTo(scrollable);
694 var scrollTo = ot[1] - this.form.maskOffset;
696 scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
698 scrollable.scrollTo('top', scrollTo);
700 var box = this.target.el.getBox();
702 var zIndex = Roo.bootstrap.Modal.zIndex++;
705 this.maskEl.top.setStyle('position', 'absolute');
706 this.maskEl.top.setStyle('z-index', zIndex);
707 this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding);
708 this.maskEl.top.setLeft(0);
709 this.maskEl.top.setTop(0);
710 this.maskEl.top.show();
712 this.maskEl.left.setStyle('position', 'absolute');
713 this.maskEl.left.setStyle('z-index', zIndex);
714 this.maskEl.left.setSize(box.x - this.padding, box.height + this.padding * 2);
715 this.maskEl.left.setLeft(0);
716 this.maskEl.left.setTop(box.y - this.padding);
717 this.maskEl.left.show();
719 this.maskEl.bottom.setStyle('position', 'absolute');
720 this.maskEl.bottom.setStyle('z-index', zIndex);
721 this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding);
722 this.maskEl.bottom.setLeft(0);
723 this.maskEl.bottom.setTop(box.bottom + this.padding);
724 this.maskEl.bottom.show();
726 this.maskEl.right.setStyle('position', 'absolute');
727 this.maskEl.right.setStyle('z-index', zIndex);
728 this.maskEl.right.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2);
729 this.maskEl.right.setLeft(box.right + this.padding);
730 this.maskEl.right.setTop(box.y - this.padding);
731 this.maskEl.right.show();
733 this.toolTip.bindEl = this.target.el;
735 this.toolTip.el.setStyle('z-index', Roo.bootstrap.Modal.zIndex++);
737 var tip = this.target.blankText;
739 if(this.target.getValue() !== '' ) {
741 if (this.target.invalidText.length) {
742 tip = this.target.invalidText;
743 } else if (this.target.regexText.length){
744 tip = this.target.regexText;
748 this.toolTip.show(tip);
750 this.intervalID = window.setInterval(function() {
751 Roo.bootstrap.Form.popover.unmask();
754 window.onwheel = function(){ return false;};
756 (function(){ this.isMasked = true; }).defer(500, this);
762 if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
766 this.maskEl.top.setStyle('position', 'absolute');
767 this.maskEl.top.setSize(0, 0).setXY([0, 0]);
768 this.maskEl.top.hide();
770 this.maskEl.left.setStyle('position', 'absolute');
771 this.maskEl.left.setSize(0, 0).setXY([0, 0]);
772 this.maskEl.left.hide();
774 this.maskEl.bottom.setStyle('position', 'absolute');
775 this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
776 this.maskEl.bottom.hide();
778 this.maskEl.right.setStyle('position', 'absolute');
779 this.maskEl.right.setSize(0, 0).setXY([0, 0]);
780 this.maskEl.right.hide();
784 this.toolTip.el.hide();
786 window.onwheel = function(){ return true;};
789 window.clearInterval(this.intervalID);
790 this.intervalID = false;
793 this.isMasked = false;