Aleksander Machniak
2016-02-29 74a7dd553c2780315684551268f62b1fd2efc35f
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));