Extend jQuery UI datepicker widget with basic accessibility features

pull/198/head
Thomas Bruederli 11 years ago
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));

@ -1457,6 +1457,10 @@ body .ui-tooltip {
margin: 0;
border-spacing: 0;
}
.ui-datepicker table:focus {
outline: 2px solid #4fadd5;
outline-offset: -2px;
}
.ui-datepicker td {
border: 1px solid #bbb;
padding: 0;
@ -1482,9 +1486,9 @@ body .ui-tooltip {
}
.ui-datepicker td a.ui-state-active {
color: #fff;
border-color: #0286ac;
text-shadow: 0px 1px 1px #00516e;
background: #00acd4;
border-color: #0286ac !important;
text-shadow: 0px 1px 1px #00516e !important;
background: #00acd4 !important;
background: -moz-linear-gradient(top, #00acd4 0%, #008fc7 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#00acd4), color-stop(100%,#008fc7));
background: -o-linear-gradient(top, #00acd4 0%, #008fc7 100%);
@ -1494,4 +1498,9 @@ body .ui-tooltip {
.ui-datepicker .ui-state-highlight {
color: #0081c2;
}
.ui-datepicker td.ui-datepicker-days-cell-over a.ui-state-default {
color: #fff;
border-color: rgba(73,180,210,0.7);
background: rgba(73,180,210,0.7);
text-shadow: 0px 1px 1px #666;
}

Loading…
Cancel
Save