|
|
|
/*! jQuery UI Accessible Datepicker extension
|
|
|
|
* (to be appended to jquery-ui-*.custom.min.js)
|
|
|
|
*
|
|
|
|
* @licstart The following is the entire license notice for the
|
|
|
|
* JavaScript code in this page.
|
|
|
|
*
|
|
|
|
* Copyright 2014 Kolab Systems AG
|
|
|
|
*
|
|
|
|
* The JavaScript code in this page is free software: you can
|
|
|
|
* redistribute it and/or modify it under the terms of the GNU
|
|
|
|
* General Public License (GNU GPL) as published by the Free Software
|
|
|
|
* Foundation, either version 3 of the License, or (at your option)
|
|
|
|
* any later version. The code is distributed WITHOUT ANY WARRANTY;
|
|
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
|
|
* FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
|
|
|
|
*
|
|
|
|
* As additional permission under GNU GPL version 3 section 7, you
|
|
|
|
* may distribute non-source (e.g., minimized or compacted) forms of
|
|
|
|
* that code without the copy of the GNU GPL normally required by
|
|
|
|
* section 4, provided you include this license notice and a URL
|
|
|
|
* through which recipients can access the Corresponding Source.
|
|
|
|
*
|
|
|
|
* @licend The above is the entire license notice
|
|
|
|
* for the JavaScript code in this page.
|
|
|
|
*/
|
|
|
|
|
|
|
|
(function($, undefined) {
|
|
|
|
|
|
|
|
// references to super class methods
|
|
|
|
var __newInst = $.datepicker._newInst;
|
|
|
|
var __updateDatepicker = $.datepicker._updateDatepicker;
|
|
|
|
var __connectDatepicker = $.datepicker._connectDatepicker;
|
|
|
|
var __showDatepicker = $.datepicker._showDatepicker;
|
|
|
|
var __hideDatepicker = $.datepicker._hideDatepicker;
|
|
|
|
|
|
|
|
// "extend" singleton instance methods
|
|
|
|
$.extend($.datepicker, {
|
|
|
|
|
|
|
|
/* Create a new instance object */
|
|
|
|
_newInst: function(target, inline) {
|
|
|
|
var that = this, inst = __newInst.call(this, target, inline);
|
|
|
|
|
|
|
|
if (inst.inline) {
|
|
|
|
// attach keyboard event handler
|
|
|
|
inst.dpDiv.on('keydown.datepicker', '.ui-datepicker-calendar', function(event) {
|
|
|
|
// we're only interested navigation keys
|
|
|
|
if ($.inArray(event.keyCode, [ 13, 33, 34, 35, 36, 37, 38, 39, 40]) == -1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
event.stopPropagation();
|
|
|
|
event.preventDefault();
|
|
|
|
inst._hasfocus = true;
|
|
|
|
|
|
|
|
var activeCell;
|
|
|
|
switch (event.keyCode) {
|
|
|
|
case $.ui.keyCode.ENTER:
|
|
|
|
if ((activeCell = $('.' + that._dayOverClass, inst.dpDiv).get(0) || $('.' + that._currentClass, inst.dpDiv).get(0))) {
|
|
|
|
that._selectDay(inst.input, inst.selectedMonth, inst.selectedYear, activeCell);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case $.ui.keyCode.PAGE_UP:
|
|
|
|
that._adjustDate(inst.input, -that._get(inst, 'stepMonths'), 'M');
|
|
|
|
break;
|
|
|
|
case $.ui.keyCode.PAGE_DOWN:
|
|
|
|
that._adjustDate(inst.input, that._get(inst, 'stepMonths'), 'M');
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return that._cursorKeydown(event, inst);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.attr('role', 'region')
|
|
|
|
.attr('aria-labelledby', inst.id + '-dp-title');
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
var widgetId = inst.dpDiv.attr('id') || inst.id + '-dp-widget';
|
|
|
|
inst.dpDiv.attr('id', widgetId)
|
|
|
|
.attr('aria-hidden', 'true')
|
|
|
|
.attr('aria-labelledby', inst.id + '-dp-title');
|
|
|
|
|
|
|
|
$(inst.input).attr('aria-haspopup', 'true')
|
|
|
|
.attr('aria-expanded', 'false')
|
|
|
|
.attr('aria-owns', widgetId);
|
|
|
|
}
|
|
|
|
|
|
|
|
return inst;
|
|
|
|
},
|
|
|
|
|
|
|
|
/* Attach the date picker to an input field */
|
|
|
|
_connectDatepicker: function(target, inst) {
|
|
|
|
__connectDatepicker.call(this, target, inst);
|
|
|
|
|
|
|
|
var that = this;
|
|
|
|
|
|
|
|
// register additional keyboard events to control date selection with cursor keys
|
|
|
|
$(target).unbind('keyup.datepicker-extended').bind('keyup.datepicker-extended', function(event) {
|
|
|
|
var inc = 1;
|
|
|
|
switch (event.keyCode) {
|
|
|
|
case 109:
|
|
|
|
case 189: // "minus"
|
|
|
|
inc = -1;
|
|
|
|
case 107:
|
|
|
|
case 187: // "plus"
|
|
|
|
that._adjustInstDate(inst, inc, 'D');
|
|
|
|
that._selectDate(target, that._formatDate(inst, inst.selectedDay, inst.selectedMonth, inst.selectedYear));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case $.ui.keyCode.UP:
|
|
|
|
case $.ui.keyCode.DOWN:
|
|
|
|
// unfold datepicker if not visible
|
|
|
|
if ($.datepicker._lastInput !== target && !$.datepicker._isDisabledDatepicker(target)) {
|
|
|
|
that._showDatepicker(event);
|
|
|
|
event.stopPropagation();
|
|
|
|
event.preventDefault();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
if (!$.datepicker._isDisabledDatepicker(target) && !event.ctrlKey && !event.metaKey) {
|
|
|
|
return that._cursorKeydown(event, inst);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.attr('autocomplete', 'off');
|
|
|
|
},
|
|
|
|
|
|
|
|
/* Handle keyboard event on datepicker widget */
|
|
|
|
_cursorKeydown: function(event, inst) {
|
|
|
|
inst._keyEvent = true;
|
|
|
|
|
|
|
|
var isRTL = inst.dpDiv.hasClass('ui-datepicker-rtl');
|
|
|
|
|
|
|
|
switch (event.keyCode) {
|
|
|
|
case $.ui.keyCode.LEFT:
|
|
|
|
this._adjustDate(inst.input, (isRTL ? +1 : -1), 'D');
|
|
|
|
break;
|
|
|
|
case $.ui.keyCode.RIGHT:
|
|
|
|
this._adjustDate(inst.input, (isRTL ? -1 : +1), 'D');
|
|
|
|
break;
|
|
|
|
case $.ui.keyCode.UP:
|
|
|
|
this._adjustDate(inst.input, -7, 'D');
|
|
|
|
break;
|
|
|
|
case $.ui.keyCode.DOWN:
|
|
|
|
this._adjustDate(inst.input, +7, 'D');
|
|
|
|
break;
|
|
|
|
case $.ui.keyCode.HOME:
|
|
|
|
// TODO: jump to first of month
|
|
|
|
break;
|
|
|
|
case $.ui.keyCode.END:
|
|
|
|
// TODO: jump to end of month
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
/* Pop-up the date picker for a given input field */
|
|
|
|
_showDatepicker: function(input) {
|
|
|
|
input = input.target || input;
|
|
|
|
__showDatepicker.call(this, input);
|
|
|
|
|
|
|
|
var inst = $.datepicker._getInst(input);
|
|
|
|
if (inst && $.datepicker._datepickerShowing) {
|
|
|
|
inst.dpDiv.attr('aria-hidden', 'false');
|
|
|
|
$(input).attr('aria-expanded', 'true');
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/* Hide the date picker from view */
|
|
|
|
_hideDatepicker: function(input) {
|
|
|
|
__hideDatepicker.call(this, input);
|
|
|
|
|
|
|
|
var inst = this._curInst;;
|
|
|
|
if (inst && !$.datepicker._datepickerShowing) {
|
|
|
|
inst.dpDiv.attr('aria-hidden', 'true');
|
|
|
|
$(inst.input).attr('aria-expanded', 'false');
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/* Render the date picker content */
|
|
|
|
_updateDatepicker: function(inst) {
|
|
|
|
__updateDatepicker.call(this, inst);
|
|
|
|
|
|
|
|
var activeCell = $('.' + this._dayOverClass, inst.dpDiv).get(0) || $('.' + this._currentClass, inst.dpDiv).get(0);
|
|
|
|
if (activeCell) {
|
|
|
|
activeCell = $(activeCell);
|
|
|
|
activeCell.attr('id', inst.id + '-day-' + activeCell.text());
|
|
|
|
}
|
|
|
|
|
|
|
|
// allow focus on main container only
|
|
|
|
inst.dpDiv.find('.ui-datepicker-calendar')
|
|
|
|
.attr('tabindex', inst.inline ? '0' : '-1')
|
|
|
|
.attr('role', 'grid')
|
|
|
|
.attr('aria-readonly', 'true')
|
|
|
|
.attr('aria-activedescendant', activeCell ? activeCell.attr('id') : '')
|
|
|
|
.find('td').attr('role', 'gridcell').attr('aria-selected', 'false')
|
|
|
|
.find('a').attr('tabindex', '-1');
|
|
|
|
|
|
|
|
$('.ui-datepicker-current-day', inst.dpDiv).attr('aria-selected', 'true');
|
|
|
|
|
|
|
|
inst.dpDiv.find('.ui-datepicker-title')
|
|
|
|
.attr('id', inst.id + '-dp-title')
|
|
|
|
|
|
|
|
// set focus again after update
|
|
|
|
if (inst._hasfocus) {
|
|
|
|
inst.dpDiv.find('.ui-datepicker-calendar').focus();
|
|
|
|
inst._hasfocus = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}(jQuery));
|