From: Alan Knowles Date: Fri, 30 Jul 2021 07:09:25 +0000 (+0800) Subject: MOVED Roo/bootstrap/DateField.js to Roo/bootstrap/form/DateField.js X-Git-Url: http://git.roojs.org/?p=roojs1;a=commitdiff_plain;h=c5e6c0b27e0c1aa40d2802a986eb8697f5af9b72 MOVED Roo/bootstrap/DateField.js to Roo/bootstrap/form/DateField.js 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 --- diff --git a/Roo/bootstrap/form/DateField.js b/Roo/bootstrap/form/DateField.js new file mode 100644 index 0000000000..ba18ac35df --- /dev/null +++ b/Roo/bootstrap/form/DateField.js @@ -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: ' ' + }) + } + + 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= 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: '' + }, + { + tag: 'th', + cls: 'switch', + colspan: '5' + }, + { + tag: 'th', + cls: 'next', + html: '' + } + + ] + } + ] + }, + + 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 index 0000000000..421a119371 --- /dev/null +++ b/Roo/bootstrap/form/DateSplitField.js @@ -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 : '
{value}
', + 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 : '', + 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 : '
{value}
', + 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 index 0000000000..c67994148b --- /dev/null +++ b/Roo/bootstrap/form/Form.js @@ -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): + *
+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)
+     * 
+ * @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 index 0000000000..d02460329d --- /dev/null +++ b/Roo/bootstrap/form/Input.js @@ -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; + + + } +}); + +