blob: 859bd7593ee1e1060f5bd00ad9246a6fbc729a42 [file] [log] [blame]
Helgee8a39b12026-04-16 13:37:09 +02001/**
2 * Date picker for the
3 * Virtual Collection builder.
4 *
5 * @author Nils Diewald
6 */
7
8import KorAP from '../util/util.js';
9
10const validDateMatchRE = new RegExp("^(?:[lg]?eq|ne)$");
11const validDateRE = new RegExp("^(?:\\d{4})(?:-\\d\\d(?:-\\d\\d)?)?$");
12
13KorAP._validDateMatchRE = validDateMatchRE;
14KorAP._validDateRE = validDateRE;
15
16/*
17 * Localizations
18 */
19const loc = KorAP.Locale;
20loc.WDAY = loc.WDAY || [
21 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'
22];
23loc.MONTH = loc.MONTH || [
24 'January', 'February', 'March', 'April',
25 'May', 'June', 'July', 'August',
26 'September', 'October', 'November',
27 'December'
28];
29
30const d = document;
31
32// The datepicker class
33class KalamarDatepicker {
34
35 // Init datepicker
36 constructor() {
37 this._selected = [];
38 }
39
40
41 /**
42 * Get or select a specific date.
43 */
44 select(year, month, day) {
45 const t = this;
46 if (arguments.length >= 1) {
47 t._selected = {'year' : year};
48 t._showYear = year;
49 if (arguments.length >= 2) {
50 t._selected['month'] = month;
51 t._showMonth = month;
52 if (arguments.length >= 3) {
53 t._selected['day'] = day;
54 t._showDay = day;
55 };
56 };
57
58 return t;
59 };
60
61 return t._selected;
62 }
63
64
65 /**
66 * Select a specific date and
67 * init the accompanied action.
68 */
69 set(year, month, day) {
70 this.select(year, month, day);
71 this._store();
72 }
73
74
75 // Store the selected value
76 _store() {
77 if (this._click !== undefined)
78 this._click(this._selected);
79 else
80 console.dir(this._selected);
81 }
82
83
84 /**
85 * Set the action for clicking as a callback.
86 * The callback will retrieve a an object with
87 * an optional year attribute,
88 * an optional month attribute,
89 * and an optional day attribute
90 */
91 onclick(cb) {
92 this._click = cb;
93 }
94
95
96 /**
97 * The associated input field.
98 */
99 input() {
100 return this._input;
101 }
102
103
104 /**
105 * Show the datepicker.
106 * Will either show the selected year/month
107 * or the current date.
108 * Will return the element for appending to the dom.
109 */
110 show(year, month) {
111
112 const e = this._el = d.createElement('div');
113 e.setAttribute('tabindex', 0);
114 e.style.outline = 0;
115 e.classList.add('datepicker');
116
117 const today = new Date();
118 const t = this;
119
120 // Show year
121 t._showYear = (year !== undefined) ? year :
122 (t._selected['year'] ? this._selected['year'] :
123 today.getYear() + 1900);
124
125 // Show month
126 t._showMonth = month ? month :
127 (t._selected['month'] ? t._selected['month'] :
128 (today.getMonth() + 1));
129
130 // Append all helpers
131 e.appendChild(t._monthHelper());
132 e.appendChild(t._yearHelper());
133 e.appendChild(t._dayHelper());
134 t._input = e.appendChild(t._stringHelper());
135
136 // Always focus
137 e.addEventListener(
138 'mousedown',
139 function (ev) {
140 this._inField = true
141 }.bind(t)
142 );
143
144 e.addEventListener(
145 'mouseup',
146 function (ev) {
147 this._inField = false;
148 this._input.focus();
149 }.bind(t)
150 );
151
152 t._input.addEventListener(
153 'blur',
154 function (ev) {
155 if (!this._inField) {
156 if (this.fromString(this._input.value)) {
157 this._store();
158 };
159 };
160 ev.halt();
161 }.bind(t)
162 );
163
164 t._input.focus();
165
166 return t._el;
167 }
168
169 _stringHelper() {
170
171 // Create element
172 // Add input field
173 const input = d.createElement('input');
174 input.value = this.toString();
175 input.setAttribute('tabindex', 0);
176
177 input.addEventListener(
178 'keyup',
179 function (e) {
180 if (this.fromString(input.value)) {
181 this._updateYear();
182 this._updateMonth();
183 this._updateDay();
184 };
185 }.bind(this)
186 );
187
188 input.addEventListener(
189 'keypress',
190 function (e) {
191 if (e.keyCode == 13) {
192 if (this.fromString(input.value))
193 this._store();
194
195 e.halt();
196 return false;
197 }
198 }.bind(this)
199 )
200
201 return input;
202 }
203
204
205 /**
206 * Get the HTML element associated with the datepicker.
207 */
208 element() {
209 return this._el;
210 }
211
212
213 /**
214 * Get the current date in string format.
215 */
216 today() {
217 const today = new Date();
218 let str = today.getYear() + 1900;
219 const m = today.getMonth() + 1;
220 const d = today.getDate();
221 str += '-' + (m < 10 ? '0' + m : m);
222 str += '-' + (d < 10 ? '0' + d : d);
223 return str;
224 }
225
226
227 /**
228 * Stringification
229 */
230 toString() {
231 // There are values selected
232 let v = '';
233 const s = this._selected;
234 if (s['year']) {
235 v += s['year'];
236 if (s['month']) {
237 v += '-';
238 v += s['month'] < 10 ? '0' + s['month'] : s['month'];
239 if (s['day']) {
240 v += '-';
241 v += s['day'] < 10 ? '0' + s['day'] : s['day'];
242 };
243 };
244 };
245 return v;
246 }
247
248
249 /**
250 * Increment the year.
251 */
252 incrYear() {
253 const t = this;
254 if (t._showYear < 9999) {
255 t._showYear++;
256 t._updateYear();
257 t._updateMonth();
258 t._updateDay();
259 return t;
260 };
261 return;
262 }
263
264
265 /**
266 * Decrement the year.
267 */
268 decrYear() {
269 const t = this;
270 if (t._showYear > 0) {
271 t._showYear--;
272 t._updateYear();
273 t._updateMonth();
274 t._updateDay();
275 return t;
276 };
277 return;
278 }
279
280
281 /**
282 * Increment the month.
283 */
284 incrMonth() {
285 const t = this;
286 t._showMonth++;
287 if (t._showMonth > 12) {
288 t._showMonth = 1;
289 t.incrYear();
290 }
291 else {
292 t._updateMonth();
293 t._updateDay();
294 };
295 return t;
296 }
297
298
299 /**
300 * Decrement the month.
301 */
302 decrMonth() {
303 const t = this;
304 t._showMonth--;
305 if (t._showMonth < 1) {
306 t._showMonth = 12;
307 t.decrYear();
308 }
309 else {
310 t._updateMonth();
311 t._updateDay();
312 };
313
314 return t;
315 }
316
317
318 // Create the year helper element.
319 _yearHelper() {
320 const t = this;
321 const year = d.createElement('div');
322 year.classList.add('year');
323
324 // Decrement year
325 year.addE('span')
326 .onclick = t.decrYear.bind(t);
327
328 t._yElement = year.addE('span');
329 t._yElement.addT(t._showYear);
330
331 t._yElement.onclick = function () {
332 t.set(t._showYear);
333 }.bind(t);
334 t._selectYear();
335
336 // Increment year
337 year.addE('span')
338 .onclick = t.incrYear.bind(t);
339
340 return year;
341 }
342
343
344 // Update the year helper view.
345 _updateYear() {
346 this._yElement.firstChild.data = this._showYear;
347 this._selectYear();
348 }
349
350
351 // Check if the viewed year is current
352 _selectYear() {
353 if (this._showYear === this.select()['year'])
354 this._yElement.classList.add('selected');
355 else
356 this._yElement.classList.remove('selected');
357 }
358
359
360 // Create the month helper element.
361 _monthHelper() {
362 const t = this;
363 const month = d.createElement('div');
364 month.classList.add('month');
365
366 // Decrement month
367 month.addE('span')
368 .onclick = t.decrMonth.bind(t);
369
370 t._mElement = month.addE('span');
371 t._mElement.addT(loc.MONTH[t._showMonth-1]);
372 t._mElement.onclick = function () {
373 this.set(this._showYear, this._showMonth);
374 }.bind(t);
375
376 t._selectMonth();
377
378 // Increment month
379 month.addE('span')
380 .onclick = t.incrMonth.bind(t);
381
382 return month;
383 }
384
385 // Update the month helper view.
386 _updateMonth() {
387 if (this._showMonth === undefined || this._showMonth > 12)
388 this._showMonth = 1;
389
390 this._mElement.firstChild.data = loc.MONTH[this._showMonth-1];
391 this._selectMonth();
392 }
393
394
395 // Check if the viewed month is current
396 _selectMonth() {
397 const t = this;
398 if (t._showYear === t.select()['year'] &&
399 t._showMonth === t.select()['month'])
400 t._mElement.classList.add('selected');
401 else
402 t._mElement.classList.remove('selected');
403 }
404
405
406 // Create the day (calendar) helper element.
407 _dayHelper() {
408 const table = d.createElement('table');
409
410 // Localized day view
411 const tr = table.addE('thead').addE('tr');
412 for (let i = 0; i < 7; i++) {
413 tr.addE('th').addT(loc.WDAY[i]);
414 };
415
416 this._dBElement = this._dayBody();
417
418 table.appendChild(this._dBElement);
419 return table;
420 }
421
422
423 // Create day body for calendar table
424 _dayBody() {
425 const showDate = new Date(
426 this._showYear,
427 this._showMonth - 1,
428 1,
429 0,
430 0,
431 0,
432 0
433 );
434 const date = new Date(
435 this._showYear,
436 this._showMonth - 1,
437 1,
438 0,
439 0,
440 0,
441 0
442 );
443 const today = new Date();
444 const that = this;
445
446 // What happens, in case someone clicks
447 // on a date
448 const click = function () {
449 that.set(
450 that._showYear,
451 that._showMonth,
452 parseInt(this.firstChild.data)
453 );
454 };
455
456 // Skip back to the previous monday (may be in the last month)
457 date.setDate(date.getDate() - ((date.getDay() + 6) % 7));
458
459 const tb = d.createElement('tbody');
460
461 const s = this.select();
462
463 let tr, i, td;
464
465 // Iterate over all days of the table
466 while (1) {
467
468 // Loop through the week
469 tr = tb.addE('tr');
470 for (i = 0; i < 7; i++) {
471 td = tr.addE('td');
472
473 // Not part of the current month
474 if (date.getMonth() !== showDate.getMonth()) {
475 td.classList.add('out');
476 }
477 else {
478 td.onclick = click;
479 };
480
481 // This is the current day
482 if (date.getDate() === today.getDate() &&
483 date.getMonth() === today.getMonth() &&
484 date.getFullYear() === today.getFullYear()) {
485 td.classList.add('today');
486 };
487
488 // This is the day selected
489 if (s && s['day']) {
490 if (date.getDate() === s['day'] &&
491 date.getMonth() === s['month']-1 &&
492 date.getFullYear() === s['year']) {
493 td.classList.add('selected');
494 };
495 };
496
497 // Add the current day to the table
498 td.addT(date.getDate());
499
500 // Next day
501 date.setDate(date.getDate() + 1);
502 };
503
504 if (date.getMonth() !== showDate.getMonth())
505 break;
506 };
507 return tb;
508 }
509
510 // Update the calendar view
511 _updateDay() {
512 const newBody = this._dayBody();
513 this._dBElement.parentNode.replaceChild(
514 newBody,
515 this._dBElement
516 );
517 this._dBElement = newBody;
518 }
519
520
521 /**
522 * Parse date from string.
523 */
524 fromString(v) {
525 if (v === undefined)
526 return false;
527
528 if (!KorAP._validDateRE.test(v))
529 return false;
530
531 const d = v.split('-', 3);
532 d[0] = parseInt(d[0]);
533 if (d[1]) d[1] = parseInt(d[1]);
534 if (d[2]) d[2] = parseInt(d[2]);
535
536 // Select values
537 this.select(d[0], d[1], d[2]);
538 return true;
539 }
540}
541
542export default KalamarDatepicker;
543export { validDateMatchRE, validDateRE };