/* * - LGPL * * form * */ /** * @class Roo.bootstrap.form.Form * @extends Roo.bootstrap.Component * @children Roo.bootstrap.Component * Bootstrap Form class * @cfg {String} method GET | POST (default POST) * @cfg {String} labelAlign top | left (default top) * @cfg {String} align left | right - for navbars * @cfg {Boolean} loadMask load mask when submit (default true) * * @constructor * Create a new Form * @param {Object} config The config object */ Roo.bootstrap.form.Form = function(config){ Roo.bootstrap.form.Form.superclass.constructor.call(this, config); Roo.bootstrap.form.Form.popover.apply(); this.addEvents({ /** * @event clientvalidation * If the monitorValid config option is true, this event fires repetitively to notify of valid state * @param {Form} this * @param {Boolean} valid true if the form has passed client-side validation */ clientvalidation: true, /** * @event beforeaction * Fires before any action is performed. Return false to cancel the action. * @param {Form} this * @param {Action} action The action to be performed */ beforeaction: true, /** * @event actionfailed * Fires when an action fails. * @param {Form} this * @param {Action} action The action that failed */ actionfailed : true, /** * @event actioncomplete * Fires when an action is completed. * @param {Form} this * @param {Action} action The action that completed */ actioncomplete : true }); }; Roo.extend(Roo.bootstrap.form.Form, Roo.bootstrap.Component, { /** * @cfg {String} method * The request method to use (GET or POST) for form actions if one isn't supplied in the action options. */ method : 'POST', /** * @cfg {String} url * The URL to use for form actions if one isn't supplied in the action options. */ /** * @cfg {Boolean} fileUpload * Set to true if this form is a file upload. */ /** * @cfg {Object} baseParams * Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}. */ /** * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds). */ timeout: 30, /** * @cfg {Sting} align (left|right) for navbar forms */ align : 'left', // private activeAction : null, /** * By default wait messages are displayed with Roo.MessageBox.wait. You can target a specific * element by passing it or its id or mask the form itself by passing in true. * @type Mixed */ waitMsgTarget : false, loadMask : true, /** * @cfg {Boolean} errorMask (true|false) default false */ errorMask : false, /** * @cfg {Number} maskOffset Default 100 */ maskOffset : 100, /** * @cfg {Boolean} maskBody */ maskBody : false, getAutoCreate : function(){ var cfg = { tag: 'form', method : this.method || 'POST', id : this.id || Roo.id(), cls : '' }; if (this.parent().xtype.match(/^Nav/)) { cfg.cls = 'navbar-form form-inline navbar-' + this.align; } if (this.labelAlign == 'left' ) { cfg.cls += ' form-horizontal'; } return cfg; }, initEvents : function() { this.el.on('submit', this.onSubmit, this); // this was added as random key presses on the form where triggering form submit. this.el.on('keypress', function(e) { if (e.getCharCode() != 13) { return true; } // we might need to allow it for textareas.. and some other items. // check e.getTarget(). if(e.getTarget().nodeName.toLowerCase() === 'textarea'){ return true; } Roo.log("keypress blocked"); e.preventDefault(); return false; }); }, // private onSubmit : function(e){ e.stopEvent(); }, /** * Returns true if client-side validation on the form is successful. * @return Boolean */ isValid : function(){ var items = this.getItems(); var valid = true; var target = false; items.each(function(f){ if(f.validate()){ return; } Roo.log('invalid field: ' + f.name); valid = false; if(!target && f.el.isVisible(true)){ target = f; } }); if(this.errorMask && !valid){ Roo.bootstrap.form.Form.popover.mask(this, target); } return valid; }, /** * Returns true if any fields in this form have changed since their original load. * @return Boolean */ isDirty : function(){ var dirty = false; var items = this.getItems(); items.each(function(f){ if(f.isDirty()){ dirty = true; return false; } return true; }); return dirty; }, /** * Performs a predefined action (submit or load) or custom actions you define on this form. * @param {String} actionName The name of the action type * @param {Object} options (optional) The options to pass to the action. All of the config options listed * below are supported by both the submit and load actions unless otherwise noted (custom actions could also * accept other config options): * <pre> Property Type Description ---------------- --------------- ---------------------------------------------------------------------------------- url String The url for the action (defaults to the form's url) method String The form method to use (defaults to the form's method, or POST if not defined) params String/Object The params to pass (defaults to the form's baseParams, or none if not defined) clientValidation Boolean Applies to submit only. Pass true to call form.isValid() prior to posting to validate the form on the client (defaults to false) * </pre> * @return {BasicForm} this */ doAction : function(action, options){ if(typeof action == 'string'){ action = new Roo.form.Action.ACTION_TYPES[action](this, options); } if(this.fireEvent('beforeaction', this, action) !== false){ this.beforeAction(action); action.run.defer(100, action); } return this; }, // private beforeAction : function(action){ var o = action.options; if(this.loadMask){ if(this.maskBody){ Roo.get(document.body).mask(o.waitMsg || "Sending", 'x-mask-loading') } else { this.el.mask(o.waitMsg || "Sending", 'x-mask-loading'); } } // not really supported yet.. ?? //if(this.waitMsgTarget === true){ // this.el.mask(o.waitMsg || "Sending", 'x-mask-loading'); //}else if(this.waitMsgTarget){ // this.waitMsgTarget = Roo.get(this.waitMsgTarget); // this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading'); //}else { // Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...'); // } }, // private afterAction : function(action, success){ this.activeAction = null; var o = action.options; if(this.loadMask){ if(this.maskBody){ Roo.get(document.body).unmask(); } else { this.el.unmask(); } } //if(this.waitMsgTarget === true){ // this.el.unmask(); //}else if(this.waitMsgTarget){ // this.waitMsgTarget.unmask(); //}else{ // Roo.MessageBox.updateProgress(1); // Roo.MessageBox.hide(); // } // if(success){ if(o.reset){ this.reset(); } Roo.callback(o.success, o.scope, [this, action]); this.fireEvent('actioncomplete', this, action); }else{ // failure condition.. // we have a scenario where updates need confirming. // eg. if a locking scenario exists.. // we look for { errors : { needs_confirm : true }} in the response. if ( (typeof(action.result) != 'undefined') && (typeof(action.result.errors) != 'undefined') && (typeof(action.result.errors.needs_confirm) != 'undefined') ){ var _t = this; Roo.log("not supported yet"); /* Roo.MessageBox.confirm( "Change requires confirmation", action.result.errorMsg, function(r) { if (r != 'yes') { return; } _t.doAction('submit', { params : { _submit_confirmed : 1 } } ); } ); */ return; } Roo.callback(o.failure, o.scope, [this, action]); // show an error message if no failed handler is set.. if (!this.hasListener('actionfailed')) { Roo.log("need to add dialog support"); /* Roo.MessageBox.alert("Error", (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ? action.result.errorMsg : "Saving Failed, please check your entries or try again" ); */ } this.fireEvent('actionfailed', this, action); } }, /** * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName * @param {String} id The value to search for * @return Field */ findField : function(id){ var items = this.getItems(); var field = items.get(id); if(!field){ items.each(function(f){ if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){ field = f; return false; } return true; }); } return field || null; }, /** * Mark fields in this form invalid in bulk. * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2} * @return {BasicForm} this */ markInvalid : function(errors){ if(errors instanceof Array){ for(var i = 0, len = errors.length; i < len; i++){ var fieldError = errors[i]; var f = this.findField(fieldError.id); if(f){ f.markInvalid(fieldError.msg); } } }else{ var field, id; for(id in errors){ if(typeof errors[id] != 'function' && (field = this.findField(id))){ field.markInvalid(errors[id]); } } } //Roo.each(this.childForms || [], function (f) { // f.markInvalid(errors); //}); return this; }, /** * Set values for fields in this form in bulk. * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2} * @return {BasicForm} this */ setValues : function(values){ if(values instanceof Array){ // array of objects for(var i = 0, len = values.length; i < len; i++){ var v = values[i]; var f = this.findField(v.id); if(f){ f.setValue(v.value); if(this.trackResetOnLoad){ f.originalValue = f.getValue(); } } } }else{ // object hash var field, id; for(id in values){ if(typeof values[id] != 'function' && (field = this.findField(id))){ if (field.setFromData && field.valueField && field.displayField && // combos' with local stores can // be queried via setValue() // to set their value.. (field.store && !field.store.isLocal) ) { // it's a combo var sd = { }; sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName]; sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name]; field.setFromData(sd); } else if(field.setFromData && (field.store && !field.store.isLocal)) { field.setFromData(values); } else { field.setValue(values[id]); } if(this.trackResetOnLoad){ field.originalValue = field.getValue(); } } } } //Roo.each(this.childForms || [], function (f) { // f.setValues(values); //}); return this; }, /** * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name * they are returned as an array. * @param {Boolean} asString * @return {Object} */ getValues : function(asString){ //if (this.childForms) { // copy values from the child forms // Roo.each(this.childForms, function (f) { // this.setValues(f.getValues()); // }, this); //} var fs = Roo.lib.Ajax.serializeForm(this.el.dom); if(asString === true){ return fs; } return Roo.urlDecode(fs); }, /** * Returns the fields in this form as an object with key/value pairs. * This differs from getValues as it calls getValue on each child item, rather than using dom data. * @return {Object} */ getFieldValues : function(with_hidden) { var items = this.getItems(); var ret = {}; items.each(function(f){ if (!f.getName()) { return; } var v = f.getValue(); if (f.inputType =='radio') { if (typeof(ret[f.getName()]) == 'undefined') { ret[f.getName()] = ''; // empty.. } if (!f.el.dom.checked) { return; } v = f.el.dom.value; } if(f.xtype == 'MoneyField'){ ret[f.currencyName] = f.getCurrency(); } // not sure if this supported any more.. if ((typeof(v) == 'object') && f.getRawValue) { v = f.getRawValue() ; // dates.. } // combo boxes where name != hiddenName... if (f.name !== false && f.name != '' && f.name != f.getName()) { ret[f.name] = f.getRawValue(); } ret[f.getName()] = v; }); return ret; }, /** * Clears all invalid messages in this form. * @return {BasicForm} this */ clearInvalid : function(){ var items = this.getItems(); items.each(function(f){ f.clearInvalid(); }); return this; }, /** * Resets this form. * @return {BasicForm} this */ reset : function(){ var items = this.getItems(); items.each(function(f){ f.reset(); }); Roo.each(this.childForms || [], function (f) { f.reset(); }); return this; }, getItems : function() { var r=new Roo.util.MixedCollection(false, function(o){ return o.id || (o.id = Roo.id()); }); var iter = function(el) { if (el.inputEl) { r.add(el); } if (!el.items) { return; } Roo.each(el.items,function(e) { iter(e); }); }; iter(this); return r; }, hideFields : function(items) { Roo.each(items, function(i){ var f = this.findField(i); if(!f){ return; } f.hide(); }, this); }, showFields : function(items) { Roo.each(items, function(i){ var f = this.findField(i); if(!f){ return; } f.show(); }, this); } }); Roo.apply(Roo.bootstrap.form.Form, { popover : { padding : 5, isApplied : false, isMasked : false, form : false, target : false, toolTip : false, intervalID : false, maskEl : false, apply : function() { if(this.isApplied){ return; } this.maskEl = { top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true), left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true), bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true), right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true) }; this.maskEl.top.enableDisplayMode("block"); this.maskEl.left.enableDisplayMode("block"); this.maskEl.bottom.enableDisplayMode("block"); this.maskEl.right.enableDisplayMode("block"); this.toolTip = new Roo.bootstrap.Tooltip({ cls : 'roo-form-error-popover', alignment : { 'left' : ['r-l', [-2,0], 'right'], 'right' : ['l-r', [2,0], 'left'], 'bottom' : ['tl-bl', [0,2], 'top'], 'top' : [ 'bl-tl', [0,-2], 'bottom'] } }); this.toolTip.render(Roo.get(document.body)); this.toolTip.el.enableDisplayMode("block"); Roo.get(document.body).on('click', function(){ this.unmask(); }, this); Roo.get(document.body).on('touchstart', function(){ this.unmask(); }, this); this.isApplied = true }, mask : function(form, target) { this.form = form; this.target = target; if(!this.form.errorMask || !target.el){ return; } var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.modal', 100, true) || Roo.get(document.body); Roo.log(scrollable); var ot = this.target.el.calcOffsetsTo(scrollable); var scrollTo = ot[1] - this.form.maskOffset; scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight); scrollable.scrollTo('top', scrollTo); var box = this.target.el.getBox(); Roo.log(box); var zIndex = Roo.bootstrap.Modal.zIndex++; this.maskEl.top.setStyle('position', 'absolute'); this.maskEl.top.setStyle('z-index', zIndex); this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding); this.maskEl.top.setLeft(0); this.maskEl.top.setTop(0); this.maskEl.top.show(); this.maskEl.left.setStyle('position', 'absolute'); this.maskEl.left.setStyle('z-index', zIndex); this.maskEl.left.setSize(box.x - this.padding, box.height + this.padding * 2); this.maskEl.left.setLeft(0); this.maskEl.left.setTop(box.y - this.padding); this.maskEl.left.show(); this.maskEl.bottom.setStyle('position', 'absolute'); this.maskEl.bottom.setStyle('z-index', zIndex); this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding); this.maskEl.bottom.setLeft(0); this.maskEl.bottom.setTop(box.bottom + this.padding); this.maskEl.bottom.show(); this.maskEl.right.setStyle('position', 'absolute'); this.maskEl.right.setStyle('z-index', zIndex); this.maskEl.right.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2); this.maskEl.right.setLeft(box.right + this.padding); this.maskEl.right.setTop(box.y - this.padding); this.maskEl.right.show(); this.toolTip.bindEl = this.target.el; this.toolTip.el.setStyle('z-index', Roo.bootstrap.Modal.zIndex++); var tip = this.target.blankText; if(this.target.getValue() !== '' ) { if (this.target.invalidText.length) { tip = this.target.invalidText; } else if (this.target.regexText.length){ tip = this.target.regexText; } } this.toolTip.show(tip); this.intervalID = window.setInterval(function() { Roo.bootstrap.form.Form.popover.unmask(); }, 10000); window.onwheel = function(){ return false;}; (function(){ this.isMasked = true; }).defer(500, this); }, unmask : function() { if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){ return; } this.maskEl.top.setStyle('position', 'absolute'); this.maskEl.top.setSize(0, 0).setXY([0, 0]); this.maskEl.top.hide(); this.maskEl.left.setStyle('position', 'absolute'); this.maskEl.left.setSize(0, 0).setXY([0, 0]); this.maskEl.left.hide(); this.maskEl.bottom.setStyle('position', 'absolute'); this.maskEl.bottom.setSize(0, 0).setXY([0, 0]); this.maskEl.bottom.hide(); this.maskEl.right.setStyle('position', 'absolute'); this.maskEl.right.setSize(0, 0).setXY([0, 0]); this.maskEl.right.hide(); this.toolTip.hide(); this.toolTip.el.hide(); window.onwheel = function(){ return true;}; if(this.intervalID){ window.clearInterval(this.intervalID); this.intervalID = false; } this.isMasked = false; } } });