/*! 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('keydown.datepicker-extended').bind('keydown.datepicker-extended', function(event) { var inc = 1; switch (event.keyCode) { case 109: case 173: case 189: // "minus" inc = -1; case 61: case 107: case 187: // "plus" // do nothing if the input does not contain full date string if (this.value.length < that._formatDate(inst, inst.selectedDay, inst.selectedMonth, inst.selectedYear).length) { return true; } that._adjustInstDate(inst, inc, 'D'); that._selectDateRC(target, that._formatDate(inst, inst.selectedDay, inst.selectedMonth, inst.selectedYear)); return false; 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; } }, _selectDateRC: function(id, dateStr) { var target = $(id), inst = this._getInst(target[0]); dateStr = (dateStr != null ? dateStr : this._formatDate(inst)); if (inst.input) { inst.input.val(dateStr); } this._updateAlternate(inst); if (inst.input) { inst.input.trigger("change"); // fire the change event } if (inst.inline) { this._updateDatepicker(inst); } } }); }(jQuery));