MOVED Roo/bootstrap/DateField.js to Roo/bootstrap/form/DateField.js
[roojs1] / Roo / bootstrap / form / Form.js
diff --git a/Roo/bootstrap/form/Form.js b/Roo/bootstrap/form/Form.js
new file mode 100644 (file)
index 0000000..c679941
--- /dev/null
@@ -0,0 +1,795 @@
+/*
+ * - 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;
+            
+        }
+        
+    }
+    
+});
+