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);
28 * @event clientvalidation
29 * If the monitorValid config option is true, this event fires repetitively to notify of valid state
31 * @param {Boolean} valid true if the form has passed client-side validation
33 clientvalidation: true,
36 * Fires before any action is performed. Return false to cancel the action.
38 * @param {Action} action The action to be performed
43 * Fires when an action fails.
45 * @param {Action} action The action that failed
49 * @event actioncomplete
50 * Fires when an action is completed.
52 * @param {Action} action The action that completed
59 Roo.extend(Roo.bootstrap.Form, Roo.bootstrap.Component, {
62 * @cfg {String} method
63 * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
68 * The URL to use for form actions if one isn't supplied in the action options.
71 * @cfg {Boolean} fileUpload
72 * Set to true if this form is a file upload.
76 * @cfg {Object} baseParams
77 * Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.
81 * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
85 * @cfg {Sting} align (left|right) for navbar forms
93 * By default wait messages are displayed with Roo.MessageBox.wait. You can target a specific
94 * element by passing it or its id or mask the form itself by passing in true.
97 waitMsgTarget : false,
102 * @cfg {Boolean} errPopover (true|false) default false
106 getAutoCreate : function(){
110 method : this.method || 'POST',
111 id : this.id || Roo.id(),
114 if (this.parent().xtype.match(/^Nav/)) {
115 cfg.cls = 'navbar-form navbar-' + this.align;
119 if (this.labelAlign == 'left' ) {
120 cfg.cls += ' form-horizontal';
126 initEvents : function()
128 this.el.on('submit', this.onSubmit, this);
129 // this was added as random key presses on the form where triggering form submit.
130 this.el.on('keypress', function(e) {
131 if (e.getCharCode() != 13) {
134 // we might need to allow it for textareas.. and some other items.
135 // check e.getTarget().
137 if(e.getTarget().nodeName.toLowerCase() === 'textarea'){
141 Roo.log("keypress blocked");
147 this.errTooltip = new Roo.bootstrap.Tooltip();
148 this.errTooltip.render(this.el);
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){
179 if(this.errPopover && !valid){
180 this.showErrPopover(target);
186 showErrPopover : function(target)
188 if(!this.errPopover){
192 var oIndex = target.el.getStyle('z-index');
194 target.el.setStyle('z-index', Roo.bootstrap.Modal.zIndex++);
196 target.el.addClass('roo-invalid-outline');
198 target.inputEl().focus();
200 this.errTooltip.el.select('.tooltip-inner',true).first().dom.innerHTML = 'This field is required';
202 this.errTooltip.el.removeClass(['fade','top','bottom', 'left', 'right','in']);
206 var fadeout = function(){
208 target.inputEl().un('blur', fadeout);
209 target.inputEl().un('keyup', fadeout);
211 target.el.setStyle('z-index', oIndex);
213 target.el.removeClass('roo-invalid-outline');
215 if(!intervalFadeOut){
219 window.clearInterval(intervalFadeOut);
220 intervalFadeOut = false;
224 // target.inputEl().on('blur', fadeout, target);
225 // target.inputEl().on('keyup', fadeout, target);
227 // if(intervalFadeOut){
228 // window.clearInterval(intervalFadeOut);
229 // intervalFadeOut = false;
232 // var intervalFadeOut = window.setInterval(function() {
240 * Returns true if any fields in this form have changed since their original load.
243 isDirty : function(){
245 var items = this.getItems();
246 items.each(function(f){
256 * Performs a predefined action (submit or load) or custom actions you define on this form.
257 * @param {String} actionName The name of the action type
258 * @param {Object} options (optional) The options to pass to the action. All of the config options listed
259 * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
260 * accept other config options):
262 Property Type Description
263 ---------------- --------------- ----------------------------------------------------------------------------------
264 url String The url for the action (defaults to the form's url)
265 method String The form method to use (defaults to the form's method, or POST if not defined)
266 params String/Object The params to pass (defaults to the form's baseParams, or none if not defined)
267 clientValidation Boolean Applies to submit only. Pass true to call form.isValid() prior to posting to
268 validate the form on the client (defaults to false)
270 * @return {BasicForm} this
272 doAction : function(action, options){
273 if(typeof action == 'string'){
274 action = new Roo.form.Action.ACTION_TYPES[action](this, options);
276 if(this.fireEvent('beforeaction', this, action) !== false){
277 this.beforeAction(action);
278 action.run.defer(100, action);
284 beforeAction : function(action){
285 var o = action.options;
288 this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
290 // not really supported yet.. ??
292 //if(this.waitMsgTarget === true){
293 // this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
294 //}else if(this.waitMsgTarget){
295 // this.waitMsgTarget = Roo.get(this.waitMsgTarget);
296 // this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
298 // Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
304 afterAction : function(action, success){
305 this.activeAction = null;
306 var o = action.options;
308 //if(this.waitMsgTarget === true){
310 //}else if(this.waitMsgTarget){
311 // this.waitMsgTarget.unmask();
313 // Roo.MessageBox.updateProgress(1);
314 // Roo.MessageBox.hide();
321 Roo.callback(o.success, o.scope, [this, action]);
322 this.fireEvent('actioncomplete', this, action);
326 // failure condition..
327 // we have a scenario where updates need confirming.
328 // eg. if a locking scenario exists..
329 // we look for { errors : { needs_confirm : true }} in the response.
331 (typeof(action.result) != 'undefined') &&
332 (typeof(action.result.errors) != 'undefined') &&
333 (typeof(action.result.errors.needs_confirm) != 'undefined')
336 Roo.log("not supported yet");
339 Roo.MessageBox.confirm(
340 "Change requires confirmation",
341 action.result.errorMsg,
346 _t.doAction('submit', { params : { _submit_confirmed : 1 } } );
356 Roo.callback(o.failure, o.scope, [this, action]);
357 // show an error message if no failed handler is set..
358 if (!this.hasListener('actionfailed')) {
359 Roo.log("need to add dialog support");
361 Roo.MessageBox.alert("Error",
362 (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
363 action.result.errorMsg :
364 "Saving Failed, please check your entries or try again"
369 this.fireEvent('actionfailed', this, action);
374 * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
375 * @param {String} id The value to search for
378 findField : function(id){
379 var items = this.getItems();
380 var field = items.get(id);
382 items.each(function(f){
383 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
390 return field || null;
393 * Mark fields in this form invalid in bulk.
394 * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
395 * @return {BasicForm} this
397 markInvalid : function(errors){
398 if(errors instanceof Array){
399 for(var i = 0, len = errors.length; i < len; i++){
400 var fieldError = errors[i];
401 var f = this.findField(fieldError.id);
403 f.markInvalid(fieldError.msg);
409 if(typeof errors[id] != 'function' && (field = this.findField(id))){
410 field.markInvalid(errors[id]);
414 //Roo.each(this.childForms || [], function (f) {
415 // f.markInvalid(errors);
422 * Set values for fields in this form in bulk.
423 * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
424 * @return {BasicForm} this
426 setValues : function(values){
427 if(values instanceof Array){ // array of objects
428 for(var i = 0, len = values.length; i < len; i++){
430 var f = this.findField(v.id);
433 if(this.trackResetOnLoad){
434 f.originalValue = f.getValue();
438 }else{ // object hash
441 if(typeof values[id] != 'function' && (field = this.findField(id))){
443 if (field.setFromData &&
445 field.displayField &&
446 // combos' with local stores can
447 // be queried via setValue()
448 // to set their value..
449 (field.store && !field.store.isLocal)
453 sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
454 sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
455 field.setFromData(sd);
458 field.setValue(values[id]);
462 if(this.trackResetOnLoad){
463 field.originalValue = field.getValue();
469 //Roo.each(this.childForms || [], function (f) {
470 // f.setValues(values);
477 * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
478 * they are returned as an array.
479 * @param {Boolean} asString
482 getValues : function(asString){
483 //if (this.childForms) {
484 // copy values from the child forms
485 // Roo.each(this.childForms, function (f) {
486 // this.setValues(f.getValues());
492 var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
493 if(asString === true){
496 return Roo.urlDecode(fs);
500 * Returns the fields in this form as an object with key/value pairs.
501 * This differs from getValues as it calls getValue on each child item, rather than using dom data.
504 getFieldValues : function(with_hidden)
506 var items = this.getItems();
508 items.each(function(f){
512 var v = f.getValue();
513 if (f.inputType =='radio') {
514 if (typeof(ret[f.getName()]) == 'undefined') {
515 ret[f.getName()] = ''; // empty..
518 if (!f.el.dom.checked) {
526 // not sure if this supported any more..
527 if ((typeof(v) == 'object') && f.getRawValue) {
528 v = f.getRawValue() ; // dates..
530 // combo boxes where name != hiddenName...
531 if (f.name != f.getName()) {
532 ret[f.name] = f.getRawValue();
534 ret[f.getName()] = v;
541 * Clears all invalid messages in this form.
542 * @return {BasicForm} this
544 clearInvalid : function(){
545 var items = this.getItems();
547 items.each(function(f){
558 * @return {BasicForm} this
561 var items = this.getItems();
562 items.each(function(f){
566 Roo.each(this.childForms || [], function (f) {
573 getItems : function()
575 var r=new Roo.util.MixedCollection(false, function(o){
576 return o.id || (o.id = Roo.id());
578 var iter = function(el) {
585 Roo.each(el.items,function(e) {