Ticket #31644: DateTimeShortcuts.js

File DateTimeShortcuts.js, 20.1 KB (added by Alton Ray Carlson II, 4 years ago)
Line 
1/*global Calendar, findPosX, findPosY, get_format, gettext, gettext_noop, interpolate, ngettext, quickElement*/
2// Inserts shortcut buttons after all of the following:
3// <input type="text" class="vDateField">
4// <input type="text" class="vTimeField">
5(function() {
6 'use strict';
7 var DateTimeShortcuts = {
8 calendars: [],
9 calendarInputs: [],
10 clockInputs: [],
11 clockHours: {
12 default_: [
13 [gettext_noop('Now'), -1],
14 [gettext_noop('Midnight'), 0],
15 [gettext_noop('6 a.m.'), 6],
16 [gettext_noop('Noon'), 12],
17 [gettext_noop('6 p.m.'), 18]
18 ]
19 },
20 dismissClockFunc: [],
21 dismissCalendarFunc: [],
22 calendarDivName1: 'calendarbox', // name of calendar <div> that gets toggled
23 calendarDivName2: 'calendarin', // name of <div> that contains calendar
24 calendarLinkName: 'calendarlink', // name of the link that is used to toggle
25 clockDivName: 'clockbox', // name of clock <div> that gets toggled
26 clockLinkName: 'clocklink', // name of the link that is used to toggle
27 shortCutsClass: 'datetimeshortcuts', // class of the clock and cal shortcuts
28 timezoneWarningClass: 'timezonewarning', // class of the warning for timezone mismatch
29 timezoneOffset: 0,
30 init: function() {
31 var body = document.getElementsByTagName('body')[0];
32 var serverOffset = body.getAttribute('data-admin-utc-offset');
33 if (serverOffset) {
34 var localOffset = new Date().getTimezoneOffset() * -60;
35 DateTimeShortcuts.timezoneOffset = localOffset - serverOffset;
36 }
37
38 var inputs = document.getElementsByTagName('input');
39 for (var i = 0; i < inputs.length; i++) {
40 var inp = inputs[i];
41 if (inp.getAttribute('type') === 'text' && inp.className.match(/vTimeField/)) {
42 DateTimeShortcuts.addClock(inp);
43 DateTimeShortcuts.addTimezoneWarning(inp);
44 }
45 else if (inp.getAttribute('type') === 'text' && inp.className.match(/vDateField/)) {
46 DateTimeShortcuts.addCalendar(inp);
47 DateTimeShortcuts.addTimezoneWarning(inp);
48 }
49 }
50 },
51 // Return the current time while accounting for the server timezone.
52 now: function() {
53 var body = document.getElementsByTagName('body')[0];
54 var serverOffset = body.getAttribute('data-admin-utc-offset');
55 if (serverOffset) {
56 var localNow = new Date();
57 var localOffset = localNow.getTimezoneOffset() * -60;
58 localNow.setTime(localNow.getTime() + 1000 * (serverOffset - localOffset));
59 return localNow;
60 } else {
61 return new Date();
62 }
63 },
64 // Add a warning when the time zone in the browser and backend do not match.
65 addTimezoneWarning: function(inp) {
66 var warningClass = DateTimeShortcuts.timezoneWarningClass;
67 var timezoneOffset = DateTimeShortcuts.timezoneOffset / 3600;
68
69 // Only warn if there is a time zone mismatch.
70 if (!timezoneOffset) {
71 return;
72 }
73
74 // Check if warning is already there.
75 if (inp.parentNode.querySelectorAll('.' + warningClass).length) {
76 return;
77 }
78
79 var message;
80 if (timezoneOffset > 0) {
81 message = ngettext(
82 'Note: You are %s hour ahead of server time.',
83 'Note: You are %s hours ahead of server time.',
84 timezoneOffset
85 );
86 }
87 else {
88 timezoneOffset *= -1;
89 message = ngettext(
90 'Note: You are %s hour behind server time.',
91 'Note: You are %s hours behind server time.',
92 timezoneOffset
93 );
94 }
95 message = interpolate(message, [timezoneOffset]);
96
97 var warning = document.createElement('span');
98 warning.className = warningClass;
99 warning.textContent = message;
100 inp.parentNode.appendChild(document.createElement('br'));
101 inp.parentNode.appendChild(warning);
102 },
103 // Add clock widget to a given field
104 addClock: function(inp) {
105 var num = DateTimeShortcuts.clockInputs.length;
106 DateTimeShortcuts.clockInputs[num] = inp;
107 DateTimeShortcuts.dismissClockFunc[num] = function() { DateTimeShortcuts.dismissClock(num); return true; };
108
109 // Shortcut links (clock icon and "Now" link)
110 var shortcuts_span = document.createElement('span');
111 shortcuts_span.className = DateTimeShortcuts.shortCutsClass;
112 inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
113 var now_link = document.createElement('a');
114 now_link.setAttribute('href', "#");
115 now_link.textContent = gettext('Now');
116 now_link.addEventListener('click', function(e) {
117 e.preventDefault();
118 DateTimeShortcuts.handleClockQuicklink(num, -1);
119 });
120 var clock_link = document.createElement('a');
121 clock_link.setAttribute('href', '#');
122 clock_link.id = DateTimeShortcuts.clockLinkName + num;
123 clock_link.addEventListener('click', function(e) {
124 e.preventDefault();
125 // avoid triggering the document click handler to dismiss the clock
126 e.stopPropagation();
127 DateTimeShortcuts.openClock(num);
128 });
129
130 quickElement(
131 'span', clock_link, '',
132 'class', 'clock-icon',
133 'title', gettext('Choose a Time')
134 );
135 shortcuts_span.appendChild(document.createTextNode('\u00A0'));
136 shortcuts_span.appendChild(now_link);
137 shortcuts_span.appendChild(document.createTextNode('\u00A0|\u00A0'));
138 shortcuts_span.appendChild(clock_link);
139
140 // Create clock link div
141 //
142 // Markup looks like:
143 // <div id="clockbox1" class="clockbox module">
144 // <h2>Choose a time</h2>
145 // <ul class="timelist">
146 // <li><a href="#">Now</a></li>
147 // <li><a href="#">Midnight</a></li>
148 // <li><a href="#">6 a.m.</a></li>
149 // <li><a href="#">Noon</a></li>
150 // <li><a href="#">6 p.m.</a></li>
151 // </ul>
152 // <p class="calendar-cancel"><a href="#">Cancel</a></p>
153 // </div>
154
155 var clock_box = document.createElement('div');
156 clock_box.style.display = 'none';
157 clock_box.style.position = 'absolute';
158 clock_box.className = 'clockbox module';
159 clock_box.setAttribute('id', DateTimeShortcuts.clockDivName + num);
160 document.body.appendChild(clock_box);
161 clock_box.addEventListener('click', function(e) { e.stopPropagation(); });
162
163 quickElement('h2', clock_box, gettext('Choose a time'));
164 var time_list = quickElement('ul', clock_box);
165 time_list.className = 'timelist';
166 // The list of choices can be overridden in JavaScript like this:
167 // DateTimeShortcuts.clockHours.name = [['3 a.m.', 3]];
168 // where name is the name attribute of the <input>.
169 var name = typeof DateTimeShortcuts.clockHours[inp.name] === 'undefined' ? 'default_' : inp.name;
170 DateTimeShortcuts.clockHours[name].forEach(function(element) {
171 var time_link = quickElement('a', quickElement('li', time_list), gettext(element[0]), 'href', '#');
172 time_link.addEventListener('click', function(e) {
173 e.preventDefault();
174 DateTimeShortcuts.handleClockQuicklink(num, element[1]);
175 });
176 });
177
178 var cancel_p = quickElement('p', clock_box);
179 cancel_p.className = 'calendar-cancel';
180 var cancel_link = quickElement('a', cancel_p, gettext('Cancel'), 'href', '#');
181 cancel_link.addEventListener('click', function(e) {
182 e.preventDefault();
183 DateTimeShortcuts.dismissClock(num);
184 });
185
186 document.addEventListener('keyup', function(event) {
187 if (event.which === 27) {
188 // ESC key closes popup
189 DateTimeShortcuts.dismissClock(num);
190 event.preventDefault();
191 }
192 });
193 },
194 openClock: function(num) {
195 var clock_box = document.getElementById(DateTimeShortcuts.clockDivName + num);
196 var clock_link = document.getElementById(DateTimeShortcuts.clockLinkName + num);
197
198 // Recalculate the clockbox position
199 // is it left-to-right or right-to-left layout ?
200 if (window.getComputedStyle(document.body).direction !== 'rtl') {
201 clock_box.style.left = findPosX(clock_link) + 17 + 'px';
202 }
203 else {
204 // since style's width is in em, it'd be tough to calculate
205 // px value of it. let's use an estimated px for now
206 // TODO: IE returns wrong value for findPosX when in rtl mode
207 // (it returns as it was left aligned), needs to be fixed.
208 clock_box.style.left = findPosX(clock_link) - 110 + 'px';
209 }
210 clock_box.style.top = Math.max(0, findPosY(clock_link) - 30) + 'px';
211
212 // Show the clock box
213 clock_box.style.display = 'block';
214 document.addEventListener('click', DateTimeShortcuts.dismissClockFunc[num]);
215 },
216 dismissClock: function(num) {
217 document.getElementById(DateTimeShortcuts.clockDivName + num).style.display = 'none';
218 document.removeEventListener('click', DateTimeShortcuts.dismissClockFunc[num]);
219 },
220 handleClockQuicklink: function(num, val) {
221 var d;
222 if (val === -1) {
223 d = DateTimeShortcuts.now();
224 }
225 else {
226 d = new Date(1970, 1, 1, val, 0, 0, 0);
227 }
228 DateTimeShortcuts.clockInputs[num].value = d.strftime(get_format('TIME_INPUT_FORMATS')[0]);
229 DateTimeShortcuts.clockInputs[num].focus();
230 DateTimeShortcuts.dismissClock(num);
231 },
232 // Add calendar widget to a given field.
233 addCalendar: function(inp) {
234 var num = DateTimeShortcuts.calendars.length;
235
236 DateTimeShortcuts.calendarInputs[num] = inp;
237 DateTimeShortcuts.dismissCalendarFunc[num] = function() { DateTimeShortcuts.dismissCalendar(num); return true; };
238
239 // Shortcut links (calendar icon and "Today" link)
240 var shortcuts_span = document.createElement('span');
241 shortcuts_span.className = DateTimeShortcuts.shortCutsClass;
242 inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
243 var today_link = document.createElement('a');
244 today_link.setAttribute('href', '#');
245 today_link.appendChild(document.createTextNode(gettext('Today')));
246 today_link.addEventListener('click', function(e) {
247 e.preventDefault();
248 DateTimeShortcuts.handleCalendarQuickLink(num, 0);
249 });
250 var cal_link = document.createElement('a');
251 cal_link.setAttribute('href', '#');
252 cal_link.id = DateTimeShortcuts.calendarLinkName + num;
253 cal_link.addEventListener('click', function(e) {
254 e.preventDefault();
255 // avoid triggering the document click handler to dismiss the calendar
256 e.stopPropagation();
257 DateTimeShortcuts.openCalendar(num);
258 });
259 quickElement(
260 'span', cal_link, '',
261 'class', 'date-icon',
262 'title', gettext('Choose a Date')
263 );
264 shortcuts_span.appendChild(document.createTextNode('\u00A0'));
265 shortcuts_span.appendChild(today_link);
266 shortcuts_span.appendChild(document.createTextNode('\u00A0|\u00A0'));
267 shortcuts_span.appendChild(cal_link);
268
269 // Create calendarbox div.
270 //
271 // Markup looks like:
272 //
273 // <div id="calendarbox3" class="calendarbox module">
274 // <h2>
275 // <a href="#" class="link-previous">&lsaquo;</a>
276 // <a href="#" class="link-next">&rsaquo;</a> February 2003
277 // </h2>
278 // <div class="calendar" id="calendarin3">
279 // <!-- (cal) -->
280 // </div>
281 // <div class="calendar-shortcuts">
282 // <a href="#">Yesterday</a> | <a href="#">Today</a> | <a href="#">Tomorrow</a>
283 // </div>
284 // <p class="calendar-cancel"><a href="#">Cancel</a></p>
285 // </div>
286 var cal_box = document.createElement('div');
287 cal_box.style.display = 'none';
288 cal_box.style.position = 'absolute';
289 cal_box.className = 'calendarbox module';
290 cal_box.setAttribute('id', DateTimeShortcuts.calendarDivName1 + num);
291 document.body.appendChild(cal_box);
292 cal_box.addEventListener('click', function(e) { e.stopPropagation(); });
293
294 // next-prev links
295 var cal_nav = quickElement('div', cal_box);
296 var cal_nav_prev = quickElement('a', cal_nav, '<', 'href', '#');
297 cal_nav_prev.className = 'calendarnav-previous';
298 cal_nav_prev.addEventListener('click', function(e) {
299 e.preventDefault();
300 DateTimeShortcuts.drawPrev(num);
301 });
302
303 var cal_nav_next = quickElement('a', cal_nav, '>', 'href', '#');
304 cal_nav_next.className = 'calendarnav-next';
305 cal_nav_next.addEventListener('click', function(e) {
306 e.preventDefault();
307 DateTimeShortcuts.drawNext(num);
308 });
309
310 // main box
311 var cal_main = quickElement('div', cal_box, '', 'id', DateTimeShortcuts.calendarDivName2 + num);
312 cal_main.className = 'calendar';
313 DateTimeShortcuts.calendars[num] = new Calendar(DateTimeShortcuts.calendarDivName2 + num, DateTimeShortcuts.handleCalendarCallback(num));
314 DateTimeShortcuts.calendars[num].drawCurrent();
315
316 // calendar shortcuts
317 var shortcuts = quickElement('div', cal_box);
318 shortcuts.className = 'calendar-shortcuts';
319 var day_link = quickElement('a', shortcuts, gettext('Yesterday'), 'href', '#');
320 day_link.addEventListener('click', function(e) {
321 e.preventDefault();
322 DateTimeShortcuts.handleCalendarQuickLink(num, -1);
323 });
324 shortcuts.appendChild(document.createTextNode('\u00A0|\u00A0'));
325 day_link = quickElement('a', shortcuts, gettext('Today'), 'href', '#');
326 day_link.addEventListener('click', function(e) {
327 e.preventDefault();
328 DateTimeShortcuts.handleCalendarQuickLink(num, 0);
329 });
330 shortcuts.appendChild(document.createTextNode('\u00A0|\u00A0'));
331 day_link = quickElement('a', shortcuts, gettext('Tomorrow'), 'href', '#');
332 day_link.addEventListener('click', function(e) {
333 e.preventDefault();
334 DateTimeShortcuts.handleCalendarQuickLink(num, +1);
335 });
336
337 // cancel bar
338 var cancel_p = quickElement('p', cal_box);
339 cancel_p.className = 'calendar-cancel';
340 var cancel_link = quickElement('a', cancel_p, gettext('Cancel'), 'href', '#');
341 cancel_link.addEventListener('click', function(e) {
342 e.preventDefault();
343 DateTimeShortcuts.dismissCalendar(num);
344 });
345 document.addEventListener('keyup', function(event) {
346 if (event.which === 27) {
347 // ESC key closes popup
348 DateTimeShortcuts.dismissCalendar(num);
349 event.preventDefault();
350 }
351 });
352 },
353 openCalendar: function(num) {
354 var cal_box = document.getElementById(DateTimeShortcuts.calendarDivName1 + num);
355 var cal_link = document.getElementById(DateTimeShortcuts.calendarLinkName + num);
356 var inp = DateTimeShortcuts.calendarInputs[num];
357
358 // Determine if the current value in the input has a valid date.
359 // If so, draw the calendar with that date's year and month.
360 if (inp.value) {
361 var format = get_format('DATE_INPUT_FORMATS')[0];
362 var selected = inp.value.strptime(format);
363 var year = selected.getUTCFullYear();
364 var month = selected.getUTCMonth() + 1;
365 var re = /\d{4}/;
366 if (re.test(year.toString()) && month >= 1 && month <= 12) {
367 DateTimeShortcuts.calendars[num].drawDate(month, year, selected);
368 }
369 }
370
371 // Recalculate the clockbox position
372 // is it left-to-right or right-to-left layout ?
373 if (window.getComputedStyle(document.body).direction !== 'rtl') {
374 cal_box.style.left = findPosX(cal_link) + 17 + 'px';
375 }
376 else {
377 // since style's width is in em, it'd be tough to calculate
378 // px value of it. let's use an estimated px for now
379 // TODO: IE returns wrong value for findPosX when in rtl mode
380 // (it returns as it was left aligned), needs to be fixed.
381 cal_box.style.left = findPosX(cal_link) - 180 + 'px';
382 }
383 cal_box.style.top = Math.max(0, findPosY(cal_link) - 75) + 'px';
384
385 cal_box.style.display = 'block';
386 document.addEventListener('click', DateTimeShortcuts.dismissCalendarFunc[num]);
387 },
388 dismissCalendar: function(num) {
389 document.getElementById(DateTimeShortcuts.calendarDivName1 + num).style.display = 'none';
390 document.removeEventListener('click', DateTimeShortcuts.dismissCalendarFunc[num]);
391 },
392 drawPrev: function(num) {
393 DateTimeShortcuts.calendars[num].drawPreviousMonth();
394 },
395 drawNext: function(num) {
396 DateTimeShortcuts.calendars[num].drawNextMonth();
397 },
398 handleCalendarCallback: function(num) {
399 var format = get_format('DATE_INPUT_FORMATS')[0];
400 // the format needs to be escaped a little
401 format = format.replace('\\', '\\\\')
402 .replace('\r', '\\r')
403 .replace('\n', '\\n')
404 .replace('\t', '\\t')
405 .replace("'", "\\'");
406 return function(y, m, d) {
407 DateTimeShortcuts.calendarInputs[num].value = new Date(y, m - 1, d).strftime(format);
408 DateTimeShortcuts.calendarInputs[num].focus();
409 document.getElementById(DateTimeShortcuts.calendarDivName1 + num).style.display = 'none';
410 if ("createEvent" in document) {
411 var evt = document.createEvent("HTMLEvents");
412 evt.initEvent("change", true, true);
413 DateTimeShortcuts.calendarInputs[num].dispatchEvent(evt);
414 }
415 else
416 DateTimeShortcuts.calendarInputs[num].fireEvent("onchange");
417 };
418 },
419 handleCalendarQuickLink: function(num, offset) {
420 var d = DateTimeShortcuts.now();
421 d.setDate(d.getDate() + offset);
422 DateTimeShortcuts.calendarInputs[num].value = d.strftime(get_format('DATE_INPUT_FORMATS')[0]);
423 DateTimeShortcuts.calendarInputs[num].focus();
424 DateTimeShortcuts.dismissCalendar(num);
425 }
426 };
427
428 window.addEventListener('load', DateTimeShortcuts.init);
429 window.DateTimeShortcuts = DateTimeShortcuts;
430})();
Back to Top