UNPKG

20.1 kBJavaScriptView Raw
1
2export var CalendarDate = {
3
4 /**
5 * Get number of days in month of year
6 * @param year
7 * @param month_index (0-11)
8 * @returns {number} 28-31
9 */
10 getDaysInMonth: function(year, month_index) {
11 return new Date(year, month_index+1, 0).getDate();
12 },
13
14 getWeeksInMonth: function(year, month_index) {
15 if (this.getDayOfWeekOfFirstMonthDay(year, month_index) === 0 && this.getDaysInMonth(year, month_index) === 28) {
16 return 4; // only in February with 28 days when monday is February 1st are 4 weeks
17 } else {
18 return 5;
19 }
20 },
21
22 /**
23 * Get day of week (monday-sunday) of first day in month of year
24 * @param year
25 * @param month_index (0-11)
26 * @returns {number} day of week index - 0 (monday) - 6 (sunday)
27 */
28 getDayOfWeekOfFirstMonthDay(year, month_index) {
29 var d = new Date(year, month_index).getDay();
30 if (d === 0) {
31 d = 7;
32 }
33 return d-1;
34 },
35
36 /**
37 * Get matrix (table of 4-5 rows and 7 columns) of month of year.
38 * Each cell contains month day number (1-31) or null if day belongs to previous/next month
39 * Matrix can be used to easily build custom calendar/datepicker
40 * @param year
41 * @param month_index
42 * @returns {Array} - 2D array
43 */
44 getMonthMatrix(year, month_index) {
45 var first_day_of_week = this.getDayOfWeekOfFirstMonthDay(year, month_index);
46 var days_in_month = this.getDaysInMonth(year, month_index);
47 var weeks_in_month = this.getWeeksInMonth(year, month_index);
48
49 var matrix = [];
50 var day_nr = 1;
51 for (var j = 0; j < weeks_in_month; j++) {
52 matrix[j] = [];
53 for (var k = 0; k < 7; k++) {
54 var td = document.createElement('td');
55 if (j == 0) {
56 // if first week
57 if (k >= first_day_of_week) {
58 matrix[j][k] = day_nr;
59 day_nr++;
60 } else {
61 matrix[j][k] = null;
62 }
63 } else if (j == weeks_in_month - 1) {
64 // if last week
65 if (day_nr <= days_in_month) {
66 matrix[j][k] = day_nr;
67 day_nr++;
68 } else {
69 matrix[j][k] = null;
70 }
71 } else {
72 // mid weeks
73 matrix[j][k] = day_nr;
74 day_nr++;
75 }
76 }
77 }
78 return matrix;
79 },
80
81 getCurrentDate() {
82 var date = new Date();
83 var year = date.getUTCFullYear();
84 var month_index = date.getUTCMonth();
85 var day = date.getUTCDate();
86 return {
87 year: year,
88 monthIndex: month_index,
89 day: day
90 }
91 }
92};
93
94export var CalendarMarkup = {
95
96 lang: {
97 daysOfWeeks: ['Mon', 'Thu', 'Wed', 'Thr', 'Fri', 'Sat', 'Sun'],
98 months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
99 },
100
101 createTableHead: function() {
102 var thead = document.createElement('div');
103 var tr = document.createElement('div');
104 var th_collection = [];
105 for (var k = 0; k < 7; k++) {
106 var th = document.createElement('div');
107 th.innerHTML = this.lang.daysOfWeeks[k];
108 th_collection.push(th);
109 tr.appendChild(th)
110 }
111 thead.appendChild(tr);
112 return {
113 thead: thead,
114 row: tr,
115 thCollection: th_collection
116 }
117 },
118
119 createTableBody: function(year, month_index, current_day = null) {
120
121 var matrix = CalendarDate.getMonthMatrix(year, month_index);
122
123 var tbody = document.createElement('div');
124 var inactive_td_collection = [];
125 var current_td = null;
126 var current_date_td = null; // today
127 var today = CalendarDate.getCurrentDate();
128 var td_collection = [];
129 var row_collection = [];
130
131 for (var j = 0; j < matrix.length; j++) {
132 var row = document.createElement('div');
133 row_collection.push(row);
134 tbody.appendChild(row);
135 for (var k = 0; k < matrix[j].length; k++) {
136 var td = document.createElement('div');
137 if (matrix[j][k] === null) {
138 inactive_td_collection.push(td);
139 } else {
140 td.innerHTML = matrix[j][k];
141 td_collection.push(td);
142 if (current_day !== null && matrix[j][k] == current_day) {
143 current_td = td;
144 }
145 if (today.year === year && today.monthIndex === month_index && today.day === matrix[j][k]) {
146 current_date_td = td;
147 }
148 }
149 row.appendChild(td);
150 }
151 }
152
153 return {
154 tbody: tbody,
155 rowCollection: row_collection,
156 tdCollection: td_collection,
157 inactiveTdCollection: inactive_td_collection,
158 currentTd: current_td,
159 currentDateTd: current_date_td
160 };
161 },
162
163 createEmptyTable: function() {
164 var table = document.createElement('div');
165 return table;
166 },
167
168 createPrevButton: function(content = '') {
169 var el = document.createElement('div');
170 el.innerHTML = content;
171 return el;
172 },
173
174 createNextButton: function(content = '') {
175 var el = document.createElement('div');
176 el.innerHTML = content;
177 return el;
178 },
179
180 createMonthSelect: function(selected_month_index = 0) {
181 var select = document.createElement('select');
182 for (var month_index = 0; month_index < this.lang.months.length; month_index++) {
183 var option = document.createElement('option');
184 option.setAttribute('value', month_index);
185 option.innerHTML = this.lang.months[month_index];
186 if (month_index == selected_month_index) {
187 option.setAttribute('selected', 1);
188 }
189 select.appendChild(option);
190 }
191 return select;
192 },
193
194 createYearSelect: function(selected_year = 2000, min_year = 1950, max_year = 2050) {
195 var select = document.createElement('select');
196 for (var i = min_year; i <= max_year; i++) {
197 var option = document.createElement('option');
198 option.setAttribute('value', i);
199 option.innerHTML = i;
200 if (i == selected_year) {
201 option.setAttribute('selected', true);
202 }
203 select.appendChild(option);
204 }
205 return select;
206 },
207
208 createEmptyHeader: function() {
209 var header = document.createElement('header');
210 return header;
211 },
212
213 createEmptyCalendar: function() {
214 var calendar = document.createElement('calendar');
215 return calendar;
216 }
217};
218
219export var CalendarDecorator = {
220 theme: {
221 def: {
222 classPrefix: 'bny-',
223
224 calendarClass: 'cal', // main container in which header and month table located
225 headerClass: 'cal-header', // main header section containing prev, month, year, next
226 monthClass: 'cal-month',
227 yearClass: 'cal-year',
228 prevButtonClass: 'cal-prev',
229 nextButtonClass: 'cal-next',
230
231 tableClass: 'cal-table', // main table section containing th of mon-sun and month days
232 tableHeadClass: 'cal-head',
233 tableHeadDayOfWeekClass: 'cal-day-of-week',
234 tableBodyClass: 'cal-body',
235 tableRowClass: 'cal-row',
236 weekClass: 'cal-week',
237 dayClass: 'cal-day',
238 dayInactiveClass: 'cal-day-inactive', // if month day belongs to prev/next month
239 dayCurrentClass: 'cal-day-current', // currently selected day
240 currentDateClass: 'cal-day-today',
241
242 popupClass: 'cal-popup',
243 popupBodyFadeClass: 'cal-popup-body-fade'
244 }
245 },
246
247 buildClass: function(classProperty, theme = 'def') {
248 return this.theme[theme].classPrefix + this.theme[theme][classProperty + 'Class'];
249 },
250
251 decorateTableHead: function(thead, row, th_collection) {
252 thead.classList.add(this.buildClass('tableHead'));
253 row.classList.add(this.buildClass('tableRow'));
254 for (var k = 0; k < th_collection.length; k++) {
255 th_collection[k].classList.add(this.buildClass('tableHeadDayOfWeek'));
256 }
257 },
258
259 decorateTableBody: function(tbody, row_collection, td_collection, inactive_td_collection, current_td, current_date_td) {
260 tbody.classList.add(this.buildClass('tableBody'));
261 for (var k = 0; k < row_collection.length; k++) {
262 row_collection[k].classList.add(this.buildClass('tableRow'));
263 row_collection[k].classList.add(this.buildClass('week'));
264 }
265 for (k = 0; k < td_collection.length; k++) {
266 td_collection[k].classList.add(this.buildClass('day'));
267 }
268 for (k = 0; k < inactive_td_collection.length; k++) {
269 inactive_td_collection[k].classList.add(this.buildClass('dayInactive'));
270 }
271 if (current_td !== null) {
272 current_td.classList.add(this.buildClass('dayCurrent'));
273 }
274 if (current_date_td !== null) {
275 current_date_td.classList.add(this.buildClass('currentDate'));
276 }
277 },
278
279 setCurrentDay: function(td_collection, day) {
280 for (var k = 0; k < td_collection.length; k++) {
281 if (td_collection[k].innerHTML == day) {
282 td_collection[k].classList.add(this.buildClass('dayCurrent'));
283 } else if (td_collection[k].classList.contains(this.buildClass('dayCurrent'))) {
284 td_collection[k].classList.remove(this.buildClass('dayCurrent'));
285 }
286 }
287 },
288
289 decorateTable: function(table) {
290 table.classList.add(this.buildClass('table'));
291 },
292
293 decoratePrevButton: function(prev_button) {
294 prev_button.classList.add(this.buildClass('prevButton'));
295 },
296
297 decorateNextButton: function(next_button) {
298 next_button.classList.add(this.buildClass('nextButton'));
299 },
300
301 decorateMonthSelect: function(select) {
302 select.classList.add(this.buildClass('month'));
303 },
304
305 decorateYearSelect: function(select) {
306 select.classList.add(this.buildClass('year'));
307 },
308
309 decorateHeader: function(header) {
310 header.classList.add(this.buildClass('header'));
311 },
312
313 decorateCalendar: function(calendar) {
314 calendar.classList.add(this.buildClass('calendar'));
315 },
316
317 decorateCalendarPopup(calendar) {
318 calendar.classList.add(this.buildClass('popup'));
319 document.body.classList.add(this.buildClass('popupBodyFade'));
320 },
321
322 undecorateCalendarPopup(calendar) {
323 calendar.classList.remove(this.buildClass('popup'));
324 document.body.classList.remove(this.buildClass('popupBodyFade'));
325 }
326};
327
328export var CalendarController = {
329
330 attachDayClickEvent: function(calendar_id) {
331 var td_collection = Calendar._calendars[calendar_id].tableBody.tdCollection;
332 for (var k = 0; k < td_collection.length; k++) {
333 td_collection[k].addEventListener('click', function() {
334 Calendar.pickDay(calendar_id, Calendar._calendars[calendar_id].displayedYear, Calendar._calendars[calendar_id].displayedMonthIndex, this.innerHTML);
335 });
336 }
337 },
338
339 attachMonthSelectEvent: function(calendar_id) {
340 Calendar._calendars[calendar_id].headerMonthSelect.addEventListener('change', function() {
341 Calendar.changeMonth(calendar_id, Calendar._calendars[calendar_id].displayedYear, +this.value);
342 });
343 },
344
345 attachYearSelectEvent: function(calendar_id) {
346 Calendar._calendars[calendar_id].headerYearSelect.addEventListener('change', function() {
347 Calendar.changeMonth(calendar_id, this.value, Calendar._calendars[calendar_id].displayedMonthIndex);
348 });
349 },
350
351 attachPrevClickEvent: function(calendar_id) {
352 Calendar._calendars[calendar_id].headerPrevButton.addEventListener('click', function() {
353 if (Calendar._calendars[calendar_id].displayedMonthIndex === 0) {
354 Calendar.changeMonth(calendar_id, Calendar._calendars[calendar_id].displayedYear - 1, 11);
355 } else {
356 Calendar.changeMonth(calendar_id, Calendar._calendars[calendar_id].displayedYear, Calendar._calendars[calendar_id].displayedMonthIndex - 1);
357 }
358 });
359 },
360
361 attachNextClickEvent: function(calendar_id) {
362 Calendar._calendars[calendar_id].headerNextButton.addEventListener('click', function() {
363 if (Calendar._calendars[calendar_id].displayedMonthIndex === 11) {
364 Calendar.changeMonth(calendar_id, Calendar._calendars[calendar_id].displayedYear + 1, 0);
365 } else {
366 Calendar.changeMonth(calendar_id, Calendar._calendars[calendar_id].displayedYear, Calendar._calendars[calendar_id].displayedMonthIndex + 1);
367 }
368 });
369 },
370
371 attachCloseOnOutsideClickEvent: function(calendar_id) {
372 document.addEventListener('click', function() {
373 if (!Calendar.isHidden(calendar_id)) {
374 Calendar.hide(calendar_id);
375 }
376 });
377
378 Calendar.getCalendar(calendar_id).addEventListener('click', function(e) {
379 e.stopPropagation();
380 });
381 }
382
383};
384
385export var Calendar = {
386
387 _calendars: {},
388
389 create: function(id = 'datepicker', min_year = 1950, max_year = 2050, year = null, month_index = null, day = null) {
390
391 var current_date = CalendarDate.getCurrentDate();
392
393 if (year === null) {
394 year = current_date.year;
395 }
396 if (month_index === null) {
397 month_index = current_date.monthIndex;
398 }
399 if (day === null) {
400 day = current_date.day;
401 }
402
403 var calendar = CalendarMarkup.createEmptyCalendar();
404 CalendarDecorator.decorateCalendar(calendar);
405
406 var header = CalendarMarkup.createEmptyHeader();
407 CalendarDecorator.decorateHeader(header);
408
409 var header_prev_btn = CalendarMarkup.createPrevButton();
410 CalendarDecorator.decoratePrevButton(header_prev_btn);
411
412 var header_next_btn = CalendarMarkup.createNextButton();
413 CalendarDecorator.decorateNextButton(header_next_btn);
414
415 var header_month_select = CalendarMarkup.createMonthSelect(month_index);
416 CalendarDecorator.decorateMonthSelect(header_month_select);
417
418 var header_year_select = CalendarMarkup.createYearSelect(year, min_year, max_year);
419 CalendarDecorator.decorateYearSelect(header_year_select);
420
421 header.appendChild(header_prev_btn);
422 header.appendChild(header_month_select);
423 header.appendChild(header_year_select);
424 header.appendChild(header_next_btn);
425
426 var table = CalendarMarkup.createEmptyTable();
427 CalendarDecorator.decorateTable(table);
428
429 var table_head = CalendarMarkup.createTableHead();
430 CalendarDecorator.decorateTableHead(table_head.thead, table_head.row, table_head.thCollection);
431
432 var table_body = CalendarMarkup.createTableBody(year, month_index, day);
433 CalendarDecorator.decorateTableBody(
434 table_body.tbody,
435 table_body.rowCollection,
436 table_body.tdCollection,
437 table_body.inactiveTdCollection,
438 table_body.currentTd,
439 table_body.currentDateTd
440 );
441
442 table.appendChild(table_head.thead);
443 table.appendChild(table_body.tbody);
444
445 calendar.appendChild(header);
446 calendar.appendChild(table);
447
448 this._calendars[id] = {
449 selectedYear: year,
450 selectedMonthIndex: month_index,
451 selectedDay: day,
452 displayedYear: year,
453 displayedMonthIndex: month_index,
454 minYear: min_year,
455 maxYear: max_year,
456 calendar: calendar,
457 header: header,
458 headerPrevButton: header_prev_btn,
459 headerMonthSelect: header_month_select,
460 headerYearSelect: header_year_select,
461 headerNextButton: header_next_btn,
462 table: table,
463 tableHead: table_head,
464 tableBody: table_body,
465 onPickHandlers: []
466 };
467
468 CalendarController.attachPrevClickEvent(id);
469 CalendarController.attachNextClickEvent(id);
470
471 CalendarController.attachMonthSelectEvent(id);
472 CalendarController.attachYearSelectEvent(id);
473
474 CalendarController.attachDayClickEvent(id);
475
476 CalendarController.attachCloseOnOutsideClickEvent(id);
477
478 return calendar;
479 },
480
481 changeMonth(calendar_id, year, month_index) {
482 var cal = this._calendars[calendar_id];
483 if (year < cal.minYear || year > cal.maxYear) {
484 return false;
485 }
486 var current_day = null;
487 if (year == cal.selectedYear && month_index == cal.selectedMonthIndex) {
488 current_day = cal.selectedDay;
489 }
490 cal.displayedYear = year;
491 cal.displayedMonthIndex = month_index;
492 var table_body = CalendarMarkup.createTableBody(year, month_index, current_day);
493 CalendarDecorator.decorateTableBody(
494 table_body.tbody,
495 table_body.rowCollection,
496 table_body.tdCollection,
497 table_body.inactiveTdCollection,
498 table_body.currentTd,
499 table_body.currentDateTd
500 );
501
502 cal.tableBody.tbody.parentNode.removeChild(cal.tableBody.tbody);
503 cal.tableBody = table_body;
504 cal.table.appendChild(table_body.tbody);
505
506 cal.headerYearSelect.querySelector('option[selected]').removeAttribute('selected');
507 cal.headerYearSelect.querySelector('option[value="'+year+'"]').setAttribute('selected', 'selected');
508 cal.headerYearSelect.value = year;
509
510 cal.headerMonthSelect.querySelector('option[selected]').removeAttribute('selected');
511 cal.headerMonthSelect.options[month_index].setAttribute('selected', 'selected');
512 cal.headerMonthSelect.value = month_index;
513
514 CalendarController.attachDayClickEvent(calendar_id);
515 },
516
517 pickDay: function(calendar_id, year, month_index, day) {
518 this._calendars[calendar_id].selectedYear = year;
519 this._calendars[calendar_id].selectedMonthIndex = month_index;
520 this._calendars[calendar_id].selectedDay = day;
521 CalendarDecorator.setCurrentDay(this._calendars[calendar_id].tableBody.tdCollection, day);
522 for (var i = 0; i < this._calendars[calendar_id].onPickHandlers.length; i++) {
523 var month = parseInt(month_index) + 1;
524 var year = parseInt(year);
525 var day = parseInt(day);
526 this._calendars[calendar_id].onPickHandlers[i](year, month, day);
527 }
528 },
529
530 onPick: function(calendar_id, handler) {
531 this._calendars[calendar_id].onPickHandlers.push(handler);
532 },
533
534 getCalendar(calendar_id) {
535 return this._calendars[calendar_id].calendar;
536 },
537
538 show: function(calendar_id, popup = false) {
539 if (popup) {
540 CalendarDecorator.decorateCalendarPopup(this._calendars[calendar_id].calendar)
541 }
542 this._calendars[calendar_id].calendar.classList.remove('hidden');
543 },
544
545 hide: function(calendar_id, popup = false) {
546 if (popup) {
547 CalendarDecorator.undecorateCalendarPopup(this._calendars[calendar_id].calendar)
548 }
549 this._calendars[calendar_id].calendar.classList.add('hidden');
550 },
551
552 isHidden: function(calendar_id) {
553 return this._calendars[calendar_id].calendar.classList.contains('hidden');
554 }
555};