linuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacos
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1329 lines
40 KiB
1329 lines
40 KiB
/** |
|
* A date picker. This class is used by the Ext.form.field.Date field to allow browsing and selection of valid |
|
* dates in a popup next to the field, but may also be used with other components. |
|
* |
|
* Typically you will need to implement a handler function to be notified when the user chooses a date from the picker; |
|
* you can register the handler using the {@link #select} event, or by implementing the {@link #handler} method. |
|
* |
|
* By default the user will be allowed to pick any date; this can be changed by using the {@link #minDate}, |
|
* {@link #maxDate}, {@link #disabledDays}, {@link #disabledDatesRE}, and/or {@link #disabledDates} configs. |
|
* |
|
* All the string values documented below may be overridden by including an Ext locale file in your page. |
|
* |
|
* @example |
|
* Ext.create('Ext.panel.Panel', { |
|
* title: 'Choose a future date:', |
|
* width: 200, |
|
* bodyPadding: 10, |
|
* renderTo: Ext.getBody(), |
|
* items: [{ |
|
* xtype: 'datepicker', |
|
* minDate: new Date(), |
|
* handler: function(picker, date) { |
|
* // do something with the selected date |
|
* } |
|
* }] |
|
* }); |
|
*/ |
|
Ext.define('Ext.picker.Date', { |
|
extend: 'Ext.Component', |
|
requires: [ |
|
'Ext.XTemplate', |
|
'Ext.button.Button', |
|
'Ext.button.Split', |
|
'Ext.util.ClickRepeater', |
|
'Ext.util.KeyNav', |
|
'Ext.fx.Manager', |
|
'Ext.picker.Month' |
|
], |
|
alias: 'widget.datepicker', |
|
alternateClassName: 'Ext.DatePicker', |
|
|
|
isDatePicker: true, |
|
|
|
focusable: true, |
|
|
|
childEls: [ |
|
'innerEl', 'eventEl', 'prevEl', 'nextEl', 'middleBtnEl', 'footerEl' |
|
], |
|
|
|
border: true, |
|
|
|
/** |
|
* @cfg |
|
* @inheritdoc |
|
*/ |
|
renderTpl: [ |
|
'<div id="{id}-innerEl" data-ref="innerEl">', |
|
'<div class="{baseCls}-header">', |
|
'<div id="{id}-prevEl" data-ref="prevEl" class="{baseCls}-prev {baseCls}-arrow" role="button" title="{prevText}"></div>', |
|
'<div id="{id}-middleBtnEl" data-ref="middleBtnEl" class="{baseCls}-month" role="heading">{%this.renderMonthBtn(values, out)%}</div>', |
|
'<div id="{id}-nextEl" data-ref="nextEl" class="{baseCls}-next {baseCls}-arrow" role="button" title="{nextText}"></div>', |
|
'</div>', |
|
'<table role="grid" id="{id}-eventEl" data-ref="eventEl" class="{baseCls}-inner" {%', |
|
// If the DatePicker is focusable, make its eventEl tabbable. |
|
// Note that we're looking at the `focusable` property because |
|
// calling `isFocusable()` will always return false at that point |
|
// as the picker is not yet rendered. |
|
'if (values.$comp.focusable) {out.push("tabindex=\\\"0\\\"");}', |
|
'%} cellspacing="0">', |
|
'<thead><tr role="row">', |
|
'<tpl for="dayNames">', |
|
'<th role="columnheader" class="{parent.baseCls}-column-header" aria-label="{.}">', |
|
'<div role="presentation" class="{parent.baseCls}-column-header-inner">{.:this.firstInitial}</div>', |
|
'</th>', |
|
'</tpl>', |
|
'</tr></thead>', |
|
'<tbody><tr role="row">', |
|
'<tpl for="days">', |
|
'{#:this.isEndOfWeek}', |
|
'<td role="gridcell">', |
|
'<div hidefocus="on" class="{parent.baseCls}-date"></div>', |
|
'</td>', |
|
'</tpl>', |
|
'</tr></tbody>', |
|
'</table>', |
|
'<tpl if="showToday">', |
|
'<div id="{id}-footerEl" data-ref="footerEl" role="presentation" class="{baseCls}-footer">{%this.renderTodayBtn(values, out)%}</div>', |
|
'</tpl>', |
|
'</div>', |
|
{ |
|
firstInitial: function(value) { |
|
return Ext.picker.Date.prototype.getDayInitial(value); |
|
}, |
|
isEndOfWeek: function(value) { |
|
// convert from 1 based index to 0 based |
|
// by decrementing value once. |
|
value--; |
|
var end = value % 7 === 0 && value !== 0; |
|
return end ? '</tr><tr role="row">' : ''; |
|
}, |
|
renderTodayBtn: function(values, out) { |
|
Ext.DomHelper.generateMarkup(values.$comp.todayBtn.getRenderTree(), out); |
|
}, |
|
renderMonthBtn: function(values, out) { |
|
Ext.DomHelper.generateMarkup(values.$comp.monthBtn.getRenderTree(), out); |
|
} |
|
} |
|
], |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} todayText |
|
* The text to display on the button that selects the current date |
|
*/ |
|
todayText: 'Today', |
|
//</locale> |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} ariaTitle |
|
* The text to display for the aria title |
|
*/ |
|
ariaTitle: 'Date Picker: {0}', |
|
//</locale> |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} ariaTitleDateFormat |
|
* The date format to display for the current value in the {@link #ariaTitle} |
|
*/ |
|
ariaTitleDateFormat: 'F d', |
|
//</locale> |
|
|
|
/** |
|
* @cfg {Function} handler |
|
* Optional. A function that will handle the select event of this picker. The handler is passed the following |
|
* parameters: |
|
* |
|
* - `picker` : Ext.picker.Date |
|
* |
|
* This Date picker. |
|
* |
|
* - `date` : Date |
|
* |
|
* The selected date. |
|
*/ |
|
|
|
/** |
|
* @cfg {Object} scope |
|
* The scope (`this` reference) in which the `{@link #handler}` function will be called. |
|
* |
|
* Defaults to this DatePicker instance. |
|
*/ |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} todayTip |
|
* A string used to format the message for displaying in a tooltip over the button that selects the current date. |
|
* The `{0}` token in string is replaced by today's date. |
|
*/ |
|
todayTip: '{0} (Spacebar)', |
|
//</locale> |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} minText |
|
* The error text to display if the minDate validation fails. |
|
*/ |
|
minText: 'This date is before the minimum date', |
|
//</locale> |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} maxText |
|
* The error text to display if the maxDate validation fails. |
|
*/ |
|
maxText: 'This date is after the maximum date', |
|
//</locale> |
|
|
|
/** |
|
* @cfg {String} format |
|
* The default date format string which can be overriden for localization support. The format must be valid |
|
* according to {@link Ext.Date#parse} (defaults to {@link Ext.Date#defaultFormat}). |
|
*/ |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} disabledDaysText |
|
* The tooltip to display when the date falls on a disabled day. |
|
*/ |
|
disabledDaysText: 'Disabled', |
|
//</locale> |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} disabledDatesText |
|
* The tooltip text to display when the date falls on a disabled date. |
|
*/ |
|
disabledDatesText: 'Disabled', |
|
//</locale> |
|
|
|
/** |
|
* @cfg {String[]} monthNames |
|
* An array of textual month names which can be overriden for localization support (defaults to Ext.Date.monthNames) |
|
* @deprecated This config is deprecated. In future the month names will be retrieved from {@link Ext.Date} |
|
*/ |
|
|
|
/** |
|
* @cfg {String[]} dayNames |
|
* An array of textual day names which can be overriden for localization support (defaults to Ext.Date.dayNames) |
|
* @deprecated This config is deprecated. In future the day names will be retrieved from {@link Ext.Date} |
|
*/ |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} nextText |
|
* The next month navigation button tooltip |
|
*/ |
|
nextText: 'Next Month (Control+Right)', |
|
//</locale> |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} prevText |
|
* The previous month navigation button tooltip |
|
*/ |
|
prevText: 'Previous Month (Control+Left)', |
|
//</locale> |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} monthYearText |
|
* The header month selector tooltip |
|
*/ |
|
monthYearText: 'Choose a month (Control+Up/Down to move years)', |
|
//</locale> |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} monthYearFormat |
|
* The date format for the header month |
|
*/ |
|
monthYearFormat: 'F Y', |
|
//</locale> |
|
|
|
//<locale> |
|
/** |
|
* @cfg {Number} [startDay=undefined] |
|
* Day index at which the week should begin, 0-based. |
|
* |
|
* Defaults to `0` (Sunday). |
|
*/ |
|
startDay: 0, |
|
//</locale> |
|
|
|
//<locale> |
|
/** |
|
* @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. |
|
*/ |
|
showToday: true, |
|
//</locale> |
|
|
|
/** |
|
* @cfg {Date} [minDate=null] |
|
* Minimum allowable date (JavaScript date object) |
|
*/ |
|
|
|
/** |
|
* @cfg {Date} [maxDate=null] |
|
* Maximum allowable date (JavaScript date object) |
|
*/ |
|
|
|
/** |
|
* @cfg {Number[]} [disabledDays=null] |
|
* An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday. |
|
*/ |
|
|
|
/** |
|
* @cfg {RegExp} [disabledDatesRE=null] |
|
* JavaScript regular expression used to disable a pattern of dates. The {@link #disabledDates} |
|
* config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the |
|
* disabledDates value. |
|
*/ |
|
|
|
/** |
|
* @cfg {String[]} 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: |
|
* |
|
* - ['03/08/2003', '09/16/2003'] would disable those exact dates |
|
* - ['03/08', '09/16'] would disable those days for every year |
|
* - ['^03/08'] would only match the beginning (useful if you are using short years) |
|
* - ['03/../2006'] would disable every day in March 2006 |
|
* - ['^03'] would disable every day in every March |
|
* |
|
* 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']. |
|
*/ |
|
|
|
/** |
|
* @cfg {Boolean} disableAnim |
|
* True to disable animations when showing the month picker. |
|
*/ |
|
disableAnim: false, |
|
|
|
/** |
|
* @cfg {String} [baseCls='x-datepicker'] |
|
* The base CSS class to apply to this components element. |
|
*/ |
|
baseCls: Ext.baseCSSPrefix + 'datepicker', |
|
|
|
/** |
|
* @cfg {String} [selectedCls='x-datepicker-selected'] |
|
* The class to apply to the selected cell. |
|
*/ |
|
|
|
/** |
|
* @cfg {String} [disabledCellCls='x-datepicker-disabled'] |
|
* The class to apply to disabled cells. |
|
*/ |
|
|
|
//<locale> |
|
/** |
|
* @cfg {String} longDayFormat |
|
* The format for displaying a date in a longer format. |
|
*/ |
|
longDayFormat: 'F d, Y', |
|
//</locale> |
|
|
|
/** |
|
* @cfg {Object} keyNavConfig |
|
* Specifies optional custom key event handlers for the {@link Ext.util.KeyNav} attached to this date picker. Must |
|
* conform to the config format recognized by the {@link Ext.util.KeyNav} constructor. Handlers specified in this |
|
* object will replace default handlers of the same name. |
|
*/ |
|
|
|
// Default value used to initialise each date in the DatePicker. |
|
// __Note:__ 12 noon was chosen because it steers well clear of all DST timezone changes. |
|
initHour: 12, // 24-hour format |
|
|
|
numDays: 42, |
|
|
|
/** |
|
* @event select |
|
* Fires when a date is selected |
|
* @param {Ext.picker.Date} this DatePicker |
|
* @param {Date} date The selected date |
|
*/ |
|
|
|
// private, inherit docs |
|
initComponent: function() { |
|
var me = this, |
|
clearTime = Ext.Date.clearTime; |
|
|
|
me.selectedCls = me.baseCls + '-selected'; |
|
me.disabledCellCls = me.baseCls + '-disabled'; |
|
me.prevCls = me.baseCls + '-prevday'; |
|
me.activeCls = me.baseCls + '-active'; |
|
me.cellCls = me.baseCls + '-cell'; |
|
me.nextCls = me.baseCls + '-prevday'; |
|
me.todayCls = me.baseCls + '-today'; |
|
|
|
|
|
if (!me.format) { |
|
me.format = Ext.Date.defaultFormat; |
|
} |
|
if (!me.dayNames) { |
|
me.dayNames = Ext.Date.dayNames; |
|
} |
|
me.dayNames = me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay)); |
|
|
|
me.callParent(); |
|
|
|
me.value = me.value ? clearTime(me.value, true) : clearTime(new Date()); |
|
|
|
me.initDisabledDays(); |
|
}, |
|
|
|
// Keep the tree structure correct for Ext.form.field.Picker input fields which poke a 'pickerField' reference down into their pop-up pickers. |
|
getRefOwner: function() { |
|
return this.pickerField || this.callParent(); |
|
}, |
|
|
|
getRefItems: function() { |
|
var results = [], |
|
monthBtn = this.monthBtn, |
|
todayBtn = this.todayBtn; |
|
|
|
if (monthBtn) { |
|
results.push(monthBtn); |
|
} |
|
|
|
if (todayBtn) { |
|
results.push(todayBtn); |
|
} |
|
return results; |
|
}, |
|
|
|
beforeRender: function() { |
|
/* |
|
* days array for looping through 6 full weeks (6 weeks * 7 days) |
|
* Note that we explicitly force the size here so the template creates |
|
* all the appropriate cells. |
|
*/ |
|
var me = this, |
|
days = new Array(me.numDays), |
|
today = Ext.Date.format(new Date(), me.format); |
|
|
|
if (me.padding && !me.width) { |
|
me.cacheWidth(); |
|
} |
|
|
|
me.monthBtn = new Ext.button.Split({ |
|
ownerCt: me, |
|
ownerLayout: me.getComponentLayout(), |
|
text: '', |
|
tooltip: me.monthYearText, |
|
listeners: { |
|
click: me.doShowMonthPicker, |
|
arrowclick: me.doShowMonthPicker, |
|
scope: me |
|
} |
|
}); |
|
|
|
if (me.showToday) { |
|
me.todayBtn = new Ext.button.Button({ |
|
ownerCt: me, |
|
ownerLayout: me.getComponentLayout(), |
|
text: Ext.String.format(me.todayText, today), |
|
tooltip: Ext.String.format(me.todayTip, today), |
|
tooltipType: 'title', |
|
handler: me.selectToday, |
|
scope: me |
|
}); |
|
} |
|
|
|
me.callParent(); |
|
|
|
Ext.applyIf(me, { |
|
renderData: {} |
|
}); |
|
|
|
Ext.apply(me.renderData, { |
|
dayNames: me.dayNames, |
|
showToday: me.showToday, |
|
prevText: me.prevText, |
|
nextText: me.nextText, |
|
days: days |
|
}); |
|
|
|
me.protoEl.unselectable(); |
|
}, |
|
|
|
cacheWidth: function() { |
|
var me = this, |
|
padding = me.parseBox(me.padding), |
|
widthEl = Ext.getBody().createChild({ |
|
cls: me.baseCls + ' ' + me.borderBoxCls, |
|
style: 'position:absolute;top:-1000px;left:-1000px;' |
|
}); |
|
|
|
me.self.prototype.width = widthEl.getWidth() + padding.left + padding.right; |
|
widthEl.destroy(); |
|
}, |
|
|
|
/** |
|
* @inheritdoc |
|
* @private |
|
*/ |
|
onRender: function(container, position) { |
|
var me = this; |
|
|
|
me.callParent(arguments); |
|
|
|
me.cells = me.eventEl.select('tbody td'); |
|
me.textNodes = me.eventEl.query('tbody td div'); |
|
|
|
me.eventEl.set({ 'aria-labelledby': me.monthBtn.id }); |
|
|
|
me.mon(me.eventEl, { |
|
scope: me, |
|
mousewheel: me.handleMouseWheel, |
|
click: { |
|
fn: me.handleDateClick, |
|
delegate: 'div.' + me.baseCls + '-date' |
|
} |
|
}); |
|
|
|
}, |
|
|
|
/** |
|
* @inheritdoc |
|
* @private |
|
*/ |
|
initEvents: function(){ |
|
var me = this, |
|
pickerField = me.pickerField, |
|
eDate = Ext.Date, |
|
day = eDate.DAY; |
|
|
|
me.callParent(); |
|
|
|
// If this is not focusable (eg being used as the picker of a DateField) |
|
// then prevent mousedown from blurring the input field. |
|
if (!me.focusable) { |
|
me.el.on({ |
|
mousedown: me.onMouseDown |
|
}); |
|
} |
|
|
|
me.prevRepeater = new Ext.util.ClickRepeater(me.prevEl, { |
|
handler: me.showPrevMonth, |
|
scope: me, |
|
preventDefault: true, |
|
stopDefault: true |
|
}); |
|
|
|
me.nextRepeater = new Ext.util.ClickRepeater(me.nextEl, { |
|
handler: me.showNextMonth, |
|
scope: me, |
|
preventDefault: true, |
|
stopDefault: true |
|
}); |
|
|
|
// Read key events through our pickerField if we are bound to one |
|
me.keyNav = new Ext.util.KeyNav(pickerField ? pickerField.inputEl : me.eventEl, Ext.apply({ |
|
scope: me, |
|
|
|
// Must capture event so that the Picker sees it before the Field. |
|
capture: true, |
|
|
|
left: function(e) { |
|
if (e.ctrlKey) { |
|
me.showPrevMonth(); |
|
} else { |
|
me.update(eDate.add(me.activeDate, day, -1)); |
|
} |
|
}, |
|
|
|
right: function(e){ |
|
if (e.ctrlKey) { |
|
me.showNextMonth(); |
|
} else { |
|
me.update(eDate.add(me.activeDate, day, 1)); |
|
} |
|
}, |
|
|
|
up: function(e) { |
|
if (e.ctrlKey) { |
|
me.showNextYear(); |
|
} else { |
|
me.update(eDate.add(me.activeDate, day, -7)); |
|
} |
|
}, |
|
|
|
down: function(e) { |
|
if (e.ctrlKey) { |
|
me.showPrevYear(); |
|
} else { |
|
me.update(eDate.add(me.activeDate, day, 7)); |
|
} |
|
}, |
|
|
|
pageUp: function(e) { |
|
if (e.ctrlKey) { |
|
me.showPrevYear(); |
|
} else { |
|
me.showPrevMonth(); |
|
} |
|
}, |
|
|
|
pageDown: function(e) { |
|
if (e.ctrlKey) { |
|
me.showNextYear(); |
|
} else { |
|
me.showNextMonth(); |
|
} |
|
}, |
|
|
|
tab: function (e) { |
|
me.handleTabClick(e); |
|
|
|
// Allow default behaviour of TAB - it MUST be allowed to navigate. |
|
return true; |
|
}, |
|
|
|
enter: function(e) { |
|
me.handleDateClick(e, me.activeCell.firstChild); |
|
}, |
|
|
|
space: function() { |
|
me.setValue(new Date(me.activeCell.firstChild.dateValue)); |
|
var startValue = me.startValue, |
|
value = me.value, |
|
pickerValue; |
|
|
|
if (pickerField) { |
|
pickerValue = pickerField.getValue(); |
|
if (pickerValue && startValue && pickerValue.getTime() === value.getTime()) { |
|
pickerField.setValue(startValue); |
|
} else { |
|
pickerField.setValue(value); |
|
} |
|
} |
|
}, |
|
|
|
home: function(e) { |
|
me.update(eDate.getFirstDateOfMonth(me.activeDate)); |
|
}, |
|
|
|
end: function(e) { |
|
me.update(eDate.getLastDateOfMonth(me.activeDate)); |
|
} |
|
}, me.keyNavConfig)); |
|
|
|
if (me.disabled) { |
|
me.syncDisabled(true); |
|
} |
|
me.update(me.value); |
|
}, |
|
|
|
onMouseDown: function(e) { |
|
e.preventDefault(); |
|
}, |
|
|
|
handleTabClick: function (e) { |
|
var me = this, |
|
t = me.getSelectedDate(me.activeDate), |
|
handler = me.handler; |
|
|
|
// The following code is like handleDateClick without the e.stopEvent() |
|
if (!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)) { |
|
me.setValue(new Date(t.dateValue)); |
|
me.fireEvent('select', me, me.value); |
|
if (handler) { |
|
handler.call(me.scope || me, me, me.value); |
|
} |
|
me.onSelect(); |
|
} |
|
}, |
|
|
|
getSelectedDate: function (date) { |
|
var me = this, |
|
t = date.getTime(), |
|
cells = me.cells, |
|
cls = me.selectedCls, |
|
cellItems = cells.elements, |
|
cLen = cellItems.length, |
|
cell, c; |
|
|
|
cells.removeCls(cls); |
|
|
|
for (c = 0; c < cLen; c++) { |
|
cell = cellItems[c].firstChild; |
|
if (cell.dateValue === t) { |
|
return cell; |
|
} |
|
} |
|
return null; |
|
}, |
|
|
|
/** |
|
* Setup the disabled dates regex based on config options |
|
* @private |
|
*/ |
|
initDisabledDays: function() { |
|
var me = this, |
|
dd = me.disabledDates, |
|
re = '(?:', |
|
len, |
|
d, dLen, dI; |
|
|
|
if(!me.disabledDatesRE && dd){ |
|
len = dd.length - 1; |
|
|
|
dLen = dd.length; |
|
|
|
for (d = 0; d < dLen; d++) { |
|
dI = dd[d]; |
|
|
|
re += Ext.isDate(dI) ? '^' + Ext.String.escapeRegex(Ext.Date.dateFormat(dI, me.format)) + '$' : dI; |
|
if (d !== len) { |
|
re += '|'; |
|
} |
|
} |
|
|
|
me.disabledDatesRE = new RegExp(re + ')'); |
|
} |
|
}, |
|
|
|
/** |
|
* Replaces any existing disabled dates with new values and refreshes the DatePicker. |
|
* @param {String[]/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. |
|
* @return {Ext.picker.Date} this |
|
*/ |
|
setDisabledDates: function(dd) { |
|
var me = this; |
|
|
|
if (Ext.isArray(dd)) { |
|
me.disabledDates = dd; |
|
me.disabledDatesRE = null; |
|
} else { |
|
me.disabledDatesRE = dd; |
|
} |
|
me.initDisabledDays(); |
|
me.update(me.value, true); |
|
return me; |
|
}, |
|
|
|
/** |
|
* Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker. |
|
* @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details |
|
* on supported values. |
|
* @return {Ext.picker.Date} this |
|
*/ |
|
setDisabledDays: function(dd) { |
|
this.disabledDays = dd; |
|
return 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 |
|
* @return {Ext.picker.Date} this |
|
*/ |
|
setMinDate: function(dt) { |
|
this.minDate = dt; |
|
return 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 |
|
* @return {Ext.picker.Date} this |
|
*/ |
|
setMaxDate: function(dt) { |
|
this.maxDate = dt; |
|
return this.update(this.value, true); |
|
}, |
|
|
|
/** |
|
* Sets the value of the date field |
|
* @param {Date} value The date to set |
|
* @return {Ext.picker.Date} this |
|
*/ |
|
setValue: function(value) { |
|
// If passed a null value just pass in a new date object. |
|
this.value = Ext.Date.clearTime(value || new Date(), true); |
|
return this.update(this.value); |
|
}, |
|
|
|
/** |
|
* Gets the current selected value of the date field |
|
* @return {Date} The selected date |
|
*/ |
|
getValue: function() { |
|
return this.value; |
|
}, |
|
|
|
//<locale type="function"> |
|
/** |
|
* Gets a single character to represent the day of the week |
|
* @return {String} The character |
|
*/ |
|
getDayInitial: function(value) { |
|
return value.substr(0,1); |
|
}, |
|
//</locale> |
|
|
|
/** |
|
* @inheritdoc |
|
* @private |
|
*/ |
|
onEnable: function() { |
|
var me = this; |
|
|
|
me.callParent(); |
|
me.syncDisabled(false); |
|
me.update(me.activeDate); |
|
|
|
}, |
|
|
|
/** |
|
* @inheritdoc |
|
* @private |
|
*/ |
|
onShow: function() { |
|
var me = this; |
|
|
|
me.callParent(); |
|
me.syncDisabled(false); |
|
if (me.pickerField) { |
|
me.startValue = me.pickerField.getValue(); |
|
} |
|
}, |
|
|
|
/** |
|
* @inheritdoc |
|
* @private |
|
*/ |
|
onHide: function() { |
|
this.callParent(); |
|
this.syncDisabled(true); |
|
}, |
|
|
|
/** |
|
* @inheritdoc |
|
* @private |
|
*/ |
|
onDisable: function() { |
|
this.callParent(); |
|
this.syncDisabled(true); |
|
}, |
|
|
|
/** |
|
* Get the current active date. |
|
* @private |
|
* @return {Date} The active date |
|
*/ |
|
getActive: function(){ |
|
return this.activeDate || this.value; |
|
}, |
|
|
|
/** |
|
* Run any animation required to hide/show the month picker. |
|
* @private |
|
* @param {Boolean} isHide True if it's a hide operation |
|
*/ |
|
runAnimation: function(isHide){ |
|
var picker = this.monthPicker, |
|
options = { |
|
duration: 200, |
|
callback: function() { |
|
picker.setVisible(!isHide); |
|
} |
|
}; |
|
|
|
if (isHide) { |
|
picker.el.slideOut('t', options); |
|
} else { |
|
picker.el.slideIn('t', options); |
|
} |
|
}, |
|
|
|
/** |
|
* Hides the month picker, if it's visible. |
|
* @param {Boolean} [animate] Indicates whether to animate this action. If the animate |
|
* parameter is not specified, the behavior will use {@link #disableAnim} to determine |
|
* whether to animate or not. |
|
* @return {Ext.picker.Date} this |
|
*/ |
|
hideMonthPicker: function(animate){ |
|
var me = this, |
|
picker = me.monthPicker; |
|
|
|
if (picker && picker.isVisible()) { |
|
if (me.shouldAnimate(animate)) { |
|
me.runAnimation(true); |
|
} else { |
|
picker.hide(); |
|
} |
|
} |
|
return me; |
|
}, |
|
|
|
doShowMonthPicker: function(){ |
|
// Wrap in an extra call so we can prevent the button |
|
// being passed as an animation parameter. |
|
this.showMonthPicker(); |
|
}, |
|
|
|
doHideMonthPicker: function() { |
|
// Wrap in an extra call so we can prevent this |
|
// being passed as an animation parameter |
|
this.hideMonthPicker(); |
|
}, |
|
|
|
/** |
|
* Show the month picker |
|
* @param {Boolean} [animate] Indicates whether to animate this action. If the animate |
|
* parameter is not specified, the behavior will use {@link #disableAnim} to determine |
|
* whether to animate or not. |
|
* @return {Ext.picker.Date} this |
|
*/ |
|
showMonthPicker: function(animate) { |
|
var me = this, |
|
el = me.el, |
|
picker; |
|
|
|
if (me.rendered && !me.disabled) { |
|
picker = me.createMonthPicker(); |
|
if (!picker.isVisible()) { |
|
picker.setValue(me.getActive()); |
|
picker.setSize(el.getSize()); |
|
|
|
// Null out floatParent so that the [-1, -1] position is not made relative to this |
|
picker.floatParent = null; |
|
picker.setPosition(-el.getBorderWidth('l'), -el.getBorderWidth('t')); |
|
if (me.shouldAnimate(animate)) { |
|
me.runAnimation(false); |
|
} else { |
|
picker.show(); |
|
} |
|
} |
|
} |
|
return me; |
|
}, |
|
|
|
/** |
|
* Checks whether a hide/show action should animate |
|
* @private |
|
* @param {Boolean} [animate] A possible animation value |
|
* @return {Boolean} Whether to animate the action |
|
*/ |
|
shouldAnimate: function(animate) { |
|
return Ext.isDefined(animate) ? animate : !this.disableAnim; |
|
}, |
|
|
|
/** |
|
* Create the month picker instance |
|
* @private |
|
* @return {Ext.picker.Month} picker |
|
*/ |
|
createMonthPicker: function() { |
|
var me = this, |
|
picker = me.monthPicker; |
|
|
|
if (!picker) { |
|
me.monthPicker = picker = new Ext.picker.Month({ |
|
renderTo: me.el, |
|
// We need to set the ownerCmp so that owns() can correctly |
|
// match up the component hierarchy so that focus does not leave |
|
// an owning picker field if/when this gets focus. |
|
ownerCmp: me, |
|
floating: true, |
|
padding: me.padding, |
|
shadow: false, |
|
small: me.showToday === false, |
|
listeners: { |
|
scope: me, |
|
cancelclick: me.onCancelClick, |
|
okclick: me.onOkClick, |
|
yeardblclick: me.onOkClick, |
|
monthdblclick: me.onOkClick |
|
} |
|
}); |
|
if (!me.disableAnim) { |
|
// hide the element if we're animating to prevent an initial flicker |
|
picker.el.setStyle('display', 'none'); |
|
} |
|
picker.hide(); |
|
me.on('beforehide', me.doHideMonthPicker, me); |
|
} |
|
return picker; |
|
}, |
|
|
|
/** |
|
* Respond to an ok click on the month picker |
|
* @private |
|
*/ |
|
onOkClick: function(picker, value) { |
|
var me = this, |
|
month = value[0], |
|
year = value[1], |
|
date = new Date(year, month, me.getActive().getDate()); |
|
|
|
if (date.getMonth() !== month) { |
|
// 'fix' the JS rolling date conversion if needed |
|
date = Ext.Date.getLastDateOfMonth(new Date(year, month, 1)); |
|
} |
|
me.setValue(date); |
|
me.hideMonthPicker(); |
|
}, |
|
|
|
/** |
|
* Respond to a cancel click on the month picker |
|
* @private |
|
*/ |
|
onCancelClick: function() { |
|
this.selectedUpdate(this.activeDate); |
|
this.hideMonthPicker(); |
|
}, |
|
|
|
/** |
|
* Show the previous month. |
|
* @param {Object} e |
|
* @return {Ext.picker.Date} this |
|
*/ |
|
showPrevMonth: function(e) { |
|
return this.setValue(Ext.Date.add(this.activeDate, Ext.Date.MONTH, -1)); |
|
}, |
|
|
|
/** |
|
* Show the next month. |
|
* @param {Object} e |
|
* @return {Ext.picker.Date} this |
|
*/ |
|
showNextMonth: function(e) { |
|
return this.setValue(Ext.Date.add(this.activeDate, Ext.Date.MONTH, 1)); |
|
}, |
|
|
|
/** |
|
* Show the previous year. |
|
* @return {Ext.picker.Date} this |
|
*/ |
|
showPrevYear: function() { |
|
return this.setValue(Ext.Date.add(this.activeDate, Ext.Date.YEAR, -1)); |
|
}, |
|
|
|
/** |
|
* Show the next year. |
|
* @return {Ext.picker.Date} this |
|
*/ |
|
showNextYear: function() { |
|
return this.setValue(Ext.Date.add(this.activeDate, Ext.Date.YEAR, 1)); |
|
}, |
|
|
|
/** |
|
* Respond to the mouse wheel event |
|
* @private |
|
* @param {Ext.event.Event} e |
|
*/ |
|
handleMouseWheel: function(e) { |
|
e.stopEvent(); |
|
if(!this.disabled){ |
|
var delta = e.getWheelDelta(); |
|
if(delta > 0){ |
|
this.showPrevMonth(); |
|
} else if(delta < 0){ |
|
this.showNextMonth(); |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* Respond to a date being clicked in the picker |
|
* @private |
|
* @param {Ext.event.Event} e |
|
* @param {HTMLElement} t |
|
*/ |
|
handleDateClick: function(e, t) { |
|
var me = this, |
|
handler = me.handler; |
|
|
|
e.stopEvent(); |
|
if(!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)){ |
|
me.setValue(new Date(t.dateValue)); |
|
me.fireEvent('select', me, me.value); |
|
if (handler) { |
|
handler.call(me.scope || me, me, me.value); |
|
} |
|
// event handling is turned off on hide |
|
// when we are using the picker in a field |
|
// therefore onSelect comes AFTER the select |
|
// event. |
|
me.onSelect(); |
|
} |
|
}, |
|
|
|
/** |
|
* Perform any post-select actions |
|
* @private |
|
*/ |
|
onSelect: function() { |
|
if (this.hideOnSelect) { |
|
this.hide(); |
|
} |
|
}, |
|
|
|
/** |
|
* Sets the current value to today. |
|
* @return {Ext.picker.Date} this |
|
*/ |
|
selectToday: function() { |
|
var me = this, |
|
btn = me.todayBtn, |
|
handler = me.handler; |
|
|
|
if (btn && !btn.disabled) { |
|
me.setValue(Ext.Date.clearTime(new Date())); |
|
me.fireEvent('select', me, me.value); |
|
if (handler) { |
|
handler.call(me.scope || me, me, me.value); |
|
} |
|
me.onSelect(); |
|
} |
|
return me; |
|
}, |
|
|
|
/** |
|
* Update the selected cell |
|
* @private |
|
* @param {Date} date The new date |
|
*/ |
|
selectedUpdate: function(date) { |
|
var me = this, |
|
t = date.getTime(), |
|
cells = me.cells, |
|
cls = me.selectedCls, |
|
c, |
|
cLen = cells.getCount(), |
|
cell; |
|
|
|
cell = me.activeCell; |
|
if (cell) { |
|
Ext.fly(cell).removeCls(cls); |
|
cell.setAttribute('aria-selected', false); |
|
} |
|
|
|
for (c = 0; c < cLen; c++) { |
|
cell = cells.item(c); |
|
|
|
if (me.textNodes[c].dateValue === t) { |
|
me.activeCell = cell.dom; |
|
me.eventEl.dom.setAttribute('aria-activedescendant', cell.dom.id); |
|
cell.dom.setAttribute('aria-selected', true); |
|
cell.addCls(cls); |
|
me.fireEvent('highlightitem', me, cell); |
|
break; |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* Update the contents of the picker for a new month |
|
* @private |
|
* @param {Date} date The new date |
|
*/ |
|
fullUpdate: function(date) { |
|
var me = this, |
|
cells = me.cells.elements, |
|
textNodes = me.textNodes, |
|
disabledCls = me.disabledCellCls, |
|
eDate = Ext.Date, |
|
i = 0, |
|
extraDays = 0, |
|
newDate = +eDate.clearTime(date, true), |
|
today = +eDate.clearTime(new Date()), |
|
min = me.minDate ? eDate.clearTime(me.minDate, true) : Number.NEGATIVE_INFINITY, |
|
max = me.maxDate ? eDate.clearTime(me.maxDate, true) : Number.POSITIVE_INFINITY, |
|
ddMatch = me.disabledDatesRE, |
|
ddText = me.disabledDatesText, |
|
ddays = me.disabledDays ? me.disabledDays.join('') : false, |
|
ddaysText = me.disabledDaysText, |
|
format = me.format, |
|
days = eDate.getDaysInMonth(date), |
|
firstOfMonth = eDate.getFirstDateOfMonth(date), |
|
startingPos = firstOfMonth.getDay() - me.startDay, |
|
previousMonth = eDate.add(date, eDate.MONTH, -1), |
|
ariaTitleDateFormat = me.ariaTitleDateFormat, |
|
prevStart, current, disableToday, tempDate, setCellClass, html, cls, |
|
formatValue, value; |
|
|
|
if (startingPos < 0) { |
|
startingPos += 7; |
|
} |
|
|
|
days += startingPos; |
|
prevStart = eDate.getDaysInMonth(previousMonth) - startingPos; |
|
current = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), prevStart, me.initHour); |
|
|
|
if (me.showToday) { |
|
tempDate = eDate.clearTime(new Date()); |
|
disableToday = (tempDate < min || tempDate > max || |
|
(ddMatch && format && ddMatch.test(eDate.dateFormat(tempDate, format))) || |
|
(ddays && ddays.indexOf(tempDate.getDay()) !== -1)); |
|
|
|
if (!me.disabled) { |
|
me.todayBtn.setDisabled(disableToday); |
|
} |
|
} |
|
|
|
setCellClass = function(cellIndex, cls){ |
|
var cell = cells[cellIndex]; |
|
|
|
value = +eDate.clearTime(current, true); |
|
cell.setAttribute('aria-label', eDate.format(current, ariaTitleDateFormat)); |
|
// store dateValue number as an expando |
|
cell.firstChild.dateValue = value; |
|
if (value === today) { |
|
cls += ' ' + me.todayCls; |
|
cell.firstChild.title = me.todayText; |
|
|
|
// Extra element for ARIA purposes |
|
me.todayElSpan = Ext.DomHelper.append(cell.firstChild, { |
|
tag: 'span', |
|
cls: Ext.baseCSSPrefix + 'hidden-clip', |
|
html: me.todayText |
|
}, true); |
|
} |
|
if (value === newDate) { |
|
me.activeCell = cell; |
|
me.eventEl.dom.setAttribute('aria-activedescendant', cell.id); |
|
cell.setAttribute('aria-selected', true); |
|
cls += ' ' + me.selectedCls; |
|
me.fireEvent('highlightitem', me, cell); |
|
} else { |
|
cell.setAttribute('aria-selected', false); |
|
} |
|
|
|
if (value < min) { |
|
cls += ' ' + disabledCls; |
|
cell.setAttribute('aria-label', me.minText); |
|
} |
|
else if (value > max) { |
|
cls += ' ' + disabledCls; |
|
cell.setAttribute('aria-label', me.maxText); |
|
} |
|
else if (ddays && ddays.indexOf(current.getDay()) !== -1){ |
|
cell.setAttribute('aria-label', ddaysText); |
|
cls += ' ' + disabledCls; |
|
} |
|
else if (ddMatch && format){ |
|
formatValue = eDate.dateFormat(current, format); |
|
if(ddMatch.test(formatValue)){ |
|
cell.setAttribute('aria-label', ddText.replace('%0', formatValue)); |
|
cls += ' ' + disabledCls; |
|
} |
|
} |
|
cell.className = cls + ' ' + me.cellCls; |
|
}; |
|
|
|
for(; i < me.numDays; ++i) { |
|
if (i < startingPos) { |
|
html = (++prevStart); |
|
cls = me.prevCls; |
|
} else if (i >= days) { |
|
html = (++extraDays); |
|
cls = me.nextCls; |
|
} else { |
|
html = i - startingPos + 1; |
|
cls = me.activeCls; |
|
} |
|
textNodes[i].innerHTML = html; |
|
current.setDate(current.getDate() + 1); |
|
setCellClass(i, cls); |
|
} |
|
|
|
me.monthBtn.setText(Ext.Date.format(date, me.monthYearFormat)); |
|
}, |
|
|
|
/** |
|
* Update the contents of the picker |
|
* @private |
|
* @param {Date} date The new date |
|
* @param {Boolean} forceRefresh True to force a full refresh |
|
*/ |
|
update: function(date, forceRefresh) { |
|
var me = this, |
|
active = me.activeDate; |
|
|
|
if (me.rendered) { |
|
me.activeDate = date; |
|
if (!forceRefresh && active && me.el && |
|
active.getMonth() === date.getMonth() && |
|
active.getFullYear() === date.getFullYear()) { |
|
me.selectedUpdate(date, active); |
|
} else { |
|
me.fullUpdate(date, active); |
|
} |
|
} |
|
return me; |
|
}, |
|
|
|
/** |
|
* @private |
|
* @inheritdoc |
|
*/ |
|
beforeDestroy: function() { |
|
var me = this; |
|
|
|
if (me.rendered) { |
|
Ext.destroy( |
|
me.keyNav, |
|
me.monthPicker, |
|
me.monthBtn, |
|
me.nextRepeater, |
|
me.prevRepeater, |
|
me.todayBtn, |
|
me.todayElSpan |
|
); |
|
delete me.textNodes; |
|
delete me.cells.elements; |
|
} |
|
me.callParent(); |
|
}, |
|
|
|
privates: { |
|
// Do the job of a container layout at this point even though we are not a Container. |
|
// TODO: Refactor as a Container. |
|
finishRenderChildren: function () { |
|
var me = this; |
|
|
|
me.callParent(); |
|
me.monthBtn.finishRender(); |
|
if (me.showToday) { |
|
me.todayBtn.finishRender(); |
|
} |
|
}, |
|
|
|
getFocusEl: function() { |
|
return this.eventEl; |
|
}, |
|
|
|
/** |
|
* Set the disabled state of various internal components |
|
* @param {Boolean} disabled |
|
* @private |
|
*/ |
|
syncDisabled: function (disabled) { |
|
var me = this, |
|
keyNav = me.keyNav; |
|
|
|
// If we have one, we have all |
|
if (keyNav) { |
|
keyNav.setDisabled(disabled); |
|
me.prevRepeater.setDisabled(disabled); |
|
me.nextRepeater.setDisabled(disabled); |
|
if (me.todayBtn) { |
|
me.todayBtn.setDisabled(disabled); |
|
} |
|
} |
|
} |
|
} |
|
});
|
|
|