Code:
Ext.define('Ext.ux.picker.DateRange', {
extend: 'Ext.picker.Date',
undos: [],
ariaTitle: '',
renderTpl: [
'<div id="{id}-innerEl" role="grid">',
'<div role="presentation" class="{baseCls}-header">',
'<div class="{baseCls}-prev"><a id="{id}-prevEl" href="#" role="button" title="{prevText}"></a></div>',
'<div class="{baseCls}-month" id="{id}-middleBtnEl">{%this.renderMonthBtn(values, out)%}</div>',
'<div class="{baseCls}-next"><a id="{id}-nextEl" href="#" role="button" title="{nextText}"></a></div>',
'</div>',
'<table id="{id}-eventEl" class="{baseCls}-inner" cellspacing="0" role="presentation">',
'<thead role="presentation"><tr role="presentation">',
'<tpl for="dayNames">',
'<th role="columnheader" title="{.}"><span>{.:this.firstInitial}</span></th>',
'</tpl>',
'</tr></thead>',
'<tbody role="presentation"><tr role="presentation">',
'<tpl for="days">',
'{#:this.isEndOfWeek}',
'<td role="gridcell" id="{[Ext.id()]}">',
'<a role="presentation" href="#" hidefocus="on" class="{parent.baseCls}-date" tabIndex="1">',
'<em role="presentation"><span role="presentation"></span></em>',
'</a>',
'</td>',
'</tpl>',
'</tr></tbody>',
'</table>',
'<div id="{id}-footerEl" role="presentation" class="{baseCls}-footer">',
'<tpl if="showToday">{%this.renderTodayBtn(values, out)%}</tpl>',
'<tpl if="showClear">{%this.renderClearBtn(values, out)%}</tpl>',
'<tpl if="showUndo">{%this.renderUndoBtn(values, out)%}</tpl>',
'<tpl if="showReset">{%this.renderResetBtn(values, out)%}</tpl>',
'</div>',
'</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);
},
renderClearBtn: function(values, out) {
Ext.DomHelper.generateMarkup(values.$comp.clearBtn.getRenderTree(), out);
},
renderUndoBtn: function(values, out) {
Ext.DomHelper.generateMarkup(values.$comp.undoBtn.getRenderTree(), out);
},
renderResetBtn: function(values, out) {
Ext.DomHelper.generateMarkup(values.$comp.resetBtn.getRenderTree(), out);
},
renderMonthBtn: function(values, out) {
Ext.DomHelper.generateMarkup(values.$comp.monthBtn.getRenderTree(), out);
}
}
],
beforeRender: function() {
var me = this;
if(me.showClear) {
me.clearBtn = Ext.create('Ext.button.Button', {
ownerCt: me,
ownerLayout: me.getComponentLayout(),
text: me.clearText,
tooltip: 'Clear all selected Dates',
handler: me.selectClear,
scope: me
});
}
if(me.showUndo) {
me.undoBtn = Ext.create('Ext.button.Button', {
ownerCt: me,
ownerLayout: me.getComponentLayout(),
text: me.undoText,
tooltip: 'Undo last action',
handler: me.onUndo,
scope: me
});
}
if(me.showReset) {
me.resetBtn = Ext.create('Ext.button.Button', {
ownerCt: me,
ownerLayout: me.getComponentLayout(),
text: me.resetText,
tooltip: 'Reset Dates',
handler: me.onReset,
scope: me
});
}
me.callParent();
Ext.apply(me.renderData, {
showClear: me.showClear,
showUndo: me.showUndo,
showReset: me.showReset
});
},
finishRenderChildren: function() {
var me = this;
me.callParent();
if (me.showClear) {
me.clearBtn.finishRender();
}
if (me.showUndo) {
me.undoBtn.finishRender();
}
if (me.showReset) {
me.resetBtn.finishRender();
}
},
selectClear: function() {
var me = this,
sd = me.selectedDates;
me.undos.push(Ext.Array.clone(sd));
Ext.Array.erase(sd,0,sd.length);
me.setValue('');
me.fireEvent('select', me, me.value);
me.refresh();
},
onUndo: function() {
var me = this;
me.selectedDates = Ext.Array.clone(me.undos.pop());
me.refresh();
},
onReset: function() {
var me = this;
me.selectedDates = Ext.Array.clone(me.initialDates);
me.refresh();
},
refresh: function() {
var me = this,
cells = me.cells,
cls = me.selectedCls,
sd = me.selectedDates;
if(me.rendered) {
cells.removeCls(cls);
cells.each(function(c){
if (Ext.Array.contains(sd, c.dom.firstChild.dateValue)) {
me.el.dom.setAttribute('aria-activedescendent', c.dom.id);
c.addCls(cls);
}
}, this);
me.undoBtn.setDisabled((me.undos.length < 1));
}
},
selectedUpdate: function(date, active){
var me = this,
t = (date) ? date.getTime() : null,
cells = me.cells,
cls = me.selectedCls,
sd = me.selectedDates;
// Push an undo set of Dates
me.undos.push(Ext.Array.clone(sd));
if(Ext.Array.contains(sd, t))
Ext.Array.remove(sd, t);
else
sd.push(t);
Ext.Array.sort(sd);
me.refresh();
},
fullUpdate: function(date, active){
var me = this,
cells = me.cells.elements,
textNodes = me.textNodes,
disabledCls = me.disabledCellCls,
eDate = Ext.Date,
i = 0,
extraDays = 0,
visible = me.isVisible(),
sel = +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),
longDayFormat = me.longDayFormat,
prevStart,
current,
disableToday,
tempDate,
setCellClass,
html,
cls,
formatValue,
value,
sd = me.selectedDates;
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);
me.todayKeyListener.setDisabled(disableToday);
}
}
setCellClass = function(cell){
value = +eDate.clearTime(current, true);
cell.title = eDate.format(current, longDayFormat);
// store dateValue number as an expando
cell.firstChild.dateValue = value;
if(value == today){
cell.className += ' ' + me.todayCls;
cell.title = me.todayText;
}
// Code I added
// Stop the refresh from selecting the active date in the current month
// And select the dates that are in the selectedDates array
if(Ext.Array.contains(sd, value)) {
cell.className += ' ' + me.selectedCls;
me.el.dom.setAttribute('aria-activedescendant', cell.id);
if (visible && me.floating) {
Ext.fly(cell.firstChild).focus(50);
}
}
// disabling
if(value < min) {
cell.className = disabledCls;
cell.title = me.minText;
return;
}
if(value > max) {
cell.className = disabledCls;
cell.title = me.maxText;
return;
}
if(ddays){
if(ddays.indexOf(current.getDay()) != -1){
cell.title = ddaysText;
cell.className = disabledCls;
}
}
if(ddMatch && format){
formatValue = eDate.dateFormat(current, format);
if(ddMatch.test(formatValue)){
cell.title = ddText.replace('%0', formatValue);
cell.className = disabledCls;
}
}
};
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;
cells[i].className = cls;
current.setDate(current.getDate() + 1);
setCellClass(cells[i]);
}
me.monthBtn.setText(me.monthNames[date.getMonth()] + ' ' + date.getFullYear());
},
setValue : function(value){
var me = this;
if(value == '') {
this.selectedDates = new Array();
this.refresh();
}
// Should only get in here on new server data, all other calls should be passing a single Ext.Date paramaeter
else if(!Ext.isDate(value)) {
var sd = value.split(this.delimiter),
dt;
Ext.each(sd, function(raw, index) {
dt = Ext.Date.parse(raw, me.dateFormat);
if(Ext.isDate(dt))
sd[index] = dt.getTime();
});
this.selectedDates = sd;
this.initialDates = Ext.Array.clone(sd);
this.refresh();
}
else
this.callParent(arguments);
},
getValue : function(asObj){
var me = this;
sd = me.selectedDates;
if(sd.length > 1)
return me.getValues(asObj);
return this.value;
},
getValues : function(asObj){
var me = this,
sd = me.selectedDates,
asObj = asObj || false,
dates = [];
if(asObj) {
Ext.each(sd, function(date) {
dates.push(new Date(date));
});
return dates;
}
return sd;
}
});
Ext.define('Ext.ux.form.field.DateRange', {
extend: 'Ext.form.field.Base',
alias: 'widget.daterangefield',
requires: ['Ext.ux.picker.DateRange'],
inputType: 'hidden',
// The format of the dates coming in as a delimited set
// Ex. "2012-01-01,2012-01-02"
// This is also the format that will be used to on submits and saves
dateFormat: 'Y-m-d',
// Picker Config
showToday: true,
showClear: true,
showUndo: true,
showReset: true,
clearText: 'C',
undoText: 'U',
resetText: 'R',
delimiter: ',',
onRender: function() {
var me = this;
me.callParent(arguments);
me.picker = me.createPicker();
},
createPicker: function() {
var me = this;
return Ext.create('Ext.ux.picker.DateRange', {
renderTo: me.bodyEl,
showToday: me.showToday,
showClear: me.showClear,
showUndo: me.showUndo,
showReset: me.showReset,
clearText: me.clearText,
undoText: me.undoText,
resetText: me.resetText,
selectedDates: [],
delimiter: me.delimiter,
dateFormat: me.dateFormat,
listeners: {
scope: me,
select: me.onSelect
}
});
},
onSelect: function(m,d) {
var me = this,
sd = m.selectedDates;
me.setRawValue(me.revertData(sd));
me.fireEvent('select', me, d);
},
setValue: function(value) {
this.callParent(arguments);
var me = this;
if(me.picker)
me.picker.setValue(value);
},
revertData: function(sd) {
var me = this,
arr = [];
for(var i=0; i<sd.length; i++) {
arr[i] = Ext.Date.format(new Date(sd[i]), me.dateFormat);
}
return arr.join(me.delimiter);
},
enable: function(silent) {
this.callParent(arguments);
this.picker.el.unmask();
},
disable: function(silent) {
this.callParent(arguments);
this.picker.el.mask();
}
})
Example of how to use: