commit | author | age
|
2efb5c
|
1 |
/*! jQuery UI Accessible Datepicker extension |
TB |
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 { |
dea516
|
77 |
var widgetId = inst.dpDiv.attr('id') || inst.id + '-dp-widget'; |
TB |
78 |
inst.dpDiv.attr('id', widgetId) |
2efb5c
|
79 |
.attr('aria-hidden', 'true') |
TB |
80 |
.attr('aria-labelledby', inst.id + '-dp-title'); |
|
81 |
|
|
82 |
$(inst.input).attr('aria-haspopup', 'true') |
|
83 |
.attr('aria-expanded', 'false') |
dea516
|
84 |
.attr('aria-owns', widgetId); |
2efb5c
|
85 |
} |
TB |
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 |
686ff4
|
97 |
$(target).unbind('keydown.datepicker-extended').bind('keydown.datepicker-extended', function(event) { |
2efb5c
|
98 |
var inc = 1; |
TB |
99 |
switch (event.keyCode) { |
|
100 |
case 109: |
686ff4
|
101 |
case 173: |
2efb5c
|
102 |
case 189: // "minus" |
TB |
103 |
inc = -1; |
686ff4
|
104 |
case 61: |
2efb5c
|
105 |
case 107: |
TB |
106 |
case 187: // "plus" |
686ff4
|
107 |
// do nothing if the input does not contain full date string |
AM |
108 |
if (this.value.length < that._formatDate(inst, inst.selectedDay, inst.selectedMonth, inst.selectedYear).length) { |
|
109 |
return true; |
|
110 |
} |
2efb5c
|
111 |
that._adjustInstDate(inst, inc, 'D'); |
686ff4
|
112 |
that._selectDateRC(target, that._formatDate(inst, inst.selectedDay, inst.selectedMonth, inst.selectedYear)); |
AM |
113 |
return false; |
2efb5c
|
114 |
|
TB |
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 |
}) |
74a7dd
|
131 |
// fix https://bugs.jqueryui.com/ticket/8593 |
AM |
132 |
.click(function (event) { that._showDatepicker(event); }) |
2efb5c
|
133 |
.attr('autocomplete', 'off'); |
TB |
134 |
}, |
|
135 |
|
|
136 |
/* Handle keyboard event on datepicker widget */ |
|
137 |
_cursorKeydown: function(event, inst) { |
|
138 |
inst._keyEvent = true; |
|
139 |
|
|
140 |
var isRTL = inst.dpDiv.hasClass('ui-datepicker-rtl'); |
|
141 |
|
|
142 |
switch (event.keyCode) { |
|
143 |
case $.ui.keyCode.LEFT: |
|
144 |
this._adjustDate(inst.input, (isRTL ? +1 : -1), 'D'); |
|
145 |
break; |
|
146 |
case $.ui.keyCode.RIGHT: |
|
147 |
this._adjustDate(inst.input, (isRTL ? -1 : +1), 'D'); |
|
148 |
break; |
|
149 |
case $.ui.keyCode.UP: |
|
150 |
this._adjustDate(inst.input, -7, 'D'); |
|
151 |
break; |
|
152 |
case $.ui.keyCode.DOWN: |
|
153 |
this._adjustDate(inst.input, +7, 'D'); |
|
154 |
break; |
|
155 |
case $.ui.keyCode.HOME: |
|
156 |
// TODO: jump to first of month |
|
157 |
break; |
|
158 |
case $.ui.keyCode.END: |
|
159 |
// TODO: jump to end of month |
|
160 |
break; |
|
161 |
} |
|
162 |
|
|
163 |
return true; |
|
164 |
}, |
|
165 |
|
|
166 |
/* Pop-up the date picker for a given input field */ |
|
167 |
_showDatepicker: function(input) { |
|
168 |
input = input.target || input; |
|
169 |
__showDatepicker.call(this, input); |
|
170 |
|
|
171 |
var inst = $.datepicker._getInst(input); |
|
172 |
if (inst && $.datepicker._datepickerShowing) { |
|
173 |
inst.dpDiv.attr('aria-hidden', 'false'); |
|
174 |
$(input).attr('aria-expanded', 'true'); |
|
175 |
} |
|
176 |
}, |
|
177 |
|
|
178 |
/* Hide the date picker from view */ |
|
179 |
_hideDatepicker: function(input) { |
|
180 |
__hideDatepicker.call(this, input); |
|
181 |
|
74a7dd
|
182 |
var inst = this._curInst; |
2efb5c
|
183 |
if (inst && !$.datepicker._datepickerShowing) { |
TB |
184 |
inst.dpDiv.attr('aria-hidden', 'true'); |
|
185 |
$(inst.input).attr('aria-expanded', 'false'); |
|
186 |
} |
|
187 |
}, |
|
188 |
|
|
189 |
/* Render the date picker content */ |
|
190 |
_updateDatepicker: function(inst) { |
|
191 |
__updateDatepicker.call(this, inst); |
|
192 |
|
|
193 |
var activeCell = $('.' + this._dayOverClass, inst.dpDiv).get(0) || $('.' + this._currentClass, inst.dpDiv).get(0); |
|
194 |
if (activeCell) { |
|
195 |
activeCell = $(activeCell); |
|
196 |
activeCell.attr('id', inst.id + '-day-' + activeCell.text()); |
|
197 |
} |
|
198 |
|
|
199 |
// allow focus on main container only |
|
200 |
inst.dpDiv.find('.ui-datepicker-calendar') |
|
201 |
.attr('tabindex', inst.inline ? '0' : '-1') |
|
202 |
.attr('role', 'grid') |
|
203 |
.attr('aria-readonly', 'true') |
5a2838
|
204 |
.attr('aria-activedescendant', activeCell ? activeCell.attr('id') : '') |
2efb5c
|
205 |
.find('td').attr('role', 'gridcell').attr('aria-selected', 'false') |
TB |
206 |
.find('a').attr('tabindex', '-1'); |
|
207 |
|
|
208 |
$('.ui-datepicker-current-day', inst.dpDiv).attr('aria-selected', 'true'); |
|
209 |
|
|
210 |
inst.dpDiv.find('.ui-datepicker-title') |
|
211 |
.attr('id', inst.id + '-dp-title') |
|
212 |
|
|
213 |
// set focus again after update |
|
214 |
if (inst._hasfocus) { |
|
215 |
inst.dpDiv.find('.ui-datepicker-calendar').focus(); |
|
216 |
inst._hasfocus = false; |
|
217 |
} |
686ff4
|
218 |
}, |
2efb5c
|
219 |
|
686ff4
|
220 |
_selectDateRC: function(id, dateStr) { |
AM |
221 |
var target = $(id), inst = this._getInst(target[0]); |
|
222 |
|
|
223 |
dateStr = (dateStr != null ? dateStr : this._formatDate(inst)); |
|
224 |
if (inst.input) { |
|
225 |
inst.input.val(dateStr); |
|
226 |
} |
|
227 |
this._updateAlternate(inst); |
|
228 |
if (inst.input) { |
|
229 |
inst.input.trigger("change"); // fire the change event |
|
230 |
} |
|
231 |
if (inst.inline) { |
|
232 |
this._updateDatepicker(inst); |
|
233 |
} |
|
234 |
} |
2efb5c
|
235 |
}); |
TB |
236 |
|
686ff4
|
237 |
}(jQuery)); |