0
|
1 /*! jQuery UI Accessible Datepicker extension
|
|
2 * (to be appended to jquery-ui-*.custom.min.js)
|
|
3 *
|
|
4 * @licstart The following is the entire license notice for the
|
|
5 * JavaScript code in this page.
|
|
6 *
|
|
7 * Copyright 2014 Kolab Systems AG
|
|
8 *
|
|
9 * The JavaScript code in this page is free software: you can
|
|
10 * redistribute it and/or modify it under the terms of the GNU
|
|
11 * General Public License (GNU GPL) as published by the Free Software
|
|
12 * Foundation, either version 3 of the License, or (at your option)
|
|
13 * any later version. The code is distributed WITHOUT ANY WARRANTY;
|
|
14 * without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
15 * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
|
|
16 *
|
|
17 * As additional permission under GNU GPL version 3 section 7, you
|
|
18 * may distribute non-source (e.g., minimized or compacted) forms of
|
|
19 * that code without the copy of the GNU GPL normally required by
|
|
20 * section 4, provided you include this license notice and a URL
|
|
21 * through which recipients can access the Corresponding Source.
|
|
22 *
|
|
23 * @licend The above is the entire license notice
|
|
24 * for the JavaScript code in this page.
|
|
25 */
|
|
26
|
|
27 (function($, undefined) {
|
|
28
|
|
29 // references to super class methods
|
|
30 var __newInst = $.datepicker._newInst;
|
|
31 var __updateDatepicker = $.datepicker._updateDatepicker;
|
|
32 var __connectDatepicker = $.datepicker._connectDatepicker;
|
|
33 var __showDatepicker = $.datepicker._showDatepicker;
|
|
34 var __hideDatepicker = $.datepicker._hideDatepicker;
|
|
35
|
|
36 // "extend" singleton instance methods
|
|
37 $.extend($.datepicker, {
|
|
38
|
|
39 /* Create a new instance object */
|
|
40 _newInst: function(target, inline) {
|
|
41 var that = this, inst = __newInst.call(this, target, inline);
|
|
42
|
|
43 if (inst.inline) {
|
|
44 // attach keyboard event handler
|
|
45 inst.dpDiv.on('keydown.datepicker', '.ui-datepicker-calendar', function(event) {
|
|
46 // we're only interested navigation keys
|
|
47 if ($.inArray(event.keyCode, [ 13, 33, 34, 35, 36, 37, 38, 39, 40]) == -1) {
|
|
48 return;
|
|
49 }
|
|
50 event.stopPropagation();
|
|
51 event.preventDefault();
|
|
52 inst._hasfocus = true;
|
|
53
|
|
54 var activeCell;
|
|
55 switch (event.keyCode) {
|
|
56 case $.ui.keyCode.ENTER:
|
|
57 if ((activeCell = $('.' + that._dayOverClass, inst.dpDiv).get(0) || $('.' + that._currentClass, inst.dpDiv).get(0))) {
|
|
58 that._selectDay(inst.input, inst.selectedMonth, inst.selectedYear, activeCell);
|
|
59 }
|
|
60 break;
|
|
61
|
|
62 case $.ui.keyCode.PAGE_UP:
|
|
63 that._adjustDate(inst.input, -that._get(inst, 'stepMonths'), 'M');
|
|
64 break;
|
|
65 case $.ui.keyCode.PAGE_DOWN:
|
|
66 that._adjustDate(inst.input, that._get(inst, 'stepMonths'), 'M');
|
|
67 break;
|
|
68
|
|
69 default:
|
|
70 return that._cursorKeydown(event, inst);
|
|
71 }
|
|
72 })
|
|
73 .attr('role', 'region')
|
|
74 .attr('aria-labelledby', inst.id + '-dp-title');
|
|
75 }
|
|
76 else {
|
|
77 var widgetId = inst.dpDiv.attr('id') || inst.id + '-dp-widget';
|
|
78 inst.dpDiv.attr('id', widgetId)
|
|
79 .attr('aria-hidden', 'true')
|
|
80 .attr('aria-labelledby', inst.id + '-dp-title');
|
|
81
|
|
82 $(inst.input).attr('aria-haspopup', 'true')
|
|
83 .attr('aria-expanded', 'false')
|
|
84 .attr('aria-owns', widgetId);
|
|
85 }
|
|
86
|
|
87 return inst;
|
|
88 },
|
|
89
|
|
90 /* Attach the date picker to an input field */
|
|
91 _connectDatepicker: function(target, inst) {
|
|
92 __connectDatepicker.call(this, target, inst);
|
|
93
|
|
94 var that = this;
|
|
95
|
|
96 // register additional keyboard events to control date selection with cursor keys
|
|
97 $(target).unbind('keydown.datepicker-extended').bind('keydown.datepicker-extended', function(event) {
|
|
98 var inc = 1;
|
|
99 switch (event.keyCode) {
|
|
100 case 109:
|
|
101 case 173:
|
|
102 case 189: // "minus"
|
|
103 inc = -1;
|
|
104 case 61:
|
|
105 case 107:
|
|
106 case 187: // "plus"
|
|
107 // do nothing if the input does not contain full date string
|
|
108 if (this.value.length < that._formatDate(inst, inst.selectedDay, inst.selectedMonth, inst.selectedYear).length) {
|
|
109 return true;
|
|
110 }
|
|
111 that._adjustInstDate(inst, inc, 'D');
|
|
112 that._selectDateRC(target, that._formatDate(inst, inst.selectedDay, inst.selectedMonth, inst.selectedYear));
|
|
113 return false;
|
|
114
|
|
115 case $.ui.keyCode.UP:
|
|
116 case $.ui.keyCode.DOWN:
|
|
117 // unfold datepicker if not visible
|
|
118 if ($.datepicker._lastInput !== target && !$.datepicker._isDisabledDatepicker(target)) {
|
|
119 that._showDatepicker(event);
|
|
120 event.stopPropagation();
|
|
121 event.preventDefault();
|
|
122 return false;
|
|
123 }
|
|
124
|
|
125 default:
|
|
126 if (!$.datepicker._isDisabledDatepicker(target) && !event.ctrlKey && !event.metaKey) {
|
|
127 return that._cursorKeydown(event, inst);
|
|
128 }
|
|
129 }
|
|
130 })
|
|
131 .attr('autocomplete', 'off');
|
|
132 },
|
|
133
|
|
134 /* Handle keyboard event on datepicker widget */
|
|
135 _cursorKeydown: function(event, inst) {
|
|
136 inst._keyEvent = true;
|
|
137
|
|
138 var isRTL = inst.dpDiv.hasClass('ui-datepicker-rtl');
|
|
139
|
|
140 switch (event.keyCode) {
|
|
141 case $.ui.keyCode.LEFT:
|
|
142 this._adjustDate(inst.input, (isRTL ? +1 : -1), 'D');
|
|
143 break;
|
|
144 case $.ui.keyCode.RIGHT:
|
|
145 this._adjustDate(inst.input, (isRTL ? -1 : +1), 'D');
|
|
146 break;
|
|
147 case $.ui.keyCode.UP:
|
|
148 this._adjustDate(inst.input, -7, 'D');
|
|
149 break;
|
|
150 case $.ui.keyCode.DOWN:
|
|
151 this._adjustDate(inst.input, +7, 'D');
|
|
152 break;
|
|
153 case $.ui.keyCode.HOME:
|
|
154 // TODO: jump to first of month
|
|
155 break;
|
|
156 case $.ui.keyCode.END:
|
|
157 // TODO: jump to end of month
|
|
158 break;
|
|
159 }
|
|
160
|
|
161 return true;
|
|
162 },
|
|
163
|
|
164 /* Pop-up the date picker for a given input field */
|
|
165 _showDatepicker: function(input) {
|
|
166 input = input.target || input;
|
|
167 __showDatepicker.call(this, input);
|
|
168
|
|
169 var inst = $.datepicker._getInst(input);
|
|
170 if (inst && $.datepicker._datepickerShowing) {
|
|
171 inst.dpDiv.attr('aria-hidden', 'false');
|
|
172 $(input).attr('aria-expanded', 'true');
|
|
173 }
|
|
174 },
|
|
175
|
|
176 /* Hide the date picker from view */
|
|
177 _hideDatepicker: function(input) {
|
|
178 __hideDatepicker.call(this, input);
|
|
179
|
|
180 var inst = this._curInst;;
|
|
181 if (inst && !$.datepicker._datepickerShowing) {
|
|
182 inst.dpDiv.attr('aria-hidden', 'true');
|
|
183 $(inst.input).attr('aria-expanded', 'false');
|
|
184 }
|
|
185 },
|
|
186
|
|
187 /* Render the date picker content */
|
|
188 _updateDatepicker: function(inst) {
|
|
189 __updateDatepicker.call(this, inst);
|
|
190
|
|
191 var activeCell = $('.' + this._dayOverClass, inst.dpDiv).get(0) || $('.' + this._currentClass, inst.dpDiv).get(0);
|
|
192 if (activeCell) {
|
|
193 activeCell = $(activeCell);
|
|
194 activeCell.attr('id', inst.id + '-day-' + activeCell.text());
|
|
195 }
|
|
196
|
|
197 // allow focus on main container only
|
|
198 inst.dpDiv.find('.ui-datepicker-calendar')
|
|
199 .attr('tabindex', inst.inline ? '0' : '-1')
|
|
200 .attr('role', 'grid')
|
|
201 .attr('aria-readonly', 'true')
|
|
202 .attr('aria-activedescendant', activeCell ? activeCell.attr('id') : '')
|
|
203 .find('td').attr('role', 'gridcell').attr('aria-selected', 'false')
|
|
204 .find('a').attr('tabindex', '-1');
|
|
205
|
|
206 $('.ui-datepicker-current-day', inst.dpDiv).attr('aria-selected', 'true');
|
|
207
|
|
208 inst.dpDiv.find('.ui-datepicker-title')
|
|
209 .attr('id', inst.id + '-dp-title')
|
|
210
|
|
211 // set focus again after update
|
|
212 if (inst._hasfocus) {
|
|
213 inst.dpDiv.find('.ui-datepicker-calendar').focus();
|
|
214 inst._hasfocus = false;
|
|
215 }
|
|
216 },
|
|
217
|
|
218 _selectDateRC: function(id, dateStr) {
|
|
219 var target = $(id), inst = this._getInst(target[0]);
|
|
220
|
|
221 dateStr = (dateStr != null ? dateStr : this._formatDate(inst));
|
|
222 if (inst.input) {
|
|
223 inst.input.val(dateStr);
|
|
224 }
|
|
225 this._updateAlternate(inst);
|
|
226 if (inst.input) {
|
|
227 inst.input.trigger("change"); // fire the change event
|
|
228 }
|
|
229 if (inst.inline) {
|
|
230 this._updateDatepicker(inst);
|
|
231 }
|
|
232 }
|
|
233 });
|
|
234
|
|
235 }(jQuery));
|