import PopupElement, { PopupOptions } from "."; import { getFullDate, months } from "../../helpers/date"; import mediaSizes from "../../helpers/mediaSizes"; import InputField from "../inputField"; export default class PopupDatePicker extends PopupElement { protected controlsDiv: HTMLElement; protected monthTitle: HTMLElement; protected prevBtn: HTMLElement; protected nextBtn: HTMLElement; protected monthsContainer: HTMLElement; protected month: HTMLElement; protected minMonth: Date; protected maxMonth: Date; protected minDate: Date; protected maxDate: Date; protected selectedDate: Date; protected selectedMonth: Date; protected selectedEl: HTMLElement; protected timeDiv: HTMLDivElement; protected hoursInputField: InputField; protected minutesInputField: InputField; constructor(initDate: Date, public onPick: (timestamp: number) => void, protected options: Partial<{ noButtons: true, noTitle: true, minDate: Date, maxDate: Date withTime: true, showOverflowMonths: true }> & PopupOptions = {}) { super('popup-date-picker', options.noButtons ? [] : [{ text: 'CANCEL', isCancel: true }, { text: 'JUMP TO DATE', callback: () => { if(this.onPick) { this.onPick(this.selectedDate.getTime() / 1000 | 0); } } }], {body: true, ...options}); this.minDate = options.minDate || new Date('2013-08-01T00:00:00'); if(initDate < this.minDate) { initDate.setFullYear(this.minDate.getFullYear(), this.minDate.getMonth(), this.minDate.getDate()); } // Controls this.controlsDiv = document.createElement('div'); this.controlsDiv.classList.add('date-picker-controls'); this.prevBtn = document.createElement('button'); this.prevBtn.classList.add('btn-icon', 'tgico-down', 'date-picker-prev'); this.prevBtn.addEventListener('click', this.onPrevClick); this.nextBtn = document.createElement('button'); this.nextBtn.classList.add('btn-icon', 'tgico-down', 'date-picker-next'); this.nextBtn.addEventListener('click', this.onNextClick); this.monthTitle = document.createElement('div'); this.monthTitle.classList.add('date-picker-month-title'); this.controlsDiv.append(this.prevBtn, this.monthTitle, this.nextBtn); // Month this.monthsContainer = document.createElement('div'); this.monthsContainer.classList.add('date-picker-months'); this.monthsContainer.addEventListener('click', this.onDateClick); this.body.append(this.controlsDiv, this.monthsContainer); // Time inputs if(options.withTime) { this.timeDiv = document.createElement('div'); this.timeDiv.classList.add('date-picker-time'); const delimiter = document.createElement('div'); delimiter.classList.add('date-picker-time-delimiter'); delimiter.append(':'); const handleTimeInput = (max: number, inputField: InputField, onInput: (length: number) => void, onOverflow?: (number: number) => void) => { const maxString = '' + max; inputField.input.addEventListener('input', (e) => { let value = inputField.value.replace(/\D/g, ''); if(value.length > 2) { value = value.slice(0, 2); } else { if((value.length === 1 && +value[0] > +maxString[0]) || (value.length === 2 && +value > max)) { if(value.length === 2 && onOverflow) { onOverflow(+value[1]); } value = '0' + value[0]; } } inputField.setValueSilently(value); onInput(value.length); }); }; this.hoursInputField = new InputField({plainText: true}); this.minutesInputField = new InputField({plainText: true}); handleTimeInput(23, this.hoursInputField, (length) => { if(length === 2) { this.minutesInputField.input.focus(); } this.setTimeTitle(); }, (number) => { this.minutesInputField.value = (number + this.minutesInputField.value).slice(0, 2); }); handleTimeInput(59, this.minutesInputField, (length) => { if(!length) { this.hoursInputField.input.focus(); } this.setTimeTitle(); }); this.selectedDate = initDate; initDate.setMinutes(initDate.getMinutes() + 10); this.hoursInputField.setValueSilently(('0' + initDate.getHours()).slice(-2)); this.minutesInputField.setValueSilently(('0' + initDate.getMinutes()).slice(-2)); initDate.setHours(0, 0, 0, 0); this.timeDiv.append(this.hoursInputField.container, delimiter, this.minutesInputField.container); this.btnConfirm.addEventListener('click', () => { if(this.onPick) { this.selectedDate.setHours(+this.hoursInputField.value || 0, +this.minutesInputField.value || 0, 0, 0); this.onPick(this.selectedDate.getTime() / 1000 | 0); } this.destroy(); }, {once: true}); this.body.append(this.timeDiv); } const popupCenterer = document.createElement('div'); popupCenterer.classList.add('popup-centerer'); popupCenterer.append(this.container); this.element.append(popupCenterer); //const passed = (initDate.getTime() - (initDate.getTimezoneOffset() * 60000)) % 86400000; //this.selectedDate = this.maxDate = new Date(initDate.getTime() - passed); initDate.setHours(0, 0, 0, 0); this.selectedDate = initDate; this.maxDate = options.maxDate || new Date(); this.maxDate.setHours(0, 0, 0, 0); this.selectedMonth = new Date(this.selectedDate); this.selectedMonth.setDate(1); this.maxMonth = new Date(this.maxDate); this.maxMonth.setDate(1); this.minMonth = new Date(this.minDate); this.minMonth.setHours(0, 0, 0, 0); this.minMonth.setDate(1); if(this.selectedMonth.getTime() == this.minMonth.getTime()) { this.prevBtn.setAttribute('disabled', 'true'); } if(this.selectedMonth.getTime() == this.maxMonth.getTime()) { this.nextBtn.setAttribute('disabled', 'true'); } if(options.noTitle) { this.setTitle = () => {}; } this.setTimeTitle(); this.setTitle(); this.setMonth(); } onPrevClick = (e: MouseEvent) => { this.selectedMonth.setMonth(this.selectedMonth.getMonth() - 1); this.setMonth(); if(this.selectedMonth.getTime() == this.minMonth.getTime()) { this.prevBtn.setAttribute('disabled', 'true'); } this.nextBtn.removeAttribute('disabled'); }; onNextClick = (e: MouseEvent) => { this.selectedMonth.setMonth(this.selectedMonth.getMonth() + 1); this.setMonth(); if(this.selectedMonth.getTime() == this.maxMonth.getTime()) { this.nextBtn.setAttribute('disabled', 'true'); } this.prevBtn.removeAttribute('disabled'); }; onDateClick = (e: MouseEvent) => { //cancelEvent(e); const target = e.target as HTMLElement; if(!target.dataset.timestamp) return; if(this.selectedEl) { if(this.selectedEl == target) return; this.selectedEl.classList.remove('active'); } target.classList.add('active'); const timestamp = +target.dataset.timestamp; this.selectedDate = new Date(timestamp); this.setTitle(); this.setMonth(); this.setTimeTitle(); }; public setTimeTitle() { if(this.btnConfirm && this.selectedDate) { let dayStr = ''; const date = new Date(); date.setHours(0, 0, 0, 0); if(this.selectedDate.getTime() === date.getTime()) { dayStr = 'Today'; } else if(this.selectedDate.getTime() === (date.getTime() + 86400e3)) { dayStr = 'Tomorrow'; } else { dayStr = 'on ' + getFullDate(this.selectedDate, { noTime: true, monthAsNumber: true, leadingZero: true }); } this.btnConfirm.firstChild.nodeValue = 'Send ' + dayStr + ' at ' + ('00' + this.hoursInputField.value).slice(-2) + ':' + ('00' + this.minutesInputField.value).slice(-2); } } public setTitle() { const splitted = this.selectedDate.toString().split(' ', 3); this.title.innerText = splitted[0] + ', ' + splitted[1] + ' ' + splitted[2]; } private renderElement(disabled: boolean, innerText = '') { const el = document.createElement('button'); el.classList.add('btn-icon', 'date-picker-month-date'); if(disabled) { el.setAttribute('disabled', 'true'); } if(innerText) { el.innerText = innerText; } return el; } public setMonth() { const monthName = months[this.selectedMonth.getMonth()]; this.monthTitle.innerText = (this.timeDiv && mediaSizes.isMobile ? monthName.slice(0, 3) : monthName) + ' ' + this.selectedMonth.getFullYear(); if(this.month) { this.month.remove(); } this.month = document.createElement('div'); this.month.classList.add('date-picker-month'); const days = ['M', 'T', 'W', 'T', 'F', 'S', 'S']; this.month.append(...days.map(s => { const el = this.renderElement(true, s); el.classList.remove('date-picker-month-date'); el.classList.add('date-picker-month-day'); return el; })); const firstDate = new Date(this.selectedMonth); // 0 - sunday let dayIndex = firstDate.getDay() - 1; if(dayIndex == -1) dayIndex = days.length - 1; const clonedDate = new Date(firstDate.getTime()); clonedDate.setDate(clonedDate.getDate() - dayIndex - 1); // Padding first week for(let i = 0; i < dayIndex; ++i) { if(this.options.showOverflowMonths) { clonedDate.setDate(clonedDate.getDate() + 1); this.month.append(this.renderElement(true, '' + clonedDate.getDate())); } else { this.month.append(this.renderElement(true)); } } do { const date = firstDate.getDate(); const el = this.renderElement(firstDate > this.maxDate || firstDate < this.minDate, '' + date); el.dataset.timestamp = '' + firstDate.getTime(); if(firstDate.getTime() === this.selectedDate.getTime()) { this.selectedEl = el; el.classList.add('active'); } this.month.append(el); firstDate.setDate(date + 1); } while(firstDate.getDate() !== 1); const remainder = this.month.childElementCount % 7; if(this.options.showOverflowMonths && remainder) { for(let i = remainder; i < 7; ++i) { this.month.append(this.renderElement(true, '' + firstDate.getDate())); firstDate.setDate(firstDate.getDate() + 1); } } this.container.classList.toggle('is-max-lines', (this.month.childElementCount / 7) > 6); this.monthsContainer.append(this.month); } }