MOVED Roo/bootstrap/DateField.js to Roo/bootstrap/form/DateField.js
authorAlan Knowles <alan@roojs.com>
Fri, 30 Jul 2021 07:09:25 +0000 (15:09 +0800)
committerAlan Knowles <alan@roojs.com>
Fri, 30 Jul 2021 07:09:25 +0000 (15:09 +0800)
MOVED Roo/bootstrap/DateSplitField.js to Roo/bootstrap/form/DateSplitField.js
Roo/bootstrap/DateField.js
Roo/bootstrap/DateSplitField.js
MOVED Roo/bootstrap/form/Form.js to Roo/bootstrap/Form.js
Roo/bootstrap/form/Form.js
MOVED Roo/bootstrap/Input.js to Roo/bootstrap/form/Input.js
Roo/bootstrap/Input.js

Roo/bootstrap/form/DateField.js [new file with mode: 0644]
Roo/bootstrap/form/DateSplitField.js [new file with mode: 0644]
Roo/bootstrap/form/Form.js [new file with mode: 0644]
Roo/bootstrap/form/Input.js [new file with mode: 0644]

diff --git a/Roo/bootstrap/form/DateField.js b/Roo/bootstrap/form/DateField.js
new file mode 100644 (file)
index 0000000..ba18ac3
--- /dev/null
@@ -0,0 +1,1144 @@
+/*
+ * - LGPL
+ *
+ * DateField
+ * 
+ */
+
+/**
+ * @class Roo.bootstrap.DateField
+ * @extends Roo.bootstrap.Input
+ * Bootstrap DateField class
+ * @cfg {Number} weekStart default 0
+ * @cfg {String} viewMode default empty, (months|years)
+ * @cfg {String} minViewMode default empty, (months|years)
+ * @cfg {Number} startDate default -Infinity
+ * @cfg {Number} endDate default Infinity
+ * @cfg {Boolean} todayHighlight default false
+ * @cfg {Boolean} todayBtn default false
+ * @cfg {Boolean} calendarWeeks default false
+ * @cfg {Object} daysOfWeekDisabled default empty
+ * @cfg {Boolean} singleMode default false (true | false)
+ * 
+ * @cfg {Boolean} keyboardNavigation default true
+ * @cfg {String} language default en
+ * 
+ * @constructor
+ * Create a new DateField
+ * @param {Object} config The config object
+ */
+
+Roo.bootstrap.DateField = function(config){
+    Roo.bootstrap.DateField.superclass.constructor.call(this, config);
+     this.addEvents({
+            /**
+             * @event show
+             * Fires when this field show.
+             * @param {Roo.bootstrap.DateField} this
+             * @param {Mixed} date The date value
+             */
+            show : true,
+            /**
+             * @event show
+             * Fires when this field hide.
+             * @param {Roo.bootstrap.DateField} this
+             * @param {Mixed} date The date value
+             */
+            hide : true,
+            /**
+             * @event select
+             * Fires when select a date.
+             * @param {Roo.bootstrap.DateField} this
+             * @param {Mixed} date The date value
+             */
+            select : true,
+            /**
+             * @event beforeselect
+             * Fires when before select a date.
+             * @param {Roo.bootstrap.DateField} this
+             * @param {Mixed} date The date value
+             */
+            beforeselect : true
+        });
+};
+
+Roo.extend(Roo.bootstrap.DateField, Roo.bootstrap.Input,  {
+    
+    /**
+     * @cfg {String} format
+     * The default date format string which can be overriden for localization support.  The format must be
+     * valid according to {@link Date#parseDate} (defaults to 'm/d/y').
+     */
+    format : "m/d/y",
+    /**
+     * @cfg {String} altFormats
+     * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
+     * format (defaults to 'm/d/Y|m-d-y|m-d-Y|m/d|m-d|d').
+     */
+    altFormats : "m/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d",
+    
+    weekStart : 0,
+    
+    viewMode : '',
+    
+    minViewMode : '',
+    
+    todayHighlight : false,
+    
+    todayBtn: false,
+    
+    language: 'en',
+    
+    keyboardNavigation: true,
+    
+    calendarWeeks: false,
+    
+    startDate: -Infinity,
+    
+    endDate: Infinity,
+    
+    daysOfWeekDisabled: [],
+    
+    _events: [],
+    
+    singleMode : false,
+    
+    UTCDate: function()
+    {
+        return new Date(Date.UTC.apply(Date, arguments));
+    },
+    
+    UTCToday: function()
+    {
+        var today = new Date();
+        return this.UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
+    },
+    
+    getDate: function() {
+            var d = this.getUTCDate();
+            return new Date(d.getTime() + (d.getTimezoneOffset()*60000));
+    },
+    
+    getUTCDate: function() {
+            return this.date;
+    },
+    
+    setDate: function(d) {
+            this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset()*60000)));
+    },
+    
+    setUTCDate: function(d) {
+            this.date = d;
+            this.setValue(this.formatDate(this.date));
+    },
+        
+    onRender: function(ct, position)
+    {
+        
+        Roo.bootstrap.DateField.superclass.onRender.call(this, ct, position);
+        
+        this.language = this.language || 'en';
+        this.language = this.language in Roo.bootstrap.DateField.dates ? this.language : this.language.split('-')[0];
+        this.language = this.language in Roo.bootstrap.DateField.dates ? this.language : "en";
+        
+        this.isRTL = Roo.bootstrap.DateField.dates[this.language].rtl || false;
+        this.format = this.format || 'm/d/y';
+        this.isInline = false;
+        this.isInput = true;
+        this.component = this.el.select('.add-on', true).first() || false;
+        this.component = (this.component && this.component.length === 0) ? false : this.component;
+        this.hasInput = this.component && this.inputEl().length;
+        
+        if (typeof(this.minViewMode === 'string')) {
+            switch (this.minViewMode) {
+                case 'months':
+                    this.minViewMode = 1;
+                    break;
+                case 'years':
+                    this.minViewMode = 2;
+                    break;
+                default:
+                    this.minViewMode = 0;
+                    break;
+            }
+        }
+        
+        if (typeof(this.viewMode === 'string')) {
+            switch (this.viewMode) {
+                case 'months':
+                    this.viewMode = 1;
+                    break;
+                case 'years':
+                    this.viewMode = 2;
+                    break;
+                default:
+                    this.viewMode = 0;
+                    break;
+            }
+        }
+                
+        this.pickerEl = Roo.get(document.body).createChild(Roo.bootstrap.DateField.template);
+        
+//        this.el.select('>.input-group', true).first().createChild(Roo.bootstrap.DateField.template);
+        
+        this.picker().setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
+        
+        this.picker().on('mousedown', this.onMousedown, this);
+        this.picker().on('click', this.onClick, this);
+        
+        this.picker().addClass('datepicker-dropdown');
+        
+        this.startViewMode = this.viewMode;
+        
+        if(this.singleMode){
+            Roo.each(this.picker().select('thead > tr > th', true).elements, function(v){
+                v.setVisibilityMode(Roo.Element.DISPLAY);
+                v.hide();
+            });
+            
+            Roo.each(this.picker().select('tbody > tr > td', true).elements, function(v){
+                v.setStyle('width', '189px');
+            });
+        }
+        
+        Roo.each(this.picker().select('tfoot th.today', true).elements, function(v){
+            if(!this.calendarWeeks){
+                v.remove();
+                return;
+            }
+            
+            v.dom.innerHTML = Roo.bootstrap.DateField.dates[this.language].today;
+            v.attr('colspan', function(i, val){
+                return parseInt(val) + 1;
+            });
+        });
+                       
+        
+        this.weekEnd = this.weekStart === 0 ? 6 : this.weekStart - 1;
+        
+        this.setStartDate(this.startDate);
+        this.setEndDate(this.endDate);
+        
+        this.setDaysOfWeekDisabled(this.daysOfWeekDisabled);
+        
+        this.fillDow();
+        this.fillMonths();
+        this.update();
+        this.showMode();
+        
+        if(this.isInline) {
+            this.showPopup();
+        }
+    },
+    
+    picker : function()
+    {
+        return this.pickerEl;
+//        return this.el.select('.datepicker', true).first();
+    },
+    
+    fillDow: function()
+    {
+        var dowCnt = this.weekStart;
+        
+        var dow = {
+            tag: 'tr',
+            cn: [
+                
+            ]
+        };
+        
+        if(this.calendarWeeks){
+            dow.cn.push({
+                tag: 'th',
+                cls: 'cw',
+                html: '&nbsp;'
+            })
+        }
+        
+        while (dowCnt < this.weekStart + 7) {
+            dow.cn.push({
+                tag: 'th',
+                cls: 'dow',
+                html: Roo.bootstrap.DateField.dates[this.language].daysMin[(dowCnt++)%7]
+            });
+        }
+        
+        this.picker().select('>.datepicker-days thead', true).first().createChild(dow);
+    },
+    
+    fillMonths: function()
+    {    
+        var i = 0;
+        var months = this.picker().select('>.datepicker-months td', true).first();
+        
+        months.dom.innerHTML = '';
+        
+        while (i < 12) {
+            var month = {
+                tag: 'span',
+                cls: 'month',
+                html: Roo.bootstrap.DateField.dates[this.language].monthsShort[i++]
+            };
+            
+            months.createChild(month);
+        }
+        
+    },
+    
+    update: function()
+    {
+        this.date = (typeof(this.date) === 'undefined' || ((typeof(this.date) === 'string') && !this.date.length)) ? this.UTCToday() : (typeof(this.date) === 'string') ? this.parseDate(this.date) : this.date;
+        
+        if (this.date < this.startDate) {
+            this.viewDate = new Date(this.startDate);
+        } else if (this.date > this.endDate) {
+            this.viewDate = new Date(this.endDate);
+        } else {
+            this.viewDate = new Date(this.date);
+        }
+        
+        this.fill();
+    },
+    
+    fill: function() 
+    {
+        var d = new Date(this.viewDate),
+                year = d.getUTCFullYear(),
+                month = d.getUTCMonth(),
+                startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity,
+                startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity,
+                endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity,
+                endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity,
+                currentDate = this.date && this.date.valueOf(),
+                today = this.UTCToday();
+        
+        this.picker().select('>.datepicker-days thead th.switch', true).first().dom.innerHTML = Roo.bootstrap.DateField.dates[this.language].months[month]+' '+year;
+        
+//        this.picker().select('>tfoot th.today', true).first().dom.innerHTML = Roo.bootstrap.DateField.dates[this.language].today;
+        
+//        this.picker.select('>tfoot th.today').
+//                                             .text(dates[this.language].today)
+//                                             .toggle(this.todayBtn !== false);
+    
+        this.updateNavArrows();
+        this.fillMonths();
+                                                
+        var prevMonth = this.UTCDate(year, month-1, 28,0,0,0,0),
+        
+        day = prevMonth.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
+         
+        prevMonth.setUTCDate(day);
+        
+        prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7)%7);
+        
+        var nextMonth = new Date(prevMonth);
+        
+        nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
+        
+        nextMonth = nextMonth.valueOf();
+        
+        var fillMonths = false;
+        
+        this.picker().select('>.datepicker-days tbody',true).first().dom.innerHTML = '';
+        
+        while(prevMonth.valueOf() <= nextMonth) {
+            var clsName = '';
+            
+            if (prevMonth.getUTCDay() === this.weekStart) {
+                if(fillMonths){
+                    this.picker().select('>.datepicker-days tbody',true).first().createChild(fillMonths);
+                }
+                    
+                fillMonths = {
+                    tag: 'tr',
+                    cn: []
+                };
+                
+                if(this.calendarWeeks){
+                    // ISO 8601: First week contains first thursday.
+                    // ISO also states week starts on Monday, but we can be more abstract here.
+                    var
+                    // Start of current week: based on weekstart/current date
+                    ws = new Date(+prevMonth + (this.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5),
+                    // Thursday of this week
+                    th = new Date(+ws + (7 + 4 - ws.getUTCDay()) % 7 * 864e5),
+                    // First Thursday of year, year from thursday
+                    yth = new Date(+(yth = this.UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5),
+                    // Calendar week: ms between thursdays, div ms per day, div 7 days
+                    calWeek =  (th - yth) / 864e5 / 7 + 1;
+                    
+                    fillMonths.cn.push({
+                        tag: 'td',
+                        cls: 'cw',
+                        html: calWeek
+                    });
+                }
+            }
+            
+            if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) {
+                clsName += ' old';
+            } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) {
+                clsName += ' new';
+            }
+            if (this.todayHighlight &&
+                prevMonth.getUTCFullYear() == today.getFullYear() &&
+                prevMonth.getUTCMonth() == today.getMonth() &&
+                prevMonth.getUTCDate() == today.getDate()) {
+                clsName += ' today';
+            }
+            
+            if (currentDate && prevMonth.valueOf() === currentDate) {
+                clsName += ' active';
+            }
+            
+            if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate ||
+                    this.daysOfWeekDisabled.indexOf(prevMonth.getUTCDay()) !== -1) {
+                    clsName += ' disabled';
+            }
+            
+            fillMonths.cn.push({
+                tag: 'td',
+                cls: 'day ' + clsName,
+                html: prevMonth.getDate()
+            });
+            
+            prevMonth.setDate(prevMonth.getDate()+1);
+        }
+          
+        var currentYear = this.date && this.date.getUTCFullYear();
+        var currentMonth = this.date && this.date.getUTCMonth();
+        
+        this.picker().select('>.datepicker-months th.switch',true).first().dom.innerHTML = year;
+        
+        Roo.each(this.picker().select('>.datepicker-months tbody span',true).elements, function(v,k){
+            v.removeClass('active');
+            
+            if(currentYear === year && k === currentMonth){
+                v.addClass('active');
+            }
+            
+            if (year < startYear || year > endYear || (year == startYear && k < startMonth) || (year == endYear && k > endMonth)) {
+                v.addClass('disabled');
+            }
+            
+        });
+        
+        
+        year = parseInt(year/10, 10) * 10;
+        
+        this.picker().select('>.datepicker-years th.switch', true).first().dom.innerHTML = year + '-' + (year + 9);
+        
+        this.picker().select('>.datepicker-years tbody td',true).first().dom.innerHTML = '';
+        
+        year -= 1;
+        for (var i = -1; i < 11; i++) {
+            this.picker().select('>.datepicker-years tbody td',true).first().createChild({
+                tag: 'span',
+                cls: 'year' + (i === -1 || i === 10 ? ' old' : '') + (currentYear === year ? ' active' : '') + (year < startYear || year > endYear ? ' disabled' : ''),
+                html: year
+            });
+            
+            year += 1;
+        }
+    },
+    
+    showMode: function(dir) 
+    {
+        if (dir) {
+            this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir));
+        }
+        
+        Roo.each(this.picker().select('>div',true).elements, function(v){
+            v.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
+            v.hide();
+        });
+        this.picker().select('>.datepicker-'+Roo.bootstrap.DateField.modes[this.viewMode].clsName, true).first().show();
+    },
+    
+    place: function()
+    {
+        if(this.isInline) {
+            return;
+        }
+        
+        this.picker().removeClass(['bottom', 'top']);
+        
+        if((Roo.lib.Dom.getViewHeight() + Roo.get(document.body).getScroll().top) - (this.inputEl().getBottom() + this.picker().getHeight()) < 0){
+            /*
+             * place to the top of element!
+             *
+             */
+            
+            this.picker().addClass('top');
+            this.picker().setTop(this.inputEl().getTop() - this.picker().getHeight()).setLeft(this.inputEl().getLeft());
+            
+            return;
+        }
+        
+        this.picker().addClass('bottom');
+        
+        this.picker().setTop(this.inputEl().getBottom()).setLeft(this.inputEl().getLeft());
+    },
+    
+    parseDate : function(value)
+    {
+        if(!value || value instanceof Date){
+            return value;
+        }
+        var v = Date.parseDate(value, this.format);
+        if (!v && (this.useIso || value.match(/^(\d{4})-0?(\d+)-0?(\d+)/))) {
+            v = Date.parseDate(value, 'Y-m-d');
+        }
+        if(!v && this.altFormats){
+            if(!this.altFormatsArray){
+                this.altFormatsArray = this.altFormats.split("|");
+            }
+            for(var i = 0, len = this.altFormatsArray.length; i < len && !v; i++){
+                v = Date.parseDate(value, this.altFormatsArray[i]);
+            }
+        }
+        return v;
+    },
+    
+    formatDate : function(date, fmt)
+    {   
+        return (!date || !(date instanceof Date)) ?
+        date : date.dateFormat(fmt || this.format);
+    },
+    
+    onFocus : function()
+    {
+        Roo.bootstrap.DateField.superclass.onFocus.call(this);
+        this.showPopup();
+    },
+    
+    onBlur : function()
+    {
+        Roo.bootstrap.DateField.superclass.onBlur.call(this);
+        
+        var d = this.inputEl().getValue();
+        
+        this.setValue(d);
+                
+        this.hidePopup();
+    },
+    
+    showPopup : function()
+    {
+        this.picker().show();
+        this.update();
+        this.place();
+        
+        this.fireEvent('showpopup', this, this.date);
+    },
+    
+    hidePopup : function()
+    {
+        if(this.isInline) {
+            return;
+        }
+        this.picker().hide();
+        this.viewMode = this.startViewMode;
+        this.showMode();
+        
+        this.fireEvent('hidepopup', this, this.date);
+        
+    },
+    
+    onMousedown: function(e)
+    {
+        e.stopPropagation();
+        e.preventDefault();
+    },
+    
+    keyup: function(e)
+    {
+        Roo.bootstrap.DateField.superclass.keyup.call(this);
+        this.update();
+    },
+
+    setValue: function(v)
+    {
+        if(this.fireEvent('beforeselect', this, v) !== false){
+            var d = new Date(this.parseDate(v) ).clearTime();
+        
+            if(isNaN(d.getTime())){
+                this.date = this.viewDate = '';
+                Roo.bootstrap.DateField.superclass.setValue.call(this, '');
+                return;
+            }
+
+            v = this.formatDate(d);
+
+            Roo.bootstrap.DateField.superclass.setValue.call(this, v);
+
+            this.date = new Date(d.getTime() - d.getTimezoneOffset()*60000);
+
+            this.update();
+
+            this.fireEvent('select', this, this.date);
+        }
+    },
+    
+    getValue: function()
+    {
+        return this.formatDate(this.date);
+    },
+    
+    fireKey: function(e)
+    {
+        if (!this.picker().isVisible()){
+            if (e.keyCode == 27) { // allow escape to hide and re-show picker
+                this.showPopup();
+            }
+            return;
+        }
+        
+        var dateChanged = false,
+        dir, day, month,
+        newDate, newViewDate;
+        
+        switch(e.keyCode){
+            case 27: // escape
+                this.hidePopup();
+                e.preventDefault();
+                break;
+            case 37: // left
+            case 39: // right
+                if (!this.keyboardNavigation) {
+                    break;
+                }
+                dir = e.keyCode == 37 ? -1 : 1;
+                
+                if (e.ctrlKey){
+                    newDate = this.moveYear(this.date, dir);
+                    newViewDate = this.moveYear(this.viewDate, dir);
+                } else if (e.shiftKey){
+                    newDate = this.moveMonth(this.date, dir);
+                    newViewDate = this.moveMonth(this.viewDate, dir);
+                } else {
+                    newDate = new Date(this.date);
+                    newDate.setUTCDate(this.date.getUTCDate() + dir);
+                    newViewDate = new Date(this.viewDate);
+                    newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir);
+                }
+                if (this.dateWithinRange(newDate)){
+                    this.date = newDate;
+                    this.viewDate = newViewDate;
+                    this.setValue(this.formatDate(this.date));
+//                    this.update();
+                    e.preventDefault();
+                    dateChanged = true;
+                }
+                break;
+            case 38: // up
+            case 40: // down
+                if (!this.keyboardNavigation) {
+                    break;
+                }
+                dir = e.keyCode == 38 ? -1 : 1;
+                if (e.ctrlKey){
+                    newDate = this.moveYear(this.date, dir);
+                    newViewDate = this.moveYear(this.viewDate, dir);
+                } else if (e.shiftKey){
+                    newDate = this.moveMonth(this.date, dir);
+                    newViewDate = this.moveMonth(this.viewDate, dir);
+                } else {
+                    newDate = new Date(this.date);
+                    newDate.setUTCDate(this.date.getUTCDate() + dir * 7);
+                    newViewDate = new Date(this.viewDate);
+                    newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7);
+                }
+                if (this.dateWithinRange(newDate)){
+                    this.date = newDate;
+                    this.viewDate = newViewDate;
+                    this.setValue(this.formatDate(this.date));
+//                    this.update();
+                    e.preventDefault();
+                    dateChanged = true;
+                }
+                break;
+            case 13: // enter
+                this.setValue(this.formatDate(this.date));
+                this.hidePopup();
+                e.preventDefault();
+                break;
+            case 9: // tab
+                this.setValue(this.formatDate(this.date));
+                this.hidePopup();
+                break;
+            case 16: // shift
+            case 17: // ctrl
+            case 18: // alt
+                break;
+            default :
+                this.hidePopup();
+                
+        }
+    },
+    
+    
+    onClick: function(e) 
+    {
+        e.stopPropagation();
+        e.preventDefault();
+        
+        var target = e.getTarget();
+        
+        if(target.nodeName.toLowerCase() === 'i'){
+            target = Roo.get(target).dom.parentNode;
+        }
+        
+        var nodeName = target.nodeName;
+        var className = target.className;
+        var html = target.innerHTML;
+        //Roo.log(nodeName);
+        
+        switch(nodeName.toLowerCase()) {
+            case 'th':
+                switch(className) {
+                    case 'switch':
+                        this.showMode(1);
+                        break;
+                    case 'prev':
+                    case 'next':
+                        var dir = Roo.bootstrap.DateField.modes[this.viewMode].navStep * (className == 'prev' ? -1 : 1);
+                        switch(this.viewMode){
+                                case 0:
+                                        this.viewDate = this.moveMonth(this.viewDate, dir);
+                                        break;
+                                case 1:
+                                case 2:
+                                        this.viewDate = this.moveYear(this.viewDate, dir);
+                                        break;
+                        }
+                        this.fill();
+                        break;
+                    case 'today':
+                        var date = new Date();
+                        this.date = this.UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
+//                        this.fill()
+                        this.setValue(this.formatDate(this.date));
+                        
+                        this.hidePopup();
+                        break;
+                }
+                break;
+            case 'span':
+                if (className.indexOf('disabled') < 0) {
+                if (!this.viewDate) {
+                    this.viewDate = new Date();
+                }
+                this.viewDate.setUTCDate(1);
+                    if (className.indexOf('month') > -1) {
+                        this.viewDate.setUTCMonth(Roo.bootstrap.DateField.dates[this.language].monthsShort.indexOf(html));
+                    } else {
+                        var year = parseInt(html, 10) || 0;
+                        this.viewDate.setUTCFullYear(year);
+                        
+                    }
+                    
+                    if(this.singleMode){
+                        this.setValue(this.formatDate(this.viewDate));
+                        this.hidePopup();
+                        return;
+                    }
+                    
+                    this.showMode(-1);
+                    this.fill();
+                }
+                break;
+                
+            case 'td':
+                //Roo.log(className);
+                if (className.indexOf('day') > -1 && className.indexOf('disabled') < 0 ){
+                    var day = parseInt(html, 10) || 1;
+                    var year =  (this.viewDate || new Date()).getUTCFullYear(),
+                        month = (this.viewDate || new Date()).getUTCMonth();
+
+                    if (className.indexOf('old') > -1) {
+                        if(month === 0 ){
+                            month = 11;
+                            year -= 1;
+                        }else{
+                            month -= 1;
+                        }
+                    } else if (className.indexOf('new') > -1) {
+                        if (month == 11) {
+                            month = 0;
+                            year += 1;
+                        } else {
+                            month += 1;
+                        }
+                    }
+                    //Roo.log([year,month,day]);
+                    this.date = this.UTCDate(year, month, day,0,0,0,0);
+                    this.viewDate = this.UTCDate(year, month, Math.min(28, day),0,0,0,0);
+//                    this.fill();
+                    //Roo.log(this.formatDate(this.date));
+                    this.setValue(this.formatDate(this.date));
+                    this.hidePopup();
+                }
+                break;
+        }
+    },
+    
+    setStartDate: function(startDate)
+    {
+        this.startDate = startDate || -Infinity;
+        if (this.startDate !== -Infinity) {
+            this.startDate = this.parseDate(this.startDate);
+        }
+        this.update();
+        this.updateNavArrows();
+    },
+
+    setEndDate: function(endDate)
+    {
+        this.endDate = endDate || Infinity;
+        if (this.endDate !== Infinity) {
+            this.endDate = this.parseDate(this.endDate);
+        }
+        this.update();
+        this.updateNavArrows();
+    },
+    
+    setDaysOfWeekDisabled: function(daysOfWeekDisabled)
+    {
+        this.daysOfWeekDisabled = daysOfWeekDisabled || [];
+        if (typeof(this.daysOfWeekDisabled) !== 'object') {
+            this.daysOfWeekDisabled = this.daysOfWeekDisabled.split(/,\s*/);
+        }
+        this.daysOfWeekDisabled = this.daysOfWeekDisabled.map(function (d) {
+            return parseInt(d, 10);
+        });
+        this.update();
+        this.updateNavArrows();
+    },
+    
+    updateNavArrows: function() 
+    {
+        if(this.singleMode){
+            return;
+        }
+        
+        var d = new Date(this.viewDate),
+        year = d.getUTCFullYear(),
+        month = d.getUTCMonth();
+        
+        Roo.each(this.picker().select('.prev', true).elements, function(v){
+            v.show();
+            switch (this.viewMode) {
+                case 0:
+
+                    if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) {
+                        v.hide();
+                    }
+                    break;
+                case 1:
+                case 2:
+                    if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) {
+                        v.hide();
+                    }
+                    break;
+            }
+        });
+        
+        Roo.each(this.picker().select('.next', true).elements, function(v){
+            v.show();
+            switch (this.viewMode) {
+                case 0:
+
+                    if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) {
+                        v.hide();
+                    }
+                    break;
+                case 1:
+                case 2:
+                    if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) {
+                        v.hide();
+                    }
+                    break;
+            }
+        })
+    },
+    
+    moveMonth: function(date, dir)
+    {
+        if (!dir) {
+            return date;
+        }
+        var new_date = new Date(date.valueOf()),
+        day = new_date.getUTCDate(),
+        month = new_date.getUTCMonth(),
+        mag = Math.abs(dir),
+        new_month, test;
+        dir = dir > 0 ? 1 : -1;
+        if (mag == 1){
+            test = dir == -1
+            // If going back one month, make sure month is not current month
+            // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
+            ? function(){
+                return new_date.getUTCMonth() == month;
+            }
+            // If going forward one month, make sure month is as expected
+            // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
+            : function(){
+                return new_date.getUTCMonth() != new_month;
+            };
+            new_month = month + dir;
+            new_date.setUTCMonth(new_month);
+            // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
+            if (new_month < 0 || new_month > 11) {
+                new_month = (new_month + 12) % 12;
+            }
+        } else {
+            // For magnitudes >1, move one month at a time...
+            for (var i=0; i<mag; i++) {
+                // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
+                new_date = this.moveMonth(new_date, dir);
+            }
+            // ...then reset the day, keeping it in the new month
+            new_month = new_date.getUTCMonth();
+            new_date.setUTCDate(day);
+            test = function(){
+                return new_month != new_date.getUTCMonth();
+            };
+        }
+        // Common date-resetting loop -- if date is beyond end of month, make it
+        // end of month
+        while (test()){
+            new_date.setUTCDate(--day);
+            new_date.setUTCMonth(new_month);
+        }
+        return new_date;
+    },
+
+    moveYear: function(date, dir)
+    {
+        return this.moveMonth(date, dir*12);
+    },
+
+    dateWithinRange: function(date)
+    {
+        return date >= this.startDate && date <= this.endDate;
+    },
+
+    
+    remove: function() 
+    {
+        this.picker().remove();
+    },
+    
+    validateValue : function(value)
+    {
+        if(this.getVisibilityEl().hasClass('hidden')){
+            return true;
+        }
+        
+        if(value.length < 1)  {
+            if(this.allowBlank){
+                return true;
+            }
+            return false;
+        }
+        
+        if(value.length < this.minLength){
+            return false;
+        }
+        if(value.length > this.maxLength){
+            return false;
+        }
+        if(this.vtype){
+            var vt = Roo.form.VTypes;
+            if(!vt[this.vtype](value, this)){
+                return false;
+            }
+        }
+        if(typeof this.validator == "function"){
+            var msg = this.validator(value);
+            if(msg !== true){
+                return false;
+            }
+        }
+        
+        if(this.regex && !this.regex.test(value)){
+            return false;
+        }
+        
+        if(typeof(this.parseDate(value)) == 'undefined'){
+            return false;
+        }
+        
+        if (this.endDate !== Infinity && this.parseDate(value).getTime() > this.endDate.getTime()) {
+            return false;
+        }      
+        
+        if (this.startDate !== -Infinity && this.parseDate(value).getTime() < this.startDate.getTime()) {
+            return false;
+        } 
+        
+        
+        return true;
+    },
+    
+    reset : function()
+    {
+        this.date = this.viewDate = '';
+        
+        Roo.bootstrap.DateField.superclass.setValue.call(this, '');
+    }
+   
+});
+
+Roo.apply(Roo.bootstrap.DateField,  {
+    
+    head : {
+        tag: 'thead',
+        cn: [
+        {
+            tag: 'tr',
+            cn: [
+            {
+                tag: 'th',
+                cls: 'prev',
+                html: '<i class="fa fa-arrow-left"/>'
+            },
+            {
+                tag: 'th',
+                cls: 'switch',
+                colspan: '5'
+            },
+            {
+                tag: 'th',
+                cls: 'next',
+                html: '<i class="fa fa-arrow-right"/>'
+            }
+
+            ]
+        }
+        ]
+    },
+    
+    content : {
+        tag: 'tbody',
+        cn: [
+        {
+            tag: 'tr',
+            cn: [
+            {
+                tag: 'td',
+                colspan: '7'
+            }
+            ]
+        }
+        ]
+    },
+    
+    footer : {
+        tag: 'tfoot',
+        cn: [
+        {
+            tag: 'tr',
+            cn: [
+            {
+                tag: 'th',
+                colspan: '7',
+                cls: 'today'
+            }
+                    
+            ]
+        }
+        ]
+    },
+    
+    dates:{
+        en: {
+            days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
+            daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
+            daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
+            months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
+            monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
+            today: "Today"
+        }
+    },
+    
+    modes: [
+    {
+        clsName: 'days',
+        navFnc: 'Month',
+        navStep: 1
+    },
+    {
+        clsName: 'months',
+        navFnc: 'FullYear',
+        navStep: 1
+    },
+    {
+        clsName: 'years',
+        navFnc: 'FullYear',
+        navStep: 10
+    }]
+});
+
+Roo.apply(Roo.bootstrap.DateField,  {
+  
+    template : {
+        tag: 'div',
+        cls: 'datepicker dropdown-menu roo-dynamic shadow',
+        cn: [
+        {
+            tag: 'div',
+            cls: 'datepicker-days',
+            cn: [
+            {
+                tag: 'table',
+                cls: 'table-condensed',
+                cn:[
+                Roo.bootstrap.DateField.head,
+                {
+                    tag: 'tbody'
+                },
+                Roo.bootstrap.DateField.footer
+                ]
+            }
+            ]
+        },
+        {
+            tag: 'div',
+            cls: 'datepicker-months',
+            cn: [
+            {
+                tag: 'table',
+                cls: 'table-condensed',
+                cn:[
+                Roo.bootstrap.DateField.head,
+                Roo.bootstrap.DateField.content,
+                Roo.bootstrap.DateField.footer
+                ]
+            }
+            ]
+        },
+        {
+            tag: 'div',
+            cls: 'datepicker-years',
+            cn: [
+            {
+                tag: 'table',
+                cls: 'table-condensed',
+                cn:[
+                Roo.bootstrap.DateField.head,
+                Roo.bootstrap.DateField.content,
+                Roo.bootstrap.DateField.footer
+                ]
+            }
+            ]
+        }
+        ]
+    }
+});
+
+
\ No newline at end of file
diff --git a/Roo/bootstrap/form/DateSplitField.js b/Roo/bootstrap/form/DateSplitField.js
new file mode 100644 (file)
index 0000000..421a119
--- /dev/null
@@ -0,0 +1,432 @@
+/*
+ * - LGPL
+ *
+ * page DateSplitField.
+ * 
+ */
+
+
+/**
+ * @class Roo.bootstrap.DateSplitField
+ * @extends Roo.bootstrap.Component
+ * Bootstrap DateSplitField class
+ * @cfg {string} fieldLabel - the label associated
+ * @cfg {Number} labelWidth set the width of label (0-12)
+ * @cfg {String} labelAlign (top|left)
+ * @cfg {Boolean} dayAllowBlank (true|false) default false
+ * @cfg {Boolean} monthAllowBlank (true|false) default false
+ * @cfg {Boolean} yearAllowBlank (true|false) default false
+ * @cfg {string} dayPlaceholder 
+ * @cfg {string} monthPlaceholder
+ * @cfg {string} yearPlaceholder
+ * @cfg {string} dayFormat default 'd'
+ * @cfg {string} monthFormat default 'm'
+ * @cfg {string} yearFormat default 'Y'
+ * @cfg {Number} labellg set the width of label (1-12)
+ * @cfg {Number} labelmd set the width of label (1-12)
+ * @cfg {Number} labelsm set the width of label (1-12)
+ * @cfg {Number} labelxs set the width of label (1-12)
+
+ *     
+ * @constructor
+ * Create a new DateSplitField
+ * @param {Object} config The config object
+ */
+
+Roo.bootstrap.DateSplitField = function(config){
+    Roo.bootstrap.DateSplitField.superclass.constructor.call(this, config);
+    
+    this.addEvents({
+        // raw events
+         /**
+         * @event years
+         * getting the data of years
+         * @param {Roo.bootstrap.DateSplitField} this
+         * @param {Object} years
+         */
+        "years" : true,
+        /**
+         * @event days
+         * getting the data of days
+         * @param {Roo.bootstrap.DateSplitField} this
+         * @param {Object} days
+         */
+        "days" : true,
+        /**
+         * @event invalid
+         * Fires after the field has been marked as invalid.
+         * @param {Roo.form.Field} this
+         * @param {String} msg The validation message
+         */
+        invalid : true,
+       /**
+         * @event valid
+         * Fires after the field has been validated with no errors.
+         * @param {Roo.form.Field} this
+         */
+        valid : true
+    });
+};
+
+Roo.extend(Roo.bootstrap.DateSplitField, Roo.bootstrap.Component,  {
+    
+    fieldLabel : '',
+    labelAlign : 'top',
+    labelWidth : 3,
+    dayAllowBlank : false,
+    monthAllowBlank : false,
+    yearAllowBlank : false,
+    dayPlaceholder : '',
+    monthPlaceholder : '',
+    yearPlaceholder : '',
+    dayFormat : 'd',
+    monthFormat : 'm',
+    yearFormat : 'Y',
+    isFormField : true,
+    labellg : 0,
+    labelmd : 0,
+    labelsm : 0,
+    labelxs : 0,
+    
+    getAutoCreate : function()
+    {
+        var cfg = {
+            tag : 'div',
+            cls : 'row roo-date-split-field-group',
+            cn : [
+                {
+                    tag : 'input',
+                    type : 'hidden',
+                    cls : 'form-hidden-field roo-date-split-field-group-value',
+                    name : this.name
+                }
+            ]
+        };
+        
+        var labelCls = 'col-md-12';
+        var contentCls = 'col-md-4';
+        
+        if(this.fieldLabel){
+            
+            var label = {
+                tag : 'div',
+                cls : 'column roo-date-split-field-label col-md-' + ((this.labelAlign == 'top') ? '12' : this.labelWidth),
+                cn : [
+                    {
+                        tag : 'label',
+                        html : this.fieldLabel
+                    }
+                ]
+            };
+            
+            if(this.labelAlign == 'left'){
+            
+                if(this.labelWidth > 12){
+                    label.style = "width: " + this.labelWidth + 'px';
+                }
+
+                if(this.labelWidth < 13 && this.labelmd == 0){
+                    this.labelmd = this.labelWidth;
+                }
+
+                if(this.labellg > 0){
+                    labelCls = ' col-lg-' + this.labellg;
+                    contentCls = ' col-lg-' + ((12 - this.labellg) / 3);
+                }
+
+                if(this.labelmd > 0){
+                    labelCls = ' col-md-' + this.labelmd;
+                    contentCls = ' col-md-' + ((12 - this.labelmd) / 3);
+                }
+
+                if(this.labelsm > 0){
+                    labelCls = ' col-sm-' + this.labelsm;
+                    contentCls = ' col-sm-' + ((12 - this.labelsm) / 3);
+                }
+
+                if(this.labelxs > 0){
+                    labelCls = ' col-xs-' + this.labelxs;
+                    contentCls = ' col-xs-' + ((12 - this.labelxs) / 3);
+                }
+            }
+            
+            label.cls += ' ' + labelCls;
+            
+            cfg.cn.push(label);
+        }
+        
+        Roo.each(['day', 'month', 'year'], function(t){
+            cfg.cn.push({
+                tag : 'div',
+                cls : 'column roo-date-split-field-' + t + ' ' + contentCls
+            });
+        }, this);
+        
+        return cfg;
+    },
+    
+    inputEl: function ()
+    {
+        return this.el.select('.roo-date-split-field-group-value', true).first();
+    },
+    
+    onRender : function(ct, position) 
+    {
+        var _this = this;
+        
+        Roo.bootstrap.DateSplitFiel.superclass.onRender.call(this, ct, position);
+        
+        this.inputEl = this.el.select('.roo-date-split-field-group-value', true).first();
+        
+        this.dayField = new Roo.bootstrap.ComboBox({
+            allowBlank : this.dayAllowBlank,
+            alwaysQuery : true,
+            displayField : 'value',
+            editable : false,
+            fieldLabel : '',
+            forceSelection : true,
+            mode : 'local',
+            placeholder : this.dayPlaceholder,
+            selectOnFocus : true,
+            tpl : '<div class="roo-select2-result"><b>{value}</b></div>',
+            triggerAction : 'all',
+            typeAhead : true,
+            valueField : 'value',
+            store : new Roo.data.SimpleStore({
+                data : (function() {    
+                    var days = [];
+                    _this.fireEvent('days', _this, days);
+                    return days;
+                })(),
+                fields : [ 'value' ]
+            }),
+            listeners : {
+                select : function (_self, record, index)
+                {
+                    _this.setValue(_this.getValue());
+                }
+            }
+        });
+
+        this.dayField.render(this.el.select('.roo-date-split-field-day', true).first(), null);
+        
+        this.monthField = new Roo.bootstrap.MonthField({
+            after : '<i class=\"fa fa-calendar\"></i>',
+            allowBlank : this.monthAllowBlank,
+            placeholder : this.monthPlaceholder,
+            readOnly : true,
+            listeners : {
+                render : function (_self)
+                {
+                    this.el.select('span.input-group-addon', true).first().on('click', function(e){
+                        e.preventDefault();
+                        _self.focus();
+                    });
+                },
+                select : function (_self, oldvalue, newvalue)
+                {
+                    _this.setValue(_this.getValue());
+                }
+            }
+        });
+        
+        this.monthField.render(this.el.select('.roo-date-split-field-month', true).first(), null);
+        
+        this.yearField = new Roo.bootstrap.ComboBox({
+            allowBlank : this.yearAllowBlank,
+            alwaysQuery : true,
+            displayField : 'value',
+            editable : false,
+            fieldLabel : '',
+            forceSelection : true,
+            mode : 'local',
+            placeholder : this.yearPlaceholder,
+            selectOnFocus : true,
+            tpl : '<div class="roo-select2-result"><b>{value}</b></div>',
+            triggerAction : 'all',
+            typeAhead : true,
+            valueField : 'value',
+            store : new Roo.data.SimpleStore({
+                data : (function() {
+                    var years = [];
+                    _this.fireEvent('years', _this, years);
+                    return years;
+                })(),
+                fields : [ 'value' ]
+            }),
+            listeners : {
+                select : function (_self, record, index)
+                {
+                    _this.setValue(_this.getValue());
+                }
+            }
+        });
+
+        this.yearField.render(this.el.select('.roo-date-split-field-year', true).first(), null);
+    },
+    
+    setValue : function(v, format)
+    {
+        this.inputEl.dom.value = v;
+        
+        var f = format || (this.yearFormat + '-' + this.monthFormat + '-' + this.dayFormat);
+        
+        var d = Date.parseDate(v, f);
+        
+        if(!d){
+            this.validate();
+            return;
+        }
+        
+        this.setDay(d.format(this.dayFormat));
+        this.setMonth(d.format(this.monthFormat));
+        this.setYear(d.format(this.yearFormat));
+        
+        this.validate();
+        
+        return;
+    },
+    
+    setDay : function(v)
+    {
+        this.dayField.setValue(v);
+        this.inputEl.dom.value = this.getValue();
+        this.validate();
+        return;
+    },
+    
+    setMonth : function(v)
+    {
+        this.monthField.setValue(v, true);
+        this.inputEl.dom.value = this.getValue();
+        this.validate();
+        return;
+    },
+    
+    setYear : function(v)
+    {
+        this.yearField.setValue(v);
+        this.inputEl.dom.value = this.getValue();
+        this.validate();
+        return;
+    },
+    
+    getDay : function()
+    {
+        return this.dayField.getValue();
+    },
+    
+    getMonth : function()
+    {
+        return this.monthField.getValue();
+    },
+    
+    getYear : function()
+    {
+        return this.yearField.getValue();
+    },
+    
+    getValue : function()
+    {
+        var f = this.yearFormat + '-' + this.monthFormat + '-' + this.dayFormat;
+        
+        var date = this.yearField.getValue() + '-' + this.monthField.getValue() + '-' + this.dayField.getValue();
+        
+        return date;
+    },
+    
+    reset : function()
+    {
+        this.setDay('');
+        this.setMonth('');
+        this.setYear('');
+        this.inputEl.dom.value = '';
+        this.validate();
+        return;
+    },
+    
+    validate : function()
+    {
+        var d = this.dayField.validate();
+        var m = this.monthField.validate();
+        var y = this.yearField.validate();
+        
+        var valid = true;
+        
+        if(
+                (!this.dayAllowBlank && !d) ||
+                (!this.monthAllowBlank && !m) ||
+                (!this.yearAllowBlank && !y)
+        ){
+            valid = false;
+        }
+        
+        if(this.dayAllowBlank && this.monthAllowBlank && this.yearAllowBlank){
+            return valid;
+        }
+        
+        if(valid){
+            this.markValid();
+            return valid;
+        }
+        
+        this.markInvalid();
+        
+        return valid;
+    },
+    
+    markValid : function()
+    {
+        
+        var label = this.el.select('label', true).first();
+        var icon = this.el.select('i.fa-star', true).first();
+
+        if(label && icon){
+            icon.remove();
+        }
+        
+        this.fireEvent('valid', this);
+    },
+    
+     /**
+     * Mark this field as invalid
+     * @param {String} msg The validation message
+     */
+    markInvalid : function(msg)
+    {
+        
+        var label = this.el.select('label', true).first();
+        var icon = this.el.select('i.fa-star', true).first();
+
+        if(label && !icon){
+            this.el.select('.roo-date-split-field-label', true).createChild({
+                tag : 'i',
+                cls : 'text-danger fa fa-lg fa-star',
+                tooltip : 'This field is required',
+                style : 'margin-right:5px;'
+            }, label, true);
+        }
+        
+        this.fireEvent('invalid', this, msg);
+    },
+    
+    clearInvalid : function()
+    {
+        var label = this.el.select('label', true).first();
+        var icon = this.el.select('i.fa-star', true).first();
+
+        if(label && icon){
+            icon.remove();
+        }
+        
+        this.fireEvent('valid', this);
+    },
+    
+    getName: function()
+    {
+        return this.name;
+    }
+    
+});
+
\ No newline at end of file
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;
+            
+        }
+        
+    }
+    
+});
+
diff --git a/Roo/bootstrap/form/Input.js b/Roo/bootstrap/form/Input.js
new file mode 100644 (file)
index 0000000..d024603
--- /dev/null
@@ -0,0 +1,1107 @@
+/*
+ * - LGPL
+ *
+ * Input
+ * 
+ */
+
+/**
+ * @class Roo.bootstrap.Input
+ * @extends Roo.bootstrap.Component
+ * Bootstrap Input class
+ * @cfg {Boolean} disabled is it disabled
+ * @cfg {String} inputType (button|checkbox|email|file|hidden|image|number|password|radio|range|reset|search|submit|text)  
+ * @cfg {String} name name of the input
+ * @cfg {string} fieldLabel - the label associated
+ * @cfg {string} placeholder - placeholder to put in text.
+ * @cfg {string} before - input group add on before
+ * @cfg {string} after - input group add on after
+ * @cfg {string} size - (lg|sm) or leave empty..
+ * @cfg {Number} xs colspan out of 12 for mobile-sized screens
+ * @cfg {Number} sm colspan out of 12 for tablet-sized screens
+ * @cfg {Number} md colspan out of 12 for computer-sized screens
+ * @cfg {Number} lg colspan out of 12 for large computer-sized screens
+ * @cfg {string} value default value of the input
+ * @cfg {Number} labelWidth set the width of label 
+ * @cfg {Number} labellg set the width of label (1-12)
+ * @cfg {Number} labelmd set the width of label (1-12)
+ * @cfg {Number} labelsm set the width of label (1-12)
+ * @cfg {Number} labelxs set the width of label (1-12)
+ * @cfg {String} labelAlign (top|left)
+ * @cfg {Boolean} readOnly Specifies that the field should be read-only
+ * @cfg {String} autocomplete - default is new-password see: https://developers.google.com/web/fundamentals/input/form/label-and-name-inputs?hl=en
+ * @cfg {String} indicatorpos (left|right) default left
+ * @cfg {String} capture (user|camera) use for file input only. (default empty)
+ * @cfg {String} accept (image|video|audio) use for file input only. (default empty)
+ * @cfg {Boolean} preventMark Do not show tick or cross if error/success
+ * @cfg {Roo.bootstrap.Button} before Button to show before
+ * @cfg {Roo.bootstrap.Button} afterButton to show before
+ * @cfg {String} align (left|center|right) Default left
+ * @cfg {Boolean} forceFeedback (true|false) Default false
+ * 
+ * @constructor
+ * Create a new Input
+ * @param {Object} config The config object
+ */
+
+Roo.bootstrap.Input = function(config){
+    
+    Roo.bootstrap.Input.superclass.constructor.call(this, config);
+    
+    this.addEvents({
+        /**
+         * @event focus
+         * Fires when this field receives input focus.
+         * @param {Roo.form.Field} this
+         */
+        focus : true,
+        /**
+         * @event blur
+         * Fires when this field loses input focus.
+         * @param {Roo.form.Field} this
+         */
+        blur : true,
+        /**
+         * @event specialkey
+         * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.  You can check
+         * {@link Roo.EventObject#getKey} to determine which key was pressed.
+         * @param {Roo.form.Field} this
+         * @param {Roo.EventObject} e The event object
+         */
+        specialkey : true,
+        /**
+         * @event change
+         * Fires just before the field blurs if the field value has changed.
+         * @param {Roo.form.Field} this
+         * @param {Mixed} newValue The new value
+         * @param {Mixed} oldValue The original value
+         */
+        change : true,
+        /**
+         * @event invalid
+         * Fires after the field has been marked as invalid.
+         * @param {Roo.form.Field} this
+         * @param {String} msg The validation message
+         */
+        invalid : true,
+        /**
+         * @event valid
+         * Fires after the field has been validated with no errors.
+         * @param {Roo.form.Field} this
+         */
+        valid : true,
+         /**
+         * @event keyup
+         * Fires after the key up
+         * @param {Roo.form.Field} this
+         * @param {Roo.EventObject}  e The event Object
+         */
+        keyup : true,
+        /**
+         * @event paste
+         * Fires after the user pastes into input
+         * @param {Roo.form.Field} this
+         * @param {Roo.EventObject}  e The event Object
+         */
+        paste : true
+    });
+};
+
+Roo.extend(Roo.bootstrap.Input, Roo.bootstrap.Component,  {
+     /**
+     * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable
+      automatic validation (defaults to "keyup").
+     */
+    validationEvent : "keyup",
+     /**
+     * @cfg {Boolean} validateOnBlur Whether the field should validate when it loses focus (defaults to true).
+     */
+    validateOnBlur : true,
+    /**
+     * @cfg {Number} validationDelay The length of time in milliseconds after user input begins until validation is initiated (defaults to 250)
+     */
+    validationDelay : 250,
+     /**
+     * @cfg {String} focusClass The CSS class to use when the field receives focus (defaults to "x-form-focus")
+     */
+    focusClass : "x-form-focus",  // not needed???
+    
+       
+    /**
+     * @cfg {String} invalidClass DEPRICATED - code uses BS4 - is-valid / is-invalid
+     */
+    invalidClass : "has-warning",
+    
+    /**
+     * @cfg {String} validClass DEPRICATED - code uses BS4 - is-valid / is-invalid
+     */
+    validClass : "has-success",
+    
+    /**
+     * @cfg {Boolean} hasFeedback (true|false) default true
+     */
+    hasFeedback : true,
+    
+    /**
+     * @cfg {String} invalidFeedbackIcon The CSS class to use when create feedback icon (defaults to "x-form-invalid")
+     */
+    invalidFeedbackClass : "glyphicon-warning-sign",
+    
+    /**
+     * @cfg {String} validFeedbackIcon The CSS class to use when create feedback icon (defaults to "x-form-invalid")
+     */
+    validFeedbackClass : "glyphicon-ok",
+    
+    /**
+     * @cfg {Boolean} selectOnFocus True to automatically select any existing field text when the field receives input focus (defaults to false)
+     */
+    selectOnFocus : false,
+    
+     /**
+     * @cfg {String} maskRe An input mask regular expression that will be used to filter keystrokes that don't match (defaults to null)
+     */
+    maskRe : null,
+       /**
+     * @cfg {String} vtype A validation type name as defined in {@link Roo.form.VTypes} (defaults to null)
+     */
+    vtype : null,
+    
+      /**
+     * @cfg {Boolean} disableKeyFilter True to disable input keystroke filtering (defaults to false)
+     */
+    disableKeyFilter : false,
+    
+       /**
+     * @cfg {Boolean} disabled True to disable the field (defaults to false).
+     */
+    disabled : false,
+     /**
+     * @cfg {Boolean} allowBlank False to validate that the value length > 0 (defaults to true)
+     */
+    allowBlank : true,
+    /**
+     * @cfg {String} blankText Error text to display if the allow blank validation fails (defaults to "This field is required")
+     */
+    blankText : "Please complete this mandatory field",
+    
+     /**
+     * @cfg {Number} minLength Minimum input field length required (defaults to 0)
+     */
+    minLength : 0,
+    /**
+     * @cfg {Number} maxLength Maximum input field length allowed (defaults to Number.MAX_VALUE)
+     */
+    maxLength : Number.MAX_VALUE,
+    /**
+     * @cfg {String} minLengthText Error text to display if the minimum length validation fails (defaults to "The minimum length for this field is {minLength}")
+     */
+    minLengthText : "The minimum length for this field is {0}",
+    /**
+     * @cfg {String} maxLengthText Error text to display if the maximum length validation fails (defaults to "The maximum length for this field is {maxLength}")
+     */
+    maxLengthText : "The maximum length for this field is {0}",
+  
+    
+    /**
+     * @cfg {Function} validator A custom validation function to be called during field validation (defaults to null).
+     * If available, this function will be called only after the basic validators all return true, and will be passed the
+     * current field value and expected to return boolean true if the value is valid or a string error message if invalid.
+     */
+    validator : null,
+    /**
+     * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation (defaults to null).
+     * If available, this regex will be evaluated only after the basic validators all return true, and will be passed the
+     * current field value.  If the test fails, the field will be marked invalid using {@link #regexText}.
+     */
+    regex : null,
+    /**
+     * @cfg {String} regexText -- Depricated - use Invalid Text
+     */
+    regexText : "",
+    
+    /**
+     * @cfg {String} invalidText The error text to display if {@link #validator} test fails during validation (defaults to "")
+     */
+    invalidText : "",
+    
+    
+    
+    autocomplete: false,
+    
+    
+    fieldLabel : '',
+    inputType : 'text',
+    
+    name : false,
+    placeholder: false,
+    before : false,
+    after : false,
+    size : false,
+    hasFocus : false,
+    preventMark: false,
+    isFormField : true,
+    value : '',
+    labelWidth : 2,
+    labelAlign : false,
+    readOnly : false,
+    align : false,
+    formatedValue : false,
+    forceFeedback : false,
+    
+    indicatorpos : 'left',
+    
+    labellg : 0,
+    labelmd : 0,
+    labelsm : 0,
+    labelxs : 0,
+    
+    capture : '',
+    accept : '',
+    
+    parentLabelAlign : function()
+    {
+        var parent = this;
+        while (parent.parent()) {
+            parent = parent.parent();
+            if (typeof(parent.labelAlign) !='undefined') {
+                return parent.labelAlign;
+            }
+        }
+        return 'left';
+        
+    },
+    
+    getAutoCreate : function()
+    {
+        var align = (!this.labelAlign) ? this.parentLabelAlign() : this.labelAlign;
+        
+        var id = Roo.id();
+        
+        var cfg = {};
+        
+        if(this.inputType != 'hidden'){
+            cfg.cls = 'form-group' //input-group
+        }
+        
+        var input =  {
+            tag: 'input',
+            id : id,
+            type : this.inputType,
+            value : this.value,
+            cls : 'form-control',
+            placeholder : this.placeholder || '',
+            autocomplete : this.autocomplete || 'new-password'
+        };
+        if (this.inputType == 'file') {
+            input.style = 'overflow:hidden'; // why not in CSS?
+        }
+        
+        if(this.capture.length){
+            input.capture = this.capture;
+        }
+        
+        if(this.accept.length){
+            input.accept = this.accept + "/*";
+        }
+        
+        if(this.align){
+            input.style = (typeof(input.style) == 'undefined') ? ('text-align:' + this.align) : (input.style + 'text-align:' + this.align);
+        }
+        
+        if(this.maxLength && this.maxLength != Number.MAX_VALUE){
+            input.maxLength = this.maxLength;
+        }
+        
+        if (this.disabled) {
+            input.disabled=true;
+        }
+        
+        if (this.readOnly) {
+            input.readonly=true;
+        }
+        
+        if (this.name) {
+            input.name = this.name;
+        }
+        
+        if (this.size) {
+            input.cls += ' input-' + this.size;
+        }
+        
+        var settings=this;
+        ['xs','sm','md','lg'].map(function(size){
+            if (settings[size]) {
+                cfg.cls += ' col-' + size + '-' + settings[size];
+            }
+        });
+        
+        var inputblock = input;
+        
+        var feedback = {
+            tag: 'span',
+            cls: 'glyphicon form-control-feedback'
+        };
+            
+        if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
+            
+            inputblock = {
+                cls : 'has-feedback',
+                cn :  [
+                    input,
+                    feedback
+                ] 
+            };  
+        }
+        
+        if (this.before || this.after) {
+            
+            inputblock = {
+                cls : 'input-group',
+                cn :  [] 
+            };
+            
+            if (this.before && typeof(this.before) == 'string') {
+                
+                inputblock.cn.push({
+                    tag :'span',
+                    cls : 'roo-input-before input-group-addon input-group-prepend input-group-text',
+                    html : this.before
+                });
+            }
+            if (this.before && typeof(this.before) == 'object') {
+                this.before = Roo.factory(this.before);
+                
+                inputblock.cn.push({
+                    tag :'span',
+                    cls : 'roo-input-before input-group-prepend   input-group-' +
+                        (this.before.xtype == 'Button' ? 'btn' : 'addon')  //?? what about checkboxes - that looks like a bit of a hack thought? 
+                });
+            }
+            
+            inputblock.cn.push(input);
+            
+            if (this.after && typeof(this.after) == 'string') {
+                inputblock.cn.push({
+                    tag :'span',
+                    cls : 'roo-input-after input-group-append input-group-text input-group-addon',
+                    html : this.after
+                });
+            }
+            if (this.after && typeof(this.after) == 'object') {
+                this.after = Roo.factory(this.after);
+                
+                inputblock.cn.push({
+                    tag :'span',
+                    cls : 'roo-input-after input-group-append  input-group-' +
+                        (this.after.xtype == 'Button' ? 'btn' : 'addon')  //?? what about checkboxes - that looks like a bit of a hack thought? 
+                });
+            }
+            
+            if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
+                inputblock.cls += ' has-feedback';
+                inputblock.cn.push(feedback);
+            }
+        };
+        var indicator = {
+            tag : 'i',
+            cls : 'roo-required-indicator ' + (this.indicatorpos == 'right'  ? 'right' : 'left') +'-indicator text-danger fa fa-lg fa-star',
+            tooltip : 'This field is required'
+        };
+        if (this.allowBlank ) {
+            indicator.style = this.allowBlank ? ' display:none' : '';
+        }
+        if (align ==='left' && this.fieldLabel.length) {
+            
+            cfg.cls += ' roo-form-group-label-left'  + (Roo.bootstrap.version == 4 ? ' row' : '');
+            
+            cfg.cn = [
+                indicator,
+                {
+                    tag: 'label',
+                    'for' :  id,
+                    cls : 'control-label col-form-label',
+                    html : this.fieldLabel
+
+                },
+                {
+                    cls : "", 
+                    cn: [
+                        inputblock
+                    ]
+                }
+            ];
+            
+            var labelCfg = cfg.cn[1];
+            var contentCfg = cfg.cn[2];
+            
+            if(this.indicatorpos == 'right'){
+                cfg.cn = [
+                    {
+                        tag: 'label',
+                        'for' :  id,
+                        cls : 'control-label col-form-label',
+                        cn : [
+                            {
+                                tag : 'span',
+                                html : this.fieldLabel
+                            },
+                            indicator
+                        ]
+                    },
+                    {
+                        cls : "",
+                        cn: [
+                            inputblock
+                        ]
+                    }
+
+                ];
+                
+                labelCfg = cfg.cn[0];
+                contentCfg = cfg.cn[1];
+            
+            }
+            
+            if(this.labelWidth > 12){
+                labelCfg.style = "width: " + this.labelWidth + 'px';
+            }
+            
+            if(this.labelWidth < 13 && this.labelmd == 0){
+                this.labellg = this.labellg > 0 ? this.labellg : this.labelWidth;
+            }
+            
+            if(this.labellg > 0){
+                labelCfg.cls += ' col-lg-' + this.labellg;
+                contentCfg.cls += ' col-lg-' + (12 - this.labellg);
+            }
+            
+            if(this.labelmd > 0){
+                labelCfg.cls += ' col-md-' + this.labelmd;
+                contentCfg.cls += ' col-md-' + (12 - this.labelmd);
+            }
+            
+            if(this.labelsm > 0){
+                labelCfg.cls += ' col-sm-' + this.labelsm;
+                contentCfg.cls += ' col-sm-' + (12 - this.labelsm);
+            }
+            
+            if(this.labelxs > 0){
+                labelCfg.cls += ' col-xs-' + this.labelxs;
+                contentCfg.cls += ' col-xs-' + (12 - this.labelxs);
+            }
+            
+            
+        } else if ( this.fieldLabel.length) {
+                
+            
+            
+            cfg.cn = [
+                {
+                    tag : 'i',
+                    cls : 'roo-required-indicator left-indicator text-danger fa fa-lg fa-star',
+                    tooltip : 'This field is required',
+                    style : this.allowBlank ? ' display:none' : '' 
+                },
+                {
+                    tag: 'label',
+                   //cls : 'input-group-addon',
+                    html : this.fieldLabel
+
+                },
+
+               inputblock
+
+           ];
+           
+           if(this.indicatorpos == 'right'){
+       
+                cfg.cn = [
+                    {
+                        tag: 'label',
+                       //cls : 'input-group-addon',
+                        html : this.fieldLabel
+
+                    },
+                    {
+                        tag : 'i',
+                        cls : 'roo-required-indicator right-indicator text-danger fa fa-lg fa-star',
+                        tooltip : 'This field is required',
+                        style : this.allowBlank ? ' display:none' : '' 
+                    },
+
+                   inputblock
+
+               ];
+
+            }
+
+        } else {
+            
+            cfg.cn = [
+
+                    inputblock
+
+            ];
+                
+                
+        };
+        
+        if (this.parentType === 'Navbar' &&  this.parent().bar) {
+           cfg.cls += ' navbar-form';
+        }
+        
+        if (this.parentType === 'NavGroup' && !(Roo.bootstrap.version == 4 && this.parent().form)) {
+            // on BS4 we do this only if not form 
+            cfg.cls += ' navbar-form';
+            cfg.tag = 'li';
+        }
+        
+        return cfg;
+        
+    },
+    /**
+     * return the real input element.
+     */
+    inputEl: function ()
+    {
+        return this.el.select('input.form-control',true).first();
+    },
+    
+    tooltipEl : function()
+    {
+        return this.inputEl();
+    },
+    
+    indicatorEl : function()
+    {
+        if (Roo.bootstrap.version == 4) {
+            return false; // not enabled in v4 yet.
+        }
+        
+        var indicator = this.el.select('i.roo-required-indicator',true).first();
+        
+        if(!indicator){
+            return false;
+        }
+        
+        return indicator;
+        
+    },
+    
+    setDisabled : function(v)
+    {
+        var i  = this.inputEl().dom;
+        if (!v) {
+            i.removeAttribute('disabled');
+            return;
+            
+        }
+        i.setAttribute('disabled','true');
+    },
+    initEvents : function()
+    {
+          
+        this.inputEl().on("keydown" , this.fireKey,  this);
+        this.inputEl().on("focus", this.onFocus,  this);
+        this.inputEl().on("blur", this.onBlur,  this);
+        
+        this.inputEl().relayEvent('keyup', this);
+        this.inputEl().relayEvent('paste', this);
+        
+        this.indicator = this.indicatorEl();
+        
+        if(this.indicator){
+            this.indicator.addClass(this.indicatorpos == 'right' ? 'hidden' : 'invisible'); // changed from invisible??? - 
+        }
+        // reference to original value for reset
+        this.originalValue = this.getValue();
+        //Roo.form.TextField.superclass.initEvents.call(this);
+        if(this.validationEvent == 'keyup'){
+            this.validationTask = new Roo.util.DelayedTask(this.validate, this);
+            this.inputEl().on('keyup', this.filterValidation, this);
+        }
+        else if(this.validationEvent !== false){
+            this.inputEl().on(this.validationEvent, this.validate, this, {buffer: this.validationDelay});
+        }
+        
+        if(this.selectOnFocus){
+            this.on("focus", this.preFocus, this);
+            
+        }
+        if(this.maskRe || (this.vtype && this.disableKeyFilter !== true && (this.maskRe = Roo.form.VTypes[this.vtype+'Mask']))){
+            this.inputEl().on("keypress", this.filterKeys, this);
+        } else {
+            this.inputEl().relayEvent('keypress', this);
+        }
+       /* if(this.grow){
+            this.el.on("keyup", this.onKeyUp,  this, {buffer:50});
+            this.el.on("click", this.autoSize,  this);
+        }
+        */
+        if(this.inputEl().is('input[type=password]') && Roo.isSafari){
+            this.inputEl().on('keydown', this.SafariOnKeyDown, this);
+        }
+        
+        if (typeof(this.before) == 'object') {
+            this.before.render(this.el.select('.roo-input-before',true).first());
+        }
+        if (typeof(this.after) == 'object') {
+            this.after.render(this.el.select('.roo-input-after',true).first());
+        }
+        
+        this.inputEl().on('change', this.onChange, this);
+        
+    },
+    filterValidation : function(e){
+        if(!e.isNavKeyPress()){
+            this.validationTask.delay(this.validationDelay);
+        }
+    },
+     /**
+     * Validates the field value
+     * @return {Boolean} True if the value is valid, else false
+     */
+    validate : function(){
+        //if(this.disabled || this.validateValue(this.processValue(this.getRawValue()))){
+        if(this.disabled || this.validateValue(this.getRawValue())){
+            this.markValid();
+            return true;
+        }
+        
+        this.markInvalid();
+        return false;
+    },
+    
+    
+    /**
+     * Validates a value according to the field's validation rules and marks the field as invalid
+     * if the validation fails
+     * @param {Mixed} value The value to validate
+     * @return {Boolean} True if the value is valid, else false
+     */
+    validateValue : function(value)
+    {
+        if(this.getVisibilityEl().hasClass('hidden')){
+            return true;
+        }
+        
+        if(value.length < 1)  { // if it's blank
+            if(this.allowBlank){
+                return true;
+            }
+            return false;
+        }
+        
+        if(value.length < this.minLength){
+            return false;
+        }
+        if(value.length > this.maxLength){
+            return false;
+        }
+        if(this.vtype){
+            var vt = Roo.form.VTypes;
+            if(!vt[this.vtype](value, this)){
+                return false;
+            }
+        }
+        if(typeof this.validator == "function"){
+            var msg = this.validator(value);
+            if(msg !== true){
+                return false;
+            }
+            if (typeof(msg) == 'string') {
+                this.invalidText = msg;
+            }
+        }
+        
+        if(this.regex && !this.regex.test(value)){
+            return false;
+        }
+        
+        return true;
+    },
+    
+     // private
+    fireKey : function(e){
+        //Roo.log('field ' + e.getKey());
+        if(e.isNavKeyPress()){
+            this.fireEvent("specialkey", this, e);
+        }
+    },
+    focus : function (selectText){
+        if(this.rendered){
+            this.inputEl().focus();
+            if(selectText === true){
+                this.inputEl().dom.select();
+            }
+        }
+        return this;
+    } ,
+    
+    onFocus : function(){
+        if(!Roo.isOpera && this.focusClass){ // don't touch in Opera
+           // this.el.addClass(this.focusClass);
+        }
+        if(!this.hasFocus){
+            this.hasFocus = true;
+            this.startValue = this.getValue();
+            this.fireEvent("focus", this);
+        }
+    },
+    
+    beforeBlur : Roo.emptyFn,
+
+    
+    // private
+    onBlur : function(){
+        this.beforeBlur();
+        if(!Roo.isOpera && this.focusClass){ // don't touch in Opera
+            //this.el.removeClass(this.focusClass);
+        }
+        this.hasFocus = false;
+        if(this.validationEvent !== false && this.validateOnBlur && this.validationEvent != "blur"){
+            this.validate();
+        }
+        var v = this.getValue();
+        if(String(v) !== String(this.startValue)){
+            this.fireEvent('change', this, v, this.startValue);
+        }
+        this.fireEvent("blur", this);
+    },
+    
+    onChange : function(e)
+    {
+        var v = this.getValue();
+        if(String(v) !== String(this.startValue)){
+            this.fireEvent('change', this, v, this.startValue);
+        }
+        
+    },
+    
+    /**
+     * Resets the current field value to the originally loaded value and clears any validation messages
+     */
+    reset : function(){
+        this.setValue(this.originalValue);
+        this.validate();
+    },
+     /**
+     * Returns the name of the field
+     * @return {Mixed} name The name field
+     */
+    getName: function(){
+        return this.name;
+    },
+     /**
+     * Returns the normalized data value (undefined or emptyText will be returned as '').  To return the raw value see {@link #getRawValue}.
+     * @return {Mixed} value The field value
+     */
+    getValue : function(){
+        
+        var v = this.inputEl().getValue();
+        
+        return v;
+    },
+    /**
+     * Returns the raw data value which may or may not be a valid, defined value.  To return a normalized value see {@link #getValue}.
+     * @return {Mixed} value The field value
+     */
+    getRawValue : function(){
+        var v = this.inputEl().getValue();
+        
+        return v;
+    },
+    
+    /**
+     * Sets the underlying DOM field's value directly, bypassing validation.  To set the value with validation see {@link #setValue}.
+     * @param {Mixed} value The value to set
+     */
+    setRawValue : function(v){
+        return this.inputEl().dom.value = (v === null || v === undefined ? '' : v);
+    },
+    
+    selectText : function(start, end){
+        var v = this.getRawValue();
+        if(v.length > 0){
+            start = start === undefined ? 0 : start;
+            end = end === undefined ? v.length : end;
+            var d = this.inputEl().dom;
+            if(d.setSelectionRange){
+                d.setSelectionRange(start, end);
+            }else if(d.createTextRange){
+                var range = d.createTextRange();
+                range.moveStart("character", start);
+                range.moveEnd("character", v.length-end);
+                range.select();
+            }
+        }
+    },
+    
+    /**
+     * Sets a data value into the field and validates it.  To set the value directly without validation see {@link #setRawValue}.
+     * @param {Mixed} value The value to set
+     */
+    setValue : function(v){
+        this.value = v;
+        if(this.rendered){
+            this.inputEl().dom.value = (v === null || v === undefined ? '' : v);
+            this.validate();
+        }
+    },
+    
+    /*
+    processValue : function(value){
+        if(this.stripCharsRe){
+            var newValue = value.replace(this.stripCharsRe, '');
+            if(newValue !== value){
+                this.setRawValue(newValue);
+                return newValue;
+            }
+        }
+        return value;
+    },
+  */
+    preFocus : function(){
+        
+        if(this.selectOnFocus){
+            this.inputEl().dom.select();
+        }
+    },
+    filterKeys : function(e){
+        var k = e.getKey();
+        if(!Roo.isIE && (e.isNavKeyPress() || k == e.BACKSPACE || (k == e.DELETE && e.button == -1))){
+            return;
+        }
+        var c = e.getCharCode(), cc = String.fromCharCode(c);
+        if(Roo.isIE && (e.isSpecialKey() || !cc)){
+            return;
+        }
+        if(!this.maskRe.test(cc)){
+            e.stopEvent();
+        }
+    },
+     /**
+     * Clear any invalid styles/messages for this field
+     */
+    clearInvalid : function(){
+        
+        if(!this.el || this.preventMark){ // not rendered
+            return;
+        }
+        
+        
+        this.el.removeClass([this.invalidClass, 'is-invalid']);
+        
+        if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
+            
+            var feedback = this.el.select('.form-control-feedback', true).first();
+            
+            if(feedback){
+                this.el.select('.form-control-feedback', true).first().removeClass(this.invalidFeedbackClass);
+            }
+            
+        }
+        
+        if(this.indicator){
+            this.indicator.removeClass('visible');
+            this.indicator.addClass(this.indicatorpos == 'right' ? 'hidden' : 'invisible');
+        }
+        
+        this.fireEvent('valid', this);
+    },
+    
+     /**
+     * Mark this field as valid
+     */
+    markValid : function()
+    {
+        if(!this.el  || this.preventMark){ // not rendered...
+            return;
+        }
+        
+        this.el.removeClass([this.invalidClass, this.validClass]);
+        this.inputEl().removeClass(['is-valid', 'is-invalid']);
+
+        var feedback = this.el.select('.form-control-feedback', true).first();
+            
+        if(feedback){
+            this.el.select('.form-control-feedback', true).first().removeClass([this.invalidFeedbackClass, this.validFeedbackClass]);
+        }
+        
+        if(this.indicator){
+            this.indicator.removeClass('visible');
+            this.indicator.addClass(this.indicatorpos == 'right' ? 'hidden' : 'invisible');
+        }
+        
+        if(this.disabled){
+            return;
+        }
+        
+           
+        if(this.allowBlank && !this.getRawValue().length){
+            return;
+        }
+        if (Roo.bootstrap.version == 3) {
+            this.el.addClass(this.validClass);
+        } else {
+            this.inputEl().addClass('is-valid');
+        }
+
+        if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank && (this.getValue().length || this.forceFeedback)){
+            
+            var feedback = this.el.select('.form-control-feedback', true).first();
+            
+            if(feedback){
+                this.el.select('.form-control-feedback', true).first().removeClass([this.invalidFeedbackClass, this.validFeedbackClass]);
+                this.el.select('.form-control-feedback', true).first().addClass([this.validFeedbackClass]);
+            }
+            
+        }
+        
+        this.fireEvent('valid', this);
+    },
+    
+     /**
+     * Mark this field as invalid
+     * @param {String} msg The validation message
+     */
+    markInvalid : function(msg)
+    {
+        if(!this.el  || this.preventMark){ // not rendered
+            return;
+        }
+        
+        this.el.removeClass([this.invalidClass, this.validClass]);
+        this.inputEl().removeClass(['is-valid', 'is-invalid']);
+        
+        var feedback = this.el.select('.form-control-feedback', true).first();
+            
+        if(feedback){
+            this.el.select('.form-control-feedback', true).first().removeClass(
+                    [this.invalidFeedbackClass, this.validFeedbackClass]);
+        }
+
+        if(this.disabled){
+            return;
+        }
+        
+        if(this.allowBlank && !this.getRawValue().length){
+            return;
+        }
+        
+        if(this.indicator){
+            this.indicator.removeClass(this.indicatorpos == 'right' ? 'hidden' : 'invisible');
+            this.indicator.addClass('visible');
+        }
+        if (Roo.bootstrap.version == 3) {
+            this.el.addClass(this.invalidClass);
+        } else {
+            this.inputEl().addClass('is-invalid');
+        }
+        
+        
+        
+        if(this.hasFeedback && this.inputType != 'hidden' && !this.allowBlank){
+            
+            var feedback = this.el.select('.form-control-feedback', true).first();
+            
+            if(feedback){
+                this.el.select('.form-control-feedback', true).first().removeClass([this.invalidFeedbackClass, this.validFeedbackClass]);
+                
+                if(this.getValue().length || this.forceFeedback){
+                    this.el.select('.form-control-feedback', true).first().addClass([this.invalidFeedbackClass]);
+                }
+                
+            }
+            
+        }
+        
+        this.fireEvent('invalid', this, msg);
+    },
+    // private
+    SafariOnKeyDown : function(event)
+    {
+        // this is a workaround for a password hang bug on chrome/ webkit.
+        if (this.inputEl().dom.type != 'password') {
+            return;
+        }
+        
+        var isSelectAll = false;
+        
+        if(this.inputEl().dom.selectionEnd > 0){
+            isSelectAll = (this.inputEl().dom.selectionEnd - this.inputEl().dom.selectionStart - this.getValue().length == 0) ? true : false;
+        }
+        if(((event.getKey() == 8 || event.getKey() == 46) && this.getValue().length ==1)){ // backspace and delete key
+            event.preventDefault();
+            this.setValue('');
+            return;
+        }
+        
+        if(isSelectAll  && event.getCharCode() > 31 && !event.ctrlKey) { // not backspace and delete key (or ctrl-v)
+            
+            event.preventDefault();
+            // this is very hacky as keydown always get's upper case.
+            //
+            var cc = String.fromCharCode(event.getCharCode());
+            this.setValue( event.shiftKey ?  cc : cc.toLowerCase());
+            
+        }
+    },
+    adjustWidth : function(tag, w){
+        tag = tag.toLowerCase();
+        if(typeof w == 'number' && Roo.isStrict && !Roo.isSafari){
+            if(Roo.isIE && (tag == 'input' || tag == 'textarea')){
+                if(tag == 'input'){
+                    return w + 2;
+                }
+                if(tag == 'textarea'){
+                    return w-2;
+                }
+            }else if(Roo.isOpera){
+                if(tag == 'input'){
+                    return w + 2;
+                }
+                if(tag == 'textarea'){
+                    return w-2;
+                }
+            }
+        }
+        return w;
+    },
+    
+    setFieldLabel : function(v)
+    {
+        if(!this.rendered){
+            return;
+        }
+        
+        if(this.indicatorEl()){
+            var ar = this.el.select('label > span',true);
+            
+            if (ar.elements.length) {
+                this.el.select('label > span',true).first().dom.innerHTML = (v === null || v === undefined ? '' : v);
+                this.fieldLabel = v;
+                return;
+            }
+            
+            var br = this.el.select('label',true);
+            
+            if(br.elements.length) {
+                this.el.select('label',true).first().dom.innerHTML = (v === null || v === undefined ? '' : v);
+                this.fieldLabel = v;
+                return;
+            }
+            
+            Roo.log('Cannot Found any of label > span || label in input');
+            return;
+        }
+        
+        this.el.select('label',true).first().dom.innerHTML = (v === null || v === undefined ? '' : v);
+        this.fieldLabel = v;
+        
+        
+    }
+});
+