--- /dev/null
+/*
+ * - LGPL
+ *
+ * form
+ *
+ */
+
+/**
+ * @class Roo.bootstrap.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 = function(config){
+
+ Roo.bootstrap.Form.superclass.constructor.call(this, config);
+
+ Roo.bootstrap.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, 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.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, {
+
+ 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.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;
+
+ }
+
+ }
+
+});
+