Extend jQuery UI datepicker widget with basic accessibility features
parent
370ef159a3
commit
2efb5cd52a
File diff suppressed because one or more lines are too long
@ -0,0 +1,213 @@
|
||||
/*! 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 {
|
||||
inst.dpDiv.attr('id', inst.id + '-dp-widget')
|
||||
.attr('aria-hidden', 'true')
|
||||
.attr('aria-labelledby', inst.id + '-dp-title');
|
||||
|
||||
$(inst.input).attr('aria-haspopup', 'true')
|
||||
.attr('aria-expanded', 'false')
|
||||
.attr('aria-owns', inst.id + '-dp-widget');
|
||||
}
|
||||
|
||||
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).bind('keyup', 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.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));
|
Loading…
Reference in New Issue