X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/25ef3491bd9ae007ff1fc2b0d7943e6eaaccf775..6e39d509471fe9b4e2660e0d1631b350d0c66f40:/src/widgets/DatePicker.js diff --git a/src/widgets/DatePicker.js b/src/widgets/DatePicker.js index e3c3b3f4..d2d830d3 100644 --- a/src/widgets/DatePicker.js +++ b/src/widgets/DatePicker.js @@ -1,779 +1,792 @@ /*! - * Ext JS Library 3.0.3 + * Ext JS Library 3.1.0 * Copyright(c) 2006-2009 Ext JS, LLC * licensing@extjs.com * http://www.extjs.com/license */ -/** - * @class Ext.DatePicker - * @extends Ext.Component - * Simple date picker class. - * @constructor - * Create a new DatePicker - * @param {Object} config The config object - * @xtype datepicker - */ -Ext.DatePicker = Ext.extend(Ext.BoxComponent, { - /** - * @cfg {String} todayText - * The text to display on the button that selects the current date (defaults to 'Today') - */ - todayText : 'Today', - /** - * @cfg {String} okText - * The text to display on the ok button (defaults to ' OK ' to give the user extra clicking room) - */ - okText : ' OK ', - /** - * @cfg {String} cancelText - * The text to display on the cancel button (defaults to 'Cancel') - */ - cancelText : 'Cancel', - /** - * @cfg {Function} handler - * Optional. A function that will handle the select event of this picker. - * The handler is passed the following parameters:
- */ - /** - * @cfg {Object} scope - * The scope (this reference) in which the {@link #handler} - * function will be called. Defaults to this DatePicker instance. - */ - /** - * @cfg {String} todayTip - * The tooltip to display for the button that selects the current date (defaults to '{current date} (Spacebar)') - */ - todayTip : '{0} (Spacebar)', - /** - * @cfg {String} minText - * The error text to display if the minDate validation fails (defaults to 'This date is before the minimum date') - */ - minText : 'This date is before the minimum date', - /** - * @cfg {String} maxText - * The error text to display if the maxDate validation fails (defaults to 'This date is after the maximum date') - */ - maxText : 'This date is after the maximum date', - /** - * @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} disabledDaysText - * The tooltip to display when the date falls on a disabled day (defaults to 'Disabled') - */ - disabledDaysText : 'Disabled', - /** - * @cfg {String} disabledDatesText - * The tooltip text to display when the date falls on a disabled date (defaults to 'Disabled') - */ - disabledDatesText : 'Disabled', - /** - * @cfg {Array} monthNames - * An array of textual month names which can be overriden for localization support (defaults to Date.monthNames) - */ - monthNames : Date.monthNames, - /** - * @cfg {Array} dayNames - * An array of textual day names which can be overriden for localization support (defaults to Date.dayNames) - */ - dayNames : Date.dayNames, - /** - * @cfg {String} nextText - * The next month navigation button tooltip (defaults to 'Next Month (Control+Right)') - */ - nextText : 'Next Month (Control+Right)', - /** - * @cfg {String} prevText - * The previous month navigation button tooltip (defaults to 'Previous Month (Control+Left)') - */ - prevText : 'Previous Month (Control+Left)', - /** - * @cfg {String} monthYearText - * The header month selector tooltip (defaults to 'Choose a month (Control+Up/Down to move years)') - */ - monthYearText : 'Choose a month (Control+Up/Down to move years)', - /** - * @cfg {Number} startDay - * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday) - */ - startDay : 0, - /** - * @cfg {Boolean} showToday - * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar - * that selects the current date (defaults to true). - */ - showToday : true, - /** - * @cfg {Date} minDate - * Minimum allowable date (JavaScript date object, defaults to null) - */ - /** - * @cfg {Date} maxDate - * Maximum allowable date (JavaScript date object, defaults to null) - */ - /** - * @cfg {Array} disabledDays - * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null). - */ - /** - * @cfg {RegExp} disabledDatesRE - * JavaScript regular expression used to disable a pattern of dates (defaults to null). The {@link #disabledDates} - * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the - * disabledDates value. - */ - /** - * @cfg {Array} disabledDates - * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular - * expression so they are very powerful. Some examples: - * - * Note that the format of the dates included in the array should exactly match the {@link #format} config. - * In order to support regular expressions, if you are using a date format that has '.' in it, you will have to - * escape the dot when restricting dates. For example: ['03\\.08\\.03']. - */ - - // private - initComponent : function(){ - Ext.DatePicker.superclass.initComponent.call(this); - - this.value = this.value ? - this.value.clearTime(true) : new Date().clearTime(); - - this.addEvents( - /** - * @event select - * Fires when a date is selected - * @param {DatePicker} this - * @param {Date} date The selected date - */ - 'select' - ); - - if(this.handler){ - this.on('select', this.handler, this.scope || this); - } - - this.initDisabledDays(); - }, - - // private - initDisabledDays : function(){ - if(!this.disabledDatesRE && this.disabledDates){ - var dd = this.disabledDates, - len = dd.length - 1, - re = '(?:'; - - Ext.each(dd, function(d, i){ - re += Ext.isDate(d) ? '^' + Ext.escapeRe(d.dateFormat(this.format)) + '$' : dd[i]; - if(i != len){ - re += '|'; - } - }, this); - this.disabledDatesRE = new RegExp(re + ')'); - } - }, - - /** - * Replaces any existing disabled dates with new values and refreshes the DatePicker. - * @param {Array/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config - * for details on supported values), or a JavaScript regular expression used to disable a pattern of dates. - */ - setDisabledDates : function(dd){ - if(Ext.isArray(dd)){ - this.disabledDates = dd; - this.disabledDatesRE = null; - }else{ - this.disabledDatesRE = dd; - } - this.initDisabledDays(); - this.update(this.value, true); - }, - - /** - * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker. - * @param {Array} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config - * for details on supported values. - */ - setDisabledDays : function(dd){ - this.disabledDays = dd; - this.update(this.value, true); - }, - - /** - * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker. - * @param {Date} value The minimum date that can be selected - */ - setMinDate : function(dt){ - this.minDate = dt; - this.update(this.value, true); - }, - - /** - * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker. - * @param {Date} value The maximum date that can be selected - */ - setMaxDate : function(dt){ - this.maxDate = dt; - this.update(this.value, true); - }, - - /** - * Sets the value of the date field - * @param {Date} value The date to set - */ - setValue : function(value){ - this.value = value.clearTime(true); - this.update(this.value); - }, - - /** - * Gets the current selected value of the date field - * @return {Date} The selected date - */ - getValue : function(){ - return this.value; - }, - - // private - focus : function(){ - this.update(this.activeDate); - }, - - // private - onEnable: function(initial){ - Ext.DatePicker.superclass.onEnable.call(this); - this.doDisabled(false); - this.update(initial ? this.value : this.activeDate); - if(Ext.isIE){ - this.el.repaint(); - } - - }, - - // private - onDisable : function(){ - Ext.DatePicker.superclass.onDisable.call(this); - this.doDisabled(true); - if(Ext.isIE && !Ext.isIE8){ - /* Really strange problem in IE6/7, when disabled, have to explicitly - * repaint each of the nodes to get them to display correctly, simply - * calling repaint on the main element doesn't appear to be enough. - */ - Ext.each([].concat(this.textNodes, this.el.query('th span')), function(el){ - Ext.fly(el).repaint(); - }); - } - }, - - // private - doDisabled : function(disabled){ - this.keyNav.setDisabled(disabled); - this.prevRepeater.setDisabled(disabled); - this.nextRepeater.setDisabled(disabled); - if(this.showToday){ - this.todayKeyListener.setDisabled(disabled); - this.todayBtn.setDisabled(disabled); - } - }, - - // private - onRender : function(container, position){ - var m = [ - '', - '', - '', - this.showToday ? '' : '', - '
  
'], - dn = this.dayNames, - i; - for(i = 0; i < 7; i++){ - var d = this.startDay+i; - if(d > 6){ - d = d-7; - } - m.push(''); - } - m[m.length] = ''; - for(i = 0; i < 42; i++) { - if(i % 7 === 0 && i !== 0){ - m[m.length] = ''; - } - m[m.length] = ''; - } - m.push('
', dn[d].substr(0,1), '
'); - - var el = document.createElement('div'); - el.className = 'x-date-picker'; - el.innerHTML = m.join(''); - - container.dom.insertBefore(el, position); - - this.el = Ext.get(el); - this.eventEl = Ext.get(el.firstChild); - - this.prevRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-left a'), { - handler: this.showPrevMonth, - scope: this, - preventDefault:true, - stopDefault:true - }); - - this.nextRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-right a'), { - handler: this.showNextMonth, - scope: this, - preventDefault:true, - stopDefault:true - }); - - this.monthPicker = this.el.down('div.x-date-mp'); - this.monthPicker.enableDisplayMode('block'); - - this.keyNav = new Ext.KeyNav(this.eventEl, { - 'left' : function(e){ - if(e.ctrlKey){ - this.showPrevMonth(); - }else{ - this.update(this.activeDate.add('d', -1)); - } - }, - - 'right' : function(e){ - if(e.ctrlKey){ - this.showNextMonth(); - }else{ - this.update(this.activeDate.add('d', 1)); - } - }, - - 'up' : function(e){ - if(e.ctrlKey){ - this.showNextYear(); - }else{ - this.update(this.activeDate.add('d', -7)); - } - }, - - 'down' : function(e){ - if(e.ctrlKey){ - this.showPrevYear(); - }else{ - this.update(this.activeDate.add('d', 7)); - } - }, - - 'pageUp' : function(e){ - this.showNextMonth(); - }, - - 'pageDown' : function(e){ - this.showPrevMonth(); - }, - - 'enter' : function(e){ - e.stopPropagation(); - return true; - }, - - scope : this - }); - - this.el.unselectable(); - - this.cells = this.el.select('table.x-date-inner tbody td'); - this.textNodes = this.el.query('table.x-date-inner tbody span'); - - this.mbtn = new Ext.Button({ - text: ' ', - tooltip: this.monthYearText, - renderTo: this.el.child('td.x-date-middle', true) - }); - this.mbtn.el.child('em').addClass('x-btn-arrow'); - - if(this.showToday){ - this.todayKeyListener = this.eventEl.addKeyListener(Ext.EventObject.SPACE, this.selectToday, this); - var today = (new Date()).dateFormat(this.format); - this.todayBtn = new Ext.Button({ - renderTo: this.el.child('td.x-date-bottom', true), - text: String.format(this.todayText, today), - tooltip: String.format(this.todayTip, today), - handler: this.selectToday, - scope: this - }); - } - this.mon(this.eventEl, 'mousewheel', this.handleMouseWheel, this); - this.mon(this.eventEl, 'click', this.handleDateClick, this, {delegate: 'a.x-date-date'}); - this.mon(this.mbtn, 'click', this.showMonthPicker, this); - this.onEnable(true); - }, - - // private - createMonthPicker : function(){ - if(!this.monthPicker.dom.firstChild){ - var buf = ['']; - for(var i = 0; i < 6; i++){ - buf.push( - '', - '', - i === 0 ? - '' : - '' - ); - } - buf.push( - '', - '
', Date.getShortMonthName(i), '', Date.getShortMonthName(i + 6), '
' - ); - this.monthPicker.update(buf.join('')); - - this.mon(this.monthPicker, 'click', this.onMonthClick, this); - this.mon(this.monthPicker, 'dblclick', this.onMonthDblClick, this); - - this.mpMonths = this.monthPicker.select('td.x-date-mp-month'); - this.mpYears = this.monthPicker.select('td.x-date-mp-year'); - - this.mpMonths.each(function(m, a, i){ - i += 1; - if((i%2) === 0){ - m.dom.xmonth = 5 + Math.round(i * 0.5); - }else{ - m.dom.xmonth = Math.round((i-1) * 0.5); - } - }); - } - }, - - // private - showMonthPicker : function(){ - if(!this.disabled){ - this.createMonthPicker(); - var size = this.el.getSize(); - this.monthPicker.setSize(size); - this.monthPicker.child('table').setSize(size); - - this.mpSelMonth = (this.activeDate || this.value).getMonth(); - this.updateMPMonth(this.mpSelMonth); - this.mpSelYear = (this.activeDate || this.value).getFullYear(); - this.updateMPYear(this.mpSelYear); - - this.monthPicker.slideIn('t', {duration:0.2}); - } - }, - - // private - updateMPYear : function(y){ - this.mpyear = y; - var ys = this.mpYears.elements; - for(var i = 1; i <= 10; i++){ - var td = ys[i-1], y2; - if((i%2) === 0){ - y2 = y + Math.round(i * 0.5); - td.firstChild.innerHTML = y2; - td.xyear = y2; - }else{ - y2 = y - (5-Math.round(i * 0.5)); - td.firstChild.innerHTML = y2; - td.xyear = y2; - } - this.mpYears.item(i-1)[y2 == this.mpSelYear ? 'addClass' : 'removeClass']('x-date-mp-sel'); - } - }, - - // private - updateMPMonth : function(sm){ - this.mpMonths.each(function(m, a, i){ - m[m.dom.xmonth == sm ? 'addClass' : 'removeClass']('x-date-mp-sel'); - }); - }, - - // private - selectMPMonth : function(m){ - - }, - - // private - onMonthClick : function(e, t){ - e.stopEvent(); - var el = new Ext.Element(t), pn; - if(el.is('button.x-date-mp-cancel')){ - this.hideMonthPicker(); - } - else if(el.is('button.x-date-mp-ok')){ - var d = new Date(this.mpSelYear, this.mpSelMonth, (this.activeDate || this.value).getDate()); - if(d.getMonth() != this.mpSelMonth){ - // 'fix' the JS rolling date conversion if needed - d = new Date(this.mpSelYear, this.mpSelMonth, 1).getLastDateOfMonth(); - } - this.update(d); - this.hideMonthPicker(); - } - else if((pn = el.up('td.x-date-mp-month', 2))){ - this.mpMonths.removeClass('x-date-mp-sel'); - pn.addClass('x-date-mp-sel'); - this.mpSelMonth = pn.dom.xmonth; - } - else if((pn = el.up('td.x-date-mp-year', 2))){ - this.mpYears.removeClass('x-date-mp-sel'); - pn.addClass('x-date-mp-sel'); - this.mpSelYear = pn.dom.xyear; - } - else if(el.is('a.x-date-mp-prev')){ - this.updateMPYear(this.mpyear-10); - } - else if(el.is('a.x-date-mp-next')){ - this.updateMPYear(this.mpyear+10); - } - }, - - // private - onMonthDblClick : function(e, t){ - e.stopEvent(); - var el = new Ext.Element(t), pn; - if((pn = el.up('td.x-date-mp-month', 2))){ - this.update(new Date(this.mpSelYear, pn.dom.xmonth, (this.activeDate || this.value).getDate())); - this.hideMonthPicker(); - } - else if((pn = el.up('td.x-date-mp-year', 2))){ - this.update(new Date(pn.dom.xyear, this.mpSelMonth, (this.activeDate || this.value).getDate())); - this.hideMonthPicker(); - } - }, - - // private - hideMonthPicker : function(disableAnim){ - if(this.monthPicker){ - if(disableAnim === true){ - this.monthPicker.hide(); - }else{ - this.monthPicker.slideOut('t', {duration:0.2}); - } - } - }, - - // private - showPrevMonth : function(e){ - this.update(this.activeDate.add('mo', -1)); - }, - - // private - showNextMonth : function(e){ - this.update(this.activeDate.add('mo', 1)); - }, - - // private - showPrevYear : function(){ - this.update(this.activeDate.add('y', -1)); - }, - - // private - showNextYear : function(){ - this.update(this.activeDate.add('y', 1)); - }, - - // private - handleMouseWheel : function(e){ - e.stopEvent(); - if(!this.disabled){ - var delta = e.getWheelDelta(); - if(delta > 0){ - this.showPrevMonth(); - } else if(delta < 0){ - this.showNextMonth(); - } - } - }, - - // private - handleDateClick : function(e, t){ - e.stopEvent(); - if(!this.disabled && t.dateValue && !Ext.fly(t.parentNode).hasClass('x-date-disabled')){ - this.setValue(new Date(t.dateValue)); - this.fireEvent('select', this, this.value); - } - }, - - // private - selectToday : function(){ - if(this.todayBtn && !this.todayBtn.disabled){ - this.setValue(new Date().clearTime()); - this.fireEvent('select', this, this.value); - } - }, - - // private - update : function(date, forceRefresh){ - if(this.rendered){ - var vd = this.activeDate, vis = this.isVisible(); - this.activeDate = date; - if(!forceRefresh && vd && this.el){ - var t = date.getTime(); - if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){ - this.cells.removeClass('x-date-selected'); - this.cells.each(function(c){ - if(c.dom.firstChild.dateValue == t){ - c.addClass('x-date-selected'); - if(vis){ - Ext.fly(c.dom.firstChild).focus(50); - } - return false; - } - }); - return; - } - } - var days = date.getDaysInMonth(), - firstOfMonth = date.getFirstDateOfMonth(), - startingPos = firstOfMonth.getDay()-this.startDay; - - if(startingPos < 0){ - startingPos += 7; - } - days += startingPos; - - var pm = date.add('mo', -1), - prevStart = pm.getDaysInMonth()-startingPos, - cells = this.cells.elements, - textEls = this.textNodes, - // convert everything to numbers so it's fast - day = 86400000, - d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime(), - today = new Date().clearTime().getTime(), - sel = date.clearTime(true).getTime(), - min = this.minDate ? this.minDate.clearTime(true) : Number.NEGATIVE_INFINITY, - max = this.maxDate ? this.maxDate.clearTime(true) : Number.POSITIVE_INFINITY, - ddMatch = this.disabledDatesRE, - ddText = this.disabledDatesText, - ddays = this.disabledDays ? this.disabledDays.join('') : false, - ddaysText = this.disabledDaysText, - format = this.format; - - if(this.showToday){ - var td = new Date().clearTime(), - disable = (td < min || td > max || - (ddMatch && format && ddMatch.test(td.dateFormat(format))) || - (ddays && ddays.indexOf(td.getDay()) != -1)); - - if(!this.disabled){ - this.todayBtn.setDisabled(disable); - this.todayKeyListener[disable ? 'disable' : 'enable'](); - } - } - - var setCellClass = function(cal, cell){ - cell.title = ''; - var t = d.getTime(); - cell.firstChild.dateValue = t; - if(t == today){ - cell.className += ' x-date-today'; - cell.title = cal.todayText; - } - if(t == sel){ - cell.className += ' x-date-selected'; - if(vis){ - Ext.fly(cell.firstChild).focus(50); - } - } - // disabling - if(t < min) { - cell.className = ' x-date-disabled'; - cell.title = cal.minText; - return; - } - if(t > max) { - cell.className = ' x-date-disabled'; - cell.title = cal.maxText; - return; - } - if(ddays){ - if(ddays.indexOf(d.getDay()) != -1){ - cell.title = ddaysText; - cell.className = ' x-date-disabled'; - } - } - if(ddMatch && format){ - var fvalue = d.dateFormat(format); - if(ddMatch.test(fvalue)){ - cell.title = ddText.replace('%0', fvalue); - cell.className = ' x-date-disabled'; - } - } - }; - - var i = 0; - for(; i < startingPos; i++) { - textEls[i].innerHTML = (++prevStart); - d.setDate(d.getDate()+1); - cells[i].className = 'x-date-prevday'; - setCellClass(this, cells[i]); - } - for(; i < days; i++){ - var intDay = i - startingPos + 1; - textEls[i].innerHTML = (intDay); - d.setDate(d.getDate()+1); - cells[i].className = 'x-date-active'; - setCellClass(this, cells[i]); - } - var extraDays = 0; - for(; i < 42; i++) { - textEls[i].innerHTML = (++extraDays); - d.setDate(d.getDate()+1); - cells[i].className = 'x-date-nextday'; - setCellClass(this, cells[i]); - } - - this.mbtn.setText(this.monthNames[date.getMonth()] + ' ' + date.getFullYear()); - - if(!this.internalRender){ - var main = this.el.dom.firstChild, - w = main.offsetWidth; - this.el.setWidth(w + this.el.getBorderWidth('lr')); - Ext.fly(main).setWidth(w); - this.internalRender = true; - // opera does not respect the auto grow header center column - // then, after it gets a width opera refuses to recalculate - // without a second pass - if(Ext.isOpera && !this.secondPass){ - main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + 'px'; - this.secondPass = true; - this.update.defer(10, this, [date]); - } - } - } - }, - - // private - beforeDestroy : function() { - if(this.rendered){ - this.keyNav.disable(); - this.keyNav = null; - Ext.destroy( - this.leftClickRpt, - this.rightClickRpt, - this.monthPicker, - this.eventEl, - this.mbtn, - this.todayBtn - ); - } - } - - /** - * @cfg {String} autoEl @hide - */ -}); - -Ext.reg('datepicker', Ext.DatePicker); +/** + * @class Ext.DatePicker + * @extends Ext.Component + *

A popup date picker. This class is used by the {@link Ext.form.DateField DateField} class + * to allow browsing and selection of valid dates.

+ *

All the string values documented below may be overridden by including an Ext locale file in + * your page.

+ * @constructor + * Create a new DatePicker + * @param {Object} config The config object + * @xtype datepicker + */ +Ext.DatePicker = Ext.extend(Ext.BoxComponent, { + /** + * @cfg {String} todayText + * The text to display on the button that selects the current date (defaults to 'Today') + */ + todayText : 'Today', + /** + * @cfg {String} okText + * The text to display on the ok button (defaults to ' OK ' to give the user extra clicking room) + */ + okText : ' OK ', + /** + * @cfg {String} cancelText + * The text to display on the cancel button (defaults to 'Cancel') + */ + cancelText : 'Cancel', + /** + * @cfg {Function} handler + * Optional. A function that will handle the select event of this picker. + * The handler is passed the following parameters:
+ */ + /** + * @cfg {Object} scope + * The scope (this reference) in which the {@link #handler} + * function will be called. Defaults to this DatePicker instance. + */ + /** + * @cfg {String} todayTip + * A string used to format the message for displaying in a tooltip over the button that + * selects the current date. Defaults to '{0} (Spacebar)' where + * the {0} token is replaced by today's date. + */ + todayTip : '{0} (Spacebar)', + /** + * @cfg {String} minText + * The error text to display if the minDate validation fails (defaults to 'This date is before the minimum date') + */ + minText : 'This date is before the minimum date', + /** + * @cfg {String} maxText + * The error text to display if the maxDate validation fails (defaults to 'This date is after the maximum date') + */ + maxText : 'This date is after the maximum date', + /** + * @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} disabledDaysText + * The tooltip to display when the date falls on a disabled day (defaults to 'Disabled') + */ + disabledDaysText : 'Disabled', + /** + * @cfg {String} disabledDatesText + * The tooltip text to display when the date falls on a disabled date (defaults to 'Disabled') + */ + disabledDatesText : 'Disabled', + /** + * @cfg {Array} monthNames + * An array of textual month names which can be overriden for localization support (defaults to Date.monthNames) + */ + monthNames : Date.monthNames, + /** + * @cfg {Array} dayNames + * An array of textual day names which can be overriden for localization support (defaults to Date.dayNames) + */ + dayNames : Date.dayNames, + /** + * @cfg {String} nextText + * The next month navigation button tooltip (defaults to 'Next Month (Control+Right)') + */ + nextText : 'Next Month (Control+Right)', + /** + * @cfg {String} prevText + * The previous month navigation button tooltip (defaults to 'Previous Month (Control+Left)') + */ + prevText : 'Previous Month (Control+Left)', + /** + * @cfg {String} monthYearText + * The header month selector tooltip (defaults to 'Choose a month (Control+Up/Down to move years)') + */ + monthYearText : 'Choose a month (Control+Up/Down to move years)', + /** + * @cfg {Number} startDay + * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday) + */ + startDay : 0, + /** + * @cfg {Boolean} showToday + * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar + * that selects the current date (defaults to true). + */ + showToday : true, + /** + * @cfg {Date} minDate + * Minimum allowable date (JavaScript date object, defaults to null) + */ + /** + * @cfg {Date} maxDate + * Maximum allowable date (JavaScript date object, defaults to null) + */ + /** + * @cfg {Array} disabledDays + * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null). + */ + /** + * @cfg {RegExp} disabledDatesRE + * JavaScript regular expression used to disable a pattern of dates (defaults to null). The {@link #disabledDates} + * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the + * disabledDates value. + */ + /** + * @cfg {Array} disabledDates + * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular + * expression so they are very powerful. Some examples: + * + * Note that the format of the dates included in the array should exactly match the {@link #format} config. + * In order to support regular expressions, if you are using a date format that has '.' in it, you will have to + * escape the dot when restricting dates. For example: ['03\\.08\\.03']. + */ + + // private + // Set by other components to stop the picker focus being updated when the value changes. + focusOnSelect: true, + + // private + initComponent : function(){ + Ext.DatePicker.superclass.initComponent.call(this); + + this.value = this.value ? + this.value.clearTime(true) : new Date().clearTime(); + + this.addEvents( + /** + * @event select + * Fires when a date is selected + * @param {DatePicker} this DatePicker + * @param {Date} date The selected date + */ + 'select' + ); + + if(this.handler){ + this.on('select', this.handler, this.scope || this); + } + + this.initDisabledDays(); + }, + + // private + initDisabledDays : function(){ + if(!this.disabledDatesRE && this.disabledDates){ + var dd = this.disabledDates, + len = dd.length - 1, + re = '(?:'; + + Ext.each(dd, function(d, i){ + re += Ext.isDate(d) ? '^' + Ext.escapeRe(d.dateFormat(this.format)) + '$' : dd[i]; + if(i != len){ + re += '|'; + } + }, this); + this.disabledDatesRE = new RegExp(re + ')'); + } + }, + + /** + * Replaces any existing disabled dates with new values and refreshes the DatePicker. + * @param {Array/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config + * for details on supported values), or a JavaScript regular expression used to disable a pattern of dates. + */ + setDisabledDates : function(dd){ + if(Ext.isArray(dd)){ + this.disabledDates = dd; + this.disabledDatesRE = null; + }else{ + this.disabledDatesRE = dd; + } + this.initDisabledDays(); + this.update(this.value, true); + }, + + /** + * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker. + * @param {Array} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config + * for details on supported values. + */ + setDisabledDays : function(dd){ + this.disabledDays = dd; + this.update(this.value, true); + }, + + /** + * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker. + * @param {Date} value The minimum date that can be selected + */ + setMinDate : function(dt){ + this.minDate = dt; + this.update(this.value, true); + }, + + /** + * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker. + * @param {Date} value The maximum date that can be selected + */ + setMaxDate : function(dt){ + this.maxDate = dt; + this.update(this.value, true); + }, + + /** + * Sets the value of the date field + * @param {Date} value The date to set + */ + setValue : function(value){ + this.value = value.clearTime(true); + this.update(this.value); + }, + + /** + * Gets the current selected value of the date field + * @return {Date} The selected date + */ + getValue : function(){ + return this.value; + }, + + // private + focus : function(){ + this.update(this.activeDate); + }, + + // private + onEnable: function(initial){ + Ext.DatePicker.superclass.onEnable.call(this); + this.doDisabled(false); + this.update(initial ? this.value : this.activeDate); + if(Ext.isIE){ + this.el.repaint(); + } + + }, + + // private + onDisable : function(){ + Ext.DatePicker.superclass.onDisable.call(this); + this.doDisabled(true); + if(Ext.isIE && !Ext.isIE8){ + /* Really strange problem in IE6/7, when disabled, have to explicitly + * repaint each of the nodes to get them to display correctly, simply + * calling repaint on the main element doesn't appear to be enough. + */ + Ext.each([].concat(this.textNodes, this.el.query('th span')), function(el){ + Ext.fly(el).repaint(); + }); + } + }, + + // private + doDisabled : function(disabled){ + this.keyNav.setDisabled(disabled); + this.prevRepeater.setDisabled(disabled); + this.nextRepeater.setDisabled(disabled); + if(this.showToday){ + this.todayKeyListener.setDisabled(disabled); + this.todayBtn.setDisabled(disabled); + } + }, + + // private + onRender : function(container, position){ + var m = [ + '', + '', + '', + this.showToday ? '' : '', + '
  
'], + dn = this.dayNames, + i; + for(i = 0; i < 7; i++){ + var d = this.startDay+i; + if(d > 6){ + d = d-7; + } + m.push(''); + } + m[m.length] = ''; + for(i = 0; i < 42; i++) { + if(i % 7 === 0 && i !== 0){ + m[m.length] = ''; + } + m[m.length] = ''; + } + m.push('
', dn[d].substr(0,1), '
'); + + var el = document.createElement('div'); + el.className = 'x-date-picker'; + el.innerHTML = m.join(''); + + container.dom.insertBefore(el, position); + + this.el = Ext.get(el); + this.eventEl = Ext.get(el.firstChild); + + this.prevRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-left a'), { + handler: this.showPrevMonth, + scope: this, + preventDefault:true, + stopDefault:true + }); + + this.nextRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-right a'), { + handler: this.showNextMonth, + scope: this, + preventDefault:true, + stopDefault:true + }); + + this.monthPicker = this.el.down('div.x-date-mp'); + this.monthPicker.enableDisplayMode('block'); + + this.keyNav = new Ext.KeyNav(this.eventEl, { + 'left' : function(e){ + if(e.ctrlKey){ + this.showPrevMonth(); + }else{ + this.update(this.activeDate.add('d', -1)); + } + }, + + 'right' : function(e){ + if(e.ctrlKey){ + this.showNextMonth(); + }else{ + this.update(this.activeDate.add('d', 1)); + } + }, + + 'up' : function(e){ + if(e.ctrlKey){ + this.showNextYear(); + }else{ + this.update(this.activeDate.add('d', -7)); + } + }, + + 'down' : function(e){ + if(e.ctrlKey){ + this.showPrevYear(); + }else{ + this.update(this.activeDate.add('d', 7)); + } + }, + + 'pageUp' : function(e){ + this.showNextMonth(); + }, + + 'pageDown' : function(e){ + this.showPrevMonth(); + }, + + 'enter' : function(e){ + e.stopPropagation(); + return true; + }, + + scope : this + }); + + this.el.unselectable(); + + this.cells = this.el.select('table.x-date-inner tbody td'); + this.textNodes = this.el.query('table.x-date-inner tbody span'); + + this.mbtn = new Ext.Button({ + text: ' ', + tooltip: this.monthYearText, + renderTo: this.el.child('td.x-date-middle', true) + }); + this.mbtn.el.child('em').addClass('x-btn-arrow'); + + if(this.showToday){ + this.todayKeyListener = this.eventEl.addKeyListener(Ext.EventObject.SPACE, this.selectToday, this); + var today = (new Date()).dateFormat(this.format); + this.todayBtn = new Ext.Button({ + renderTo: this.el.child('td.x-date-bottom', true), + text: String.format(this.todayText, today), + tooltip: String.format(this.todayTip, today), + handler: this.selectToday, + scope: this + }); + } + this.mon(this.eventEl, 'mousewheel', this.handleMouseWheel, this); + this.mon(this.eventEl, 'click', this.handleDateClick, this, {delegate: 'a.x-date-date'}); + this.mon(this.mbtn, 'click', this.showMonthPicker, this); + this.onEnable(true); + }, + + // private + createMonthPicker : function(){ + if(!this.monthPicker.dom.firstChild){ + var buf = ['']; + for(var i = 0; i < 6; i++){ + buf.push( + '', + '', + i === 0 ? + '' : + '' + ); + } + buf.push( + '', + '
', Date.getShortMonthName(i), '', Date.getShortMonthName(i + 6), '
' + ); + this.monthPicker.update(buf.join('')); + + this.mon(this.monthPicker, 'click', this.onMonthClick, this); + this.mon(this.monthPicker, 'dblclick', this.onMonthDblClick, this); + + this.mpMonths = this.monthPicker.select('td.x-date-mp-month'); + this.mpYears = this.monthPicker.select('td.x-date-mp-year'); + + this.mpMonths.each(function(m, a, i){ + i += 1; + if((i%2) === 0){ + m.dom.xmonth = 5 + Math.round(i * 0.5); + }else{ + m.dom.xmonth = Math.round((i-1) * 0.5); + } + }); + } + }, + + // private + showMonthPicker : function(){ + if(!this.disabled){ + this.createMonthPicker(); + var size = this.el.getSize(); + this.monthPicker.setSize(size); + this.monthPicker.child('table').setSize(size); + + this.mpSelMonth = (this.activeDate || this.value).getMonth(); + this.updateMPMonth(this.mpSelMonth); + this.mpSelYear = (this.activeDate || this.value).getFullYear(); + this.updateMPYear(this.mpSelYear); + + this.monthPicker.slideIn('t', {duration:0.2}); + } + }, + + // private + updateMPYear : function(y){ + this.mpyear = y; + var ys = this.mpYears.elements; + for(var i = 1; i <= 10; i++){ + var td = ys[i-1], y2; + if((i%2) === 0){ + y2 = y + Math.round(i * 0.5); + td.firstChild.innerHTML = y2; + td.xyear = y2; + }else{ + y2 = y - (5-Math.round(i * 0.5)); + td.firstChild.innerHTML = y2; + td.xyear = y2; + } + this.mpYears.item(i-1)[y2 == this.mpSelYear ? 'addClass' : 'removeClass']('x-date-mp-sel'); + } + }, + + // private + updateMPMonth : function(sm){ + this.mpMonths.each(function(m, a, i){ + m[m.dom.xmonth == sm ? 'addClass' : 'removeClass']('x-date-mp-sel'); + }); + }, + + // private + selectMPMonth : function(m){ + + }, + + // private + onMonthClick : function(e, t){ + e.stopEvent(); + var el = new Ext.Element(t), pn; + if(el.is('button.x-date-mp-cancel')){ + this.hideMonthPicker(); + } + else if(el.is('button.x-date-mp-ok')){ + var d = new Date(this.mpSelYear, this.mpSelMonth, (this.activeDate || this.value).getDate()); + if(d.getMonth() != this.mpSelMonth){ + // 'fix' the JS rolling date conversion if needed + d = new Date(this.mpSelYear, this.mpSelMonth, 1).getLastDateOfMonth(); + } + this.update(d); + this.hideMonthPicker(); + } + else if((pn = el.up('td.x-date-mp-month', 2))){ + this.mpMonths.removeClass('x-date-mp-sel'); + pn.addClass('x-date-mp-sel'); + this.mpSelMonth = pn.dom.xmonth; + } + else if((pn = el.up('td.x-date-mp-year', 2))){ + this.mpYears.removeClass('x-date-mp-sel'); + pn.addClass('x-date-mp-sel'); + this.mpSelYear = pn.dom.xyear; + } + else if(el.is('a.x-date-mp-prev')){ + this.updateMPYear(this.mpyear-10); + } + else if(el.is('a.x-date-mp-next')){ + this.updateMPYear(this.mpyear+10); + } + }, + + // private + onMonthDblClick : function(e, t){ + e.stopEvent(); + var el = new Ext.Element(t), pn; + if((pn = el.up('td.x-date-mp-month', 2))){ + this.update(new Date(this.mpSelYear, pn.dom.xmonth, (this.activeDate || this.value).getDate())); + this.hideMonthPicker(); + } + else if((pn = el.up('td.x-date-mp-year', 2))){ + this.update(new Date(pn.dom.xyear, this.mpSelMonth, (this.activeDate || this.value).getDate())); + this.hideMonthPicker(); + } + }, + + // private + hideMonthPicker : function(disableAnim){ + if(this.monthPicker){ + if(disableAnim === true){ + this.monthPicker.hide(); + }else{ + this.monthPicker.slideOut('t', {duration:0.2}); + } + } + }, + + // private + showPrevMonth : function(e){ + this.update(this.activeDate.add('mo', -1)); + }, + + // private + showNextMonth : function(e){ + this.update(this.activeDate.add('mo', 1)); + }, + + // private + showPrevYear : function(){ + this.update(this.activeDate.add('y', -1)); + }, + + // private + showNextYear : function(){ + this.update(this.activeDate.add('y', 1)); + }, + + // private + handleMouseWheel : function(e){ + e.stopEvent(); + if(!this.disabled){ + var delta = e.getWheelDelta(); + if(delta > 0){ + this.showPrevMonth(); + } else if(delta < 0){ + this.showNextMonth(); + } + } + }, + + // private + handleDateClick : function(e, t){ + e.stopEvent(); + if(!this.disabled && t.dateValue && !Ext.fly(t.parentNode).hasClass('x-date-disabled')){ + this.cancelFocus = this.focusOnSelect === false; + this.setValue(new Date(t.dateValue)); + delete this.cancelFocus; + this.fireEvent('select', this, this.value); + } + }, + + // private + selectToday : function(){ + if(this.todayBtn && !this.todayBtn.disabled){ + this.setValue(new Date().clearTime()); + this.fireEvent('select', this, this.value); + } + }, + + // private + update : function(date, forceRefresh){ + if(this.rendered){ + var vd = this.activeDate, vis = this.isVisible(); + this.activeDate = date; + if(!forceRefresh && vd && this.el){ + var t = date.getTime(); + if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){ + this.cells.removeClass('x-date-selected'); + this.cells.each(function(c){ + if(c.dom.firstChild.dateValue == t){ + c.addClass('x-date-selected'); + if(vis && !this.cancelFocus){ + Ext.fly(c.dom.firstChild).focus(50); + } + return false; + } + }, this); + return; + } + } + var days = date.getDaysInMonth(), + firstOfMonth = date.getFirstDateOfMonth(), + startingPos = firstOfMonth.getDay()-this.startDay; + + if(startingPos < 0){ + startingPos += 7; + } + days += startingPos; + + var pm = date.add('mo', -1), + prevStart = pm.getDaysInMonth()-startingPos, + cells = this.cells.elements, + textEls = this.textNodes, + // convert everything to numbers so it's fast + day = 86400000, + d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime(), + today = new Date().clearTime().getTime(), + sel = date.clearTime(true).getTime(), + min = this.minDate ? this.minDate.clearTime(true) : Number.NEGATIVE_INFINITY, + max = this.maxDate ? this.maxDate.clearTime(true) : Number.POSITIVE_INFINITY, + ddMatch = this.disabledDatesRE, + ddText = this.disabledDatesText, + ddays = this.disabledDays ? this.disabledDays.join('') : false, + ddaysText = this.disabledDaysText, + format = this.format; + + if(this.showToday){ + var td = new Date().clearTime(), + disable = (td < min || td > max || + (ddMatch && format && ddMatch.test(td.dateFormat(format))) || + (ddays && ddays.indexOf(td.getDay()) != -1)); + + if(!this.disabled){ + this.todayBtn.setDisabled(disable); + this.todayKeyListener[disable ? 'disable' : 'enable'](); + } + } + + var setCellClass = function(cal, cell){ + cell.title = ''; + var t = d.getTime(); + cell.firstChild.dateValue = t; + if(t == today){ + cell.className += ' x-date-today'; + cell.title = cal.todayText; + } + if(t == sel){ + cell.className += ' x-date-selected'; + if(vis){ + Ext.fly(cell.firstChild).focus(50); + } + } + // disabling + if(t < min) { + cell.className = ' x-date-disabled'; + cell.title = cal.minText; + return; + } + if(t > max) { + cell.className = ' x-date-disabled'; + cell.title = cal.maxText; + return; + } + if(ddays){ + if(ddays.indexOf(d.getDay()) != -1){ + cell.title = ddaysText; + cell.className = ' x-date-disabled'; + } + } + if(ddMatch && format){ + var fvalue = d.dateFormat(format); + if(ddMatch.test(fvalue)){ + cell.title = ddText.replace('%0', fvalue); + cell.className = ' x-date-disabled'; + } + } + }; + + var i = 0; + for(; i < startingPos; i++) { + textEls[i].innerHTML = (++prevStart); + d.setDate(d.getDate()+1); + cells[i].className = 'x-date-prevday'; + setCellClass(this, cells[i]); + } + for(; i < days; i++){ + var intDay = i - startingPos + 1; + textEls[i].innerHTML = (intDay); + d.setDate(d.getDate()+1); + cells[i].className = 'x-date-active'; + setCellClass(this, cells[i]); + } + var extraDays = 0; + for(; i < 42; i++) { + textEls[i].innerHTML = (++extraDays); + d.setDate(d.getDate()+1); + cells[i].className = 'x-date-nextday'; + setCellClass(this, cells[i]); + } + + this.mbtn.setText(this.monthNames[date.getMonth()] + ' ' + date.getFullYear()); + + if(!this.internalRender){ + var main = this.el.dom.firstChild, + w = main.offsetWidth; + this.el.setWidth(w + this.el.getBorderWidth('lr')); + Ext.fly(main).setWidth(w); + this.internalRender = true; + // opera does not respect the auto grow header center column + // then, after it gets a width opera refuses to recalculate + // without a second pass + if(Ext.isOpera && !this.secondPass){ + main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + 'px'; + this.secondPass = true; + this.update.defer(10, this, [date]); + } + } + } + }, + + // private + beforeDestroy : function() { + if(this.rendered){ + Ext.destroy( + this.keyNav, + this.monthPicker, + this.eventEl, + this.mbtn, + this.nextRepeater, + this.prevRepeater, + this.cells.el, + this.todayBtn + ); + delete this.textNodes; + delete this.cells.elements; + } + } + + /** + * @cfg {String} autoEl @hide + */ +}); + +Ext.reg('datepicker', Ext.DatePicker);