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
109 getAutoCreate : function(){
113 method : this.method || 'POST',
114 id : this.id || Roo.id(),
117 if (this.parent().xtype.match(/^Nav/)) {
118 cfg.cls = 'navbar-form navbar-' + this.align;
122 if (this.labelAlign == 'left' ) {
123 cfg.cls += ' form-horizontal';
129 initEvents : function()
131 this.el.on('submit', this.onSubmit, this);
132 // this was added as random key presses on the form where triggering form submit.
133 this.el.on('keypress', function(e) {
134 if (e.getCharCode() != 13) {
137 // we might need to allow it for textareas.. and some other items.
138 // check e.getTarget().
140 if(e.getTarget().nodeName.toLowerCase() === 'textarea'){
144 Roo.log("keypress blocked");
152 onSubmit : function(e){
157 * Returns true if client-side validation on the form is successful.
160 isValid : function(){
161 var items = this.getItems();
165 items.each(function(f){
172 if(!target && f.el.isVisible(true)){
178 if(this.errorMask && !valid){
179 Roo.bootstrap.Form.popover.mask(this, target);
186 * Returns true if any fields in this form have changed since their original load.
189 isDirty : function(){
191 var items = this.getItems();
192 items.each(function(f){
202 * Performs a predefined action (submit or load) or custom actions you define on this form.
203 * @param {String} actionName The name of the action type
204 * @param {Object} options (optional) The options to pass to the action. All of the config options listed
205 * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
206 * accept other config options):
208 Property Type Description
209 ---------------- --------------- ----------------------------------------------------------------------------------
210 url String The url for the action (defaults to the form's url)
211 method String The form method to use (defaults to the form's method, or POST if not defined)
212 params String/Object The params to pass (defaults to the form's baseParams, or none if not defined)
213 clientValidation Boolean Applies to submit only. Pass true to call form.isValid() prior to posting to
214 validate the form on the client (defaults to false)
216 * @return {BasicForm} this
218 doAction : function(action, options){
219 if(typeof action == 'string'){
220 action = new Roo.form.Action.ACTION_TYPES[action](this, options);
222 if(this.fireEvent('beforeaction', this, action) !== false){
223 this.beforeAction(action);
224 action.run.defer(100, action);
230 beforeAction : function(action){
231 var o = action.options;
234 this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
236 // not really supported yet.. ??
238 //if(this.waitMsgTarget === true){
239 // this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
240 //}else if(this.waitMsgTarget){
241 // this.waitMsgTarget = Roo.get(this.waitMsgTarget);
242 // this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
244 // Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
250 afterAction : function(action, success){
251 this.activeAction = null;
252 var o = action.options;
254 //if(this.waitMsgTarget === true){
256 //}else if(this.waitMsgTarget){
257 // this.waitMsgTarget.unmask();
259 // Roo.MessageBox.updateProgress(1);
260 // Roo.MessageBox.hide();
267 Roo.callback(o.success, o.scope, [this, action]);
268 this.fireEvent('actioncomplete', this, action);
272 // failure condition..
273 // we have a scenario where updates need confirming.
274 // eg. if a locking scenario exists..
275 // we look for { errors : { needs_confirm : true }} in the response.
277 (typeof(action.result) != 'undefined') &&
278 (typeof(action.result.errors) != 'undefined') &&
279 (typeof(action.result.errors.needs_confirm) != 'undefined')
282 Roo.log("not supported yet");
285 Roo.MessageBox.confirm(
286 "Change requires confirmation",
287 action.result.errorMsg,
292 _t.doAction('submit', { params : { _submit_confirmed : 1 } } );
302 Roo.callback(o.failure, o.scope, [this, action]);
303 // show an error message if no failed handler is set..
304 if (!this.hasListener('actionfailed')) {
305 Roo.log("need to add dialog support");
307 Roo.MessageBox.alert("Error",
308 (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
309 action.result.errorMsg :
310 "Saving Failed, please check your entries or try again"
315 this.fireEvent('actionfailed', this, action);
320 * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
321 * @param {String} id The value to search for
324 findField : function(id){
325 var items = this.getItems();
326 var field = items.get(id);
328 items.each(function(f){
329 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
336 return field || null;
339 * Mark fields in this form invalid in bulk.
340 * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
341 * @return {BasicForm} this
343 markInvalid : function(errors){
344 if(errors instanceof Array){
345 for(var i = 0, len = errors.length; i < len; i++){
346 var fieldError = errors[i];
347 var f = this.findField(fieldError.id);
349 f.markInvalid(fieldError.msg);
355 if(typeof errors[id] != 'function' && (field = this.findField(id))){
356 field.markInvalid(errors[id]);
360 //Roo.each(this.childForms || [], function (f) {
361 // f.markInvalid(errors);
368 * Set values for fields in this form in bulk.
369 * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
370 * @return {BasicForm} this
372 setValues : function(values){
373 if(values instanceof Array){ // array of objects
374 for(var i = 0, len = values.length; i < len; i++){
376 var f = this.findField(v.id);
379 if(this.trackResetOnLoad){
380 f.originalValue = f.getValue();
384 }else{ // object hash
387 if(typeof values[id] != 'function' && (field = this.findField(id))){
389 if (field.setFromData &&
391 field.displayField &&
392 // combos' with local stores can
393 // be queried via setValue()
394 // to set their value..
395 (field.store && !field.store.isLocal)
399 sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
400 sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
401 field.setFromData(sd);
404 field.setValue(values[id]);
408 if(this.trackResetOnLoad){
409 field.originalValue = field.getValue();
415 //Roo.each(this.childForms || [], function (f) {
416 // f.setValues(values);
423 * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
424 * they are returned as an array.
425 * @param {Boolean} asString
428 getValues : function(asString){
429 //if (this.childForms) {
430 // copy values from the child forms
431 // Roo.each(this.childForms, function (f) {
432 // this.setValues(f.getValues());
438 var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
439 if(asString === true){
442 return Roo.urlDecode(fs);
446 * Returns the fields in this form as an object with key/value pairs.
447 * This differs from getValues as it calls getValue on each child item, rather than using dom data.
450 getFieldValues : function(with_hidden)
452 var items = this.getItems();
454 items.each(function(f){
458 var v = f.getValue();
459 if (f.inputType =='radio') {
460 if (typeof(ret[f.getName()]) == 'undefined') {
461 ret[f.getName()] = ''; // empty..
464 if (!f.el.dom.checked) {
472 // not sure if this supported any more..
473 if ((typeof(v) == 'object') && f.getRawValue) {
474 v = f.getRawValue() ; // dates..
476 // combo boxes where name != hiddenName...
477 if (f.name !== false && f.name != '' && f.name != f.getName()) {
478 ret[f.name] = f.getRawValue();
480 ret[f.getName()] = v;
487 * Clears all invalid messages in this form.
488 * @return {BasicForm} this
490 clearInvalid : function(){
491 var items = this.getItems();
493 items.each(function(f){
504 * @return {BasicForm} this
507 var items = this.getItems();
508 items.each(function(f){
512 Roo.each(this.childForms || [], function (f) {
519 getItems : function()
521 var r=new Roo.util.MixedCollection(false, function(o){
522 return o.id || (o.id = Roo.id());
524 var iter = function(el) {
531 Roo.each(el.items,function(e) {
548 Roo.apply(Roo.bootstrap.Form, {
575 top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
576 left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
577 bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
578 right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
581 this.maskEl.top.enableDisplayMode("block");
582 this.maskEl.left.enableDisplayMode("block");
583 this.maskEl.bottom.enableDisplayMode("block");
584 this.maskEl.right.enableDisplayMode("block");
586 this.toolTip = new Roo.bootstrap.Tooltip({
587 cls : 'roo-form-error-popover',
589 'left' : ['r-l', [-2,0], 'right'],
590 'right' : ['l-r', [2,0], 'left'],
591 'bottom' : ['tl-bl', [0,2], 'top'],
592 'top' : [ 'bl-tl', [0,-2], 'bottom']
596 this.toolTip.render(Roo.get(document.body));
598 this.toolTip.el.enableDisplayMode("block");
600 Roo.get(document.body).on('click', function(){
604 this.isApplied = true
607 mask : function(form, target)
611 this.target = target;
613 if(!this.form.errorMask || !target.el){
617 var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.modal', 100, true) || Roo.get(document.body);
619 var scrolled = scrollable.getScroll();
621 var ot = this.target.el.calcOffsetsTo(scrollable);
625 if(ot[1] <= scrolled.top){
626 scrollTo = ot[1] - 100;
628 scrollTo = ot[1] + Roo.lib.Dom.getViewHeight() - 100;
631 scrollable.scrollTo('top', scrollTo);
633 var box = this.target.el.getBox();
635 var zIndex = Roo.bootstrap.Modal.zIndex++;
637 this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding);
638 this.maskEl.top.setXY([0, 0]);
639 this.maskEl.top.setStyle('z-index', zIndex);
640 this.maskEl.top.show();
642 this.maskEl.left.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2);
643 this.maskEl.left.setXY([box.right + this.padding, box.y - this.padding]);
644 this.maskEl.left.setStyle('z-index', zIndex);
645 this.maskEl.left.show();
647 this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding);
648 this.maskEl.bottom.setXY([0, box.bottom + this.padding]);
649 this.maskEl.bottom.setStyle('z-index', zIndex);
650 this.maskEl.bottom.show();
652 this.maskEl.right.setSize(box.x - this.padding, box.height + this.padding * 2);
653 this.maskEl.right.setXY([0, box.y - this.padding]);
654 this.maskEl.right.setStyle('z-index', zIndex);
655 this.maskEl.right.show();
658 this.toolTip.bindEl = this.target.el;
660 this.toolTip.el.setStyle('z-index', Roo.bootstrap.Modal.zIndex++);
662 var tip = this.target.blankText;
664 if(this.target.getValue() !== '' && this.target.regexText.length){
665 tip = this.target.regexText;
668 this.toolTip.show(tip);
670 this.intervalID = window.setInterval(function() {
671 Roo.bootstrap.Form.popover.unmask();
674 window.onwheel = function(){ return false;};
676 (function(){ this.isMasked = true; }).defer(500, this);
682 if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
686 this.maskEl.top.setSize(0, 0).setXY([0, 0]).hide();
687 this.maskEl.left.setSize(0, 0).setXY([0, 0]).hide();
688 this.maskEl.bottom.setSize(0, 0).setXY([0, 0]).hide();
689 this.maskEl.right.setSize(0, 0).setXY([0, 0]).hide();
693 this.toolTip.el.hide();
695 window.onwheel = function(){ return true;};
698 window.clearInterval(this.intervalID);
699 this.intervalID = false;
702 this.isMasked = false;