(function($) { 'use strict'; let _defaults = { // Close when date is selected autoClose: false, // the default output format for the input field value format: 'mmm dd, yyyy', // Used to create date object from current input string parse: null, // The initial date to view when first opened defaultDate: null, // Make the `defaultDate` the initial selected value setDefaultDate: false, disableWeekends: false, disableDayFn: null, // First day of week (0: Sunday, 1: Monday etc) firstDay: 0, // The earliest date that can be selected minDate: null, // Thelatest date that can be selected maxDate: null, // Number of years either side, or array of upper/lower range yearRange: 10, // used internally (don't config outside) minYear: 0, maxYear: 9999, minMonth: undefined, maxMonth: undefined, startRange: null, endRange: null, isRTL: false, // Render the month after year in the calendar title showMonthAfterYear: false, // Render days of the calendar grid that fall in the next or previous month showDaysInNextAndPreviousMonths: false, // Specify a DOM element to render the calendar in container: null, // Show clear button showClearBtn: false, // internationalization i18n: { cancel: 'Cancel', clear: 'Clear', done: 'Ok', previousMonth: '‹', nextMonth: '›', months: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ], monthsShort: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ], weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], weekdaysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], weekdaysAbbrev: ['S', 'M', 'T', 'W', 'T', 'F', 'S'] }, // events array events: [], // callback function onSelect: null, onOpen: null, onClose: null, onDraw: null }; /** * @class * */ class Datepicker extends Component { /** * Construct Datepicker instance and set up overlay * @constructor * @param {Element} el * @param {Object} options */ constructor(el, options) { super(Datepicker, el, options); this.el.M_Datepicker = this; this.options = $.extend({}, Datepicker.defaults, options); // make sure i18n defaults are not lost when only few i18n option properties are passed if (!!options && options.hasOwnProperty('i18n') && typeof options.i18n === 'object') { this.options.i18n = $.extend({}, Datepicker.defaults.i18n, options.i18n); } // Remove time component from minDate and maxDate options if (this.options.minDate) this.options.minDate.setHours(0, 0, 0, 0); if (this.options.maxDate) this.options.maxDate.setHours(0, 0, 0, 0); this.id = M.guid(); this._setupVariables(); this._insertHTMLIntoDOM(); this._setupModal(); this._setupEventHandlers(); if (!this.options.defaultDate) { this.options.defaultDate = new Date(Date.parse(this.el.value)); } let defDate = this.options.defaultDate; if (Datepicker._isDate(defDate)) { if (this.options.setDefaultDate) { this.setDate(defDate, true); this.setInputValue(); } else { this.gotoDate(defDate); } } else { this.gotoDate(new Date()); } /** * Describes open/close state of datepicker * @type {Boolean} */ this.isOpen = false; } static get defaults() { return _defaults; } static init(els, options) { return super.init(this, els, options); } static _isDate(obj) { return /Date/.test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime()); } static _isWeekend(date) { let day = date.getDay(); return day === 0 || day === 6; } static _setToStartOfDay(date) { if (Datepicker._isDate(date)) date.setHours(0, 0, 0, 0); } static _getDaysInMonth(year, month) { return [31, Datepicker._isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][ month ]; } static _isLeapYear(year) { // solution by Matti Virkkunen: http://stackoverflow.com/a/4881951 return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; } static _compareDates(a, b) { // weak date comparison (use setToStartOfDay(date) to ensure correct result) return a.getTime() === b.getTime(); } static _setToStartOfDay(date) { if (Datepicker._isDate(date)) date.setHours(0, 0, 0, 0); } /** * Get Instance */ static getInstance(el) { let domElem = !!el.jquery ? el[0] : el; return domElem.M_Datepicker; } /** * Teardown component */ destroy() { this._removeEventHandlers(); this.modal.destroy(); $(this.modalEl).remove(); this.destroySelects(); this.el.M_Datepicker = undefined; } destroySelects() { let oldYearSelect = this.calendarEl.querySelector('.orig-select-year'); if (oldYearSelect) { M.FormSelect.getInstance(oldYearSelect).destroy(); } let oldMonthSelect = this.calendarEl.querySelector('.orig-select-month'); if (oldMonthSelect) { M.FormSelect.getInstance(oldMonthSelect).destroy(); } } _insertHTMLIntoDOM() { if (this.options.showClearBtn) { $(this.clearBtn).css({ visibility: '' }); this.clearBtn.innerHTML = this.options.i18n.clear; } this.doneBtn.innerHTML = this.options.i18n.done; this.cancelBtn.innerHTML = this.options.i18n.cancel; if (this.options.container) { this.$modalEl.appendTo(this.options.container); } else { this.$modalEl.insertBefore(this.el); } } _setupModal() { this.modalEl.id = 'modal-' + this.id; this.modal = M.Modal.init(this.modalEl, { onCloseEnd: () => { this.isOpen = false; } }); } toString(format) { format = format || this.options.format; if (!Datepicker._isDate(this.date)) { return ''; } let formatArray = format.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g); let formattedDate = formatArray .map((label) => { if (this.formats[label]) { return this.formats[label](); } return label; }) .join(''); return formattedDate; } setDate(date, preventOnSelect) { if (!date) { this.date = null; this._renderDateDisplay(); return this.draw(); } if (typeof date === 'string') { date = new Date(Date.parse(date)); } if (!Datepicker._isDate(date)) { return; } let min = this.options.minDate, max = this.options.maxDate; if (Datepicker._isDate(min) && date < min) { date = min; } else if (Datepicker._isDate(max) && date > max) { date = max; } this.date = new Date(date.getTime()); this._renderDateDisplay(); Datepicker._setToStartOfDay(this.date); this.gotoDate(this.date); if (!preventOnSelect && typeof this.options.onSelect === 'function') { this.options.onSelect.call(this, this.date); } } setInputValue() { this.el.value = this.toString(); this.$el.trigger('change', { firedBy: this }); } _renderDateDisplay() { let displayDate = Datepicker._isDate(this.date) ? this.date : new Date(); let i18n = this.options.i18n; let day = i18n.weekdaysShort[displayDate.getDay()]; let month = i18n.monthsShort[displayDate.getMonth()]; let date = displayDate.getDate(); this.yearTextEl.innerHTML = displayDate.getFullYear(); this.dateTextEl.innerHTML = `${day}, ${month} ${date}`; } /** * change view to a specific date */ gotoDate(date) { let newCalendar = true; if (!Datepicker._isDate(date)) { return; } if (this.calendars) { let firstVisibleDate = new Date(this.calendars[0].year, this.calendars[0].month, 1), lastVisibleDate = new Date( this.calendars[this.calendars.length - 1].year, this.calendars[this.calendars.length - 1].month, 1 ), visibleDate = date.getTime(); // get the end of the month lastVisibleDate.setMonth(lastVisibleDate.getMonth() + 1); lastVisibleDate.setDate(lastVisibleDate.getDate() - 1); newCalendar = visibleDate < firstVisibleDate.getTime() || lastVisibleDate.getTime() < visibleDate; } if (newCalendar) { this.calendars = [ { month: date.getMonth(), year: date.getFullYear() } ]; } this.adjustCalendars(); } adjustCalendars() { this.calendars[0] = this.adjustCalendar(this.calendars[0]); this.draw(); } adjustCalendar(calendar) { if (calendar.month < 0) { calendar.year -= Math.ceil(Math.abs(calendar.month) / 12); calendar.month += 12; } if (calendar.month > 11) { calendar.year += Math.floor(Math.abs(calendar.month) / 12); calendar.month -= 12; } return calendar; } nextMonth() { this.calendars[0].month++; this.adjustCalendars(); } prevMonth() { this.calendars[0].month--; this.adjustCalendars(); } render(year, month, randId) { let opts = this.options, now = new Date(), days = Datepicker._getDaysInMonth(year, month), before = new Date(year, month, 1).getDay(), data = [], row = []; Datepicker._setToStartOfDay(now); if (opts.firstDay > 0) { before -= opts.firstDay; if (before < 0) { before += 7; } } let previousMonth = month === 0 ? 11 : month - 1, nextMonth = month === 11 ? 0 : month + 1, yearOfPreviousMonth = month === 0 ? year - 1 : year, yearOfNextMonth = month === 11 ? year + 1 : year, daysInPreviousMonth = Datepicker._getDaysInMonth(yearOfPreviousMonth, previousMonth); let cells = days + before, after = cells; while (after > 7) { after -= 7; } cells += 7 - after; let isWeekSelected = false; for (let i = 0, r = 0; i < cells; i++) { let day = new Date(year, month, 1 + (i - before)), isSelected = Datepicker._isDate(this.date) ? Datepicker._compareDates(day, this.date) : false, isToday = Datepicker._compareDates(day, now), hasEvent = opts.events.indexOf(day.toDateString()) !== -1 ? true : false, isEmpty = i < before || i >= days + before, dayNumber = 1 + (i - before), monthNumber = month, yearNumber = year, isStartRange = opts.startRange && Datepicker._compareDates(opts.startRange, day), isEndRange = opts.endRange && Datepicker._compareDates(opts.endRange, day), isInRange = opts.startRange && opts.endRange && opts.startRange < day && day < opts.endRange, isDisabled = (opts.minDate && day < opts.minDate) || (opts.maxDate && day > opts.maxDate) || (opts.disableWeekends && Datepicker._isWeekend(day)) || (opts.disableDayFn && opts.disableDayFn(day)); if (isEmpty) { if (i < before) { dayNumber = daysInPreviousMonth + dayNumber; monthNumber = previousMonth; yearNumber = yearOfPreviousMonth; } else { dayNumber = dayNumber - days; monthNumber = nextMonth; yearNumber = yearOfNextMonth; } } let dayConfig = { day: dayNumber, month: monthNumber, year: yearNumber, hasEvent: hasEvent, isSelected: isSelected, isToday: isToday, isDisabled: isDisabled, isEmpty: isEmpty, isStartRange: isStartRange, isEndRange: isEndRange, isInRange: isInRange, showDaysInNextAndPreviousMonths: opts.showDaysInNextAndPreviousMonths }; row.push(this.renderDay(dayConfig)); if (++r === 7) { data.push(this.renderRow(row, opts.isRTL, isWeekSelected)); row = []; r = 0; isWeekSelected = false; } } return this.renderTable(opts, data, randId); } renderDay(opts) { let arr = []; let ariaSelected = 'false'; if (opts.isEmpty) { if (opts.showDaysInNextAndPreviousMonths) { arr.push('is-outside-current-month'); arr.push('is-selection-disabled'); } else { return ''; } } if (opts.isDisabled) { arr.push('is-disabled'); } if (opts.isToday) { arr.push('is-today'); } if (opts.isSelected) { arr.push('is-selected'); ariaSelected = 'true'; } if (opts.hasEvent) { arr.push('has-event'); } if (opts.isInRange) { arr.push('is-inrange'); } if (opts.isStartRange) { arr.push('is-startrange'); } if (opts.isEndRange) { arr.push('is-endrange'); } return ( `` + `` + '' ); } renderRow(days, isRTL, isRowSelected) { return ( '' + (isRTL ? days.reverse() : days).join('') + '' ); } renderTable(opts, data, randId) { return ( '
' + this.renderHead(opts) + this.renderBody(data) + '
' ); } renderHead(opts) { let i, arr = []; for (i = 0; i < 7; i++) { arr.push( `${this.renderDayName( opts, i, true )}` ); } return '' + (opts.isRTL ? arr.reverse() : arr).join('') + ''; } renderBody(rows) { return '' + rows.join('') + ''; } renderTitle(instance, c, year, month, refYear, randId) { let i, j, arr, opts = this.options, isMinYear = year === opts.minYear, isMaxYear = year === opts.maxYear, html = '
', monthHtml, yearHtml, prev = true, next = true; for (arr = [], i = 0; i < 12; i++) { arr.push( '' ); } monthHtml = ''; if ($.isArray(opts.yearRange)) { i = opts.yearRange[0]; j = opts.yearRange[1] + 1; } else { i = year - opts.yearRange; j = 1 + year + opts.yearRange; } for (arr = []; i < j && i <= opts.maxYear; i++) { if (i >= opts.minYear) { arr.push(``); } } yearHtml = ``; let leftArrow = ''; html += ``; html += '
'; if (opts.showMonthAfterYear) { html += yearHtml + monthHtml; } else { html += monthHtml + yearHtml; } html += '
'; if (isMinYear && (month === 0 || opts.minMonth >= month)) { prev = false; } if (isMaxYear && (month === 11 || opts.maxMonth <= month)) { next = false; } let rightArrow = ''; html += ``; return (html += '
'); } /** * refresh the HTML */ draw(force) { if (!this.isOpen && !force) { return; } let opts = this.options, minYear = opts.minYear, maxYear = opts.maxYear, minMonth = opts.minMonth, maxMonth = opts.maxMonth, html = '', randId; if (this._y <= minYear) { this._y = minYear; if (!isNaN(minMonth) && this._m < minMonth) { this._m = minMonth; } } if (this._y >= maxYear) { this._y = maxYear; if (!isNaN(maxMonth) && this._m > maxMonth) { this._m = maxMonth; } } randId = 'datepicker-title-' + Math.random() .toString(36) .replace(/[^a-z]+/g, '') .substr(0, 2); for (let c = 0; c < 1; c++) { this._renderDateDisplay(); html += this.renderTitle( this, c, this.calendars[c].year, this.calendars[c].month, this.calendars[0].year, randId ) + this.render(this.calendars[c].year, this.calendars[c].month, randId); } this.destroySelects(); this.calendarEl.innerHTML = html; // Init Materialize Select let yearSelect = this.calendarEl.querySelector('.orig-select-year'); let monthSelect = this.calendarEl.querySelector('.orig-select-month'); M.FormSelect.init(yearSelect, { classes: 'select-year', dropdownOptions: { container: document.body, constrainWidth: false } }); M.FormSelect.init(monthSelect, { classes: 'select-month', dropdownOptions: { container: document.body, constrainWidth: false } }); // Add change handlers for select yearSelect.addEventListener('change', this._handleYearChange.bind(this)); monthSelect.addEventListener('change', this._handleMonthChange.bind(this)); if (typeof this.options.onDraw === 'function') { this.options.onDraw(this); } } /** * Setup Event Handlers */ _setupEventHandlers() { this._handleInputKeydownBound = this._handleInputKeydown.bind(this); this._handleInputClickBound = this._handleInputClick.bind(this); this._handleInputChangeBound = this._handleInputChange.bind(this); this._handleCalendarClickBound = this._handleCalendarClick.bind(this); this._finishSelectionBound = this._finishSelection.bind(this); this._handleMonthChange = this._handleMonthChange.bind(this); this._closeBound = this.close.bind(this); this.el.addEventListener('click', this._handleInputClickBound); this.el.addEventListener('keydown', this._handleInputKeydownBound); this.el.addEventListener('change', this._handleInputChangeBound); this.calendarEl.addEventListener('click', this._handleCalendarClickBound); this.doneBtn.addEventListener('click', this._finishSelectionBound); this.cancelBtn.addEventListener('click', this._closeBound); if (this.options.showClearBtn) { this._handleClearClickBound = this._handleClearClick.bind(this); this.clearBtn.addEventListener('click', this._handleClearClickBound); } } _setupVariables() { this.$modalEl = $(Datepicker._template); this.modalEl = this.$modalEl[0]; this.calendarEl = this.modalEl.querySelector('.datepicker-calendar'); this.yearTextEl = this.modalEl.querySelector('.year-text'); this.dateTextEl = this.modalEl.querySelector('.date-text'); if (this.options.showClearBtn) { this.clearBtn = this.modalEl.querySelector('.datepicker-clear'); } this.doneBtn = this.modalEl.querySelector('.datepicker-done'); this.cancelBtn = this.modalEl.querySelector('.datepicker-cancel'); this.formats = { d: () => { return this.date.getDate(); }, dd: () => { let d = this.date.getDate(); return (d < 10 ? '0' : '') + d; }, ddd: () => { return this.options.i18n.weekdaysShort[this.date.getDay()]; }, dddd: () => { return this.options.i18n.weekdays[this.date.getDay()]; }, m: () => { return this.date.getMonth() + 1; }, mm: () => { let m = this.date.getMonth() + 1; return (m < 10 ? '0' : '') + m; }, mmm: () => { return this.options.i18n.monthsShort[this.date.getMonth()]; }, mmmm: () => { return this.options.i18n.months[this.date.getMonth()]; }, yy: () => { return ('' + this.date.getFullYear()).slice(2); }, yyyy: () => { return this.date.getFullYear(); } }; } /** * Remove Event Handlers */ _removeEventHandlers() { this.el.removeEventListener('click', this._handleInputClickBound); this.el.removeEventListener('keydown', this._handleInputKeydownBound); this.el.removeEventListener('change', this._handleInputChangeBound); this.calendarEl.removeEventListener('click', this._handleCalendarClickBound); } _handleInputClick() { this.open(); } _handleInputKeydown(e) { if (e.which === M.keys.ENTER) { e.preventDefault(); this.open(); } } _handleCalendarClick(e) { if (!this.isOpen) { return; } let $target = $(e.target); if (!$target.hasClass('is-disabled')) { if ( $target.hasClass('datepicker-day-button') && !$target.hasClass('is-empty') && !$target.parent().hasClass('is-disabled') ) { this.setDate( new Date( e.target.getAttribute('data-year'), e.target.getAttribute('data-month'), e.target.getAttribute('data-day') ) ); if (this.options.autoClose) { this._finishSelection(); } } else if ($target.closest('.month-prev').length) { this.prevMonth(); } else if ($target.closest('.month-next').length) { this.nextMonth(); } } } _handleClearClick() { this.date = null; this.setInputValue(); this.close(); } _handleMonthChange(e) { this.gotoMonth(e.target.value); } _handleYearChange(e) { this.gotoYear(e.target.value); } /** * change view to a specific month (zero-index, e.g. 0: January) */ gotoMonth(month) { if (!isNaN(month)) { this.calendars[0].month = parseInt(month, 10); this.adjustCalendars(); } } /** * change view to a specific full year (e.g. "2012") */ gotoYear(year) { if (!isNaN(year)) { this.calendars[0].year = parseInt(year, 10); this.adjustCalendars(); } } _handleInputChange(e) { let date; // Prevent change event from being fired when triggered by the plugin if (e.firedBy === this) { return; } if (this.options.parse) { date = this.options.parse(this.el.value, this.options.format); } else { date = new Date(Date.parse(this.el.value)); } if (Datepicker._isDate(date)) { this.setDate(date); } } renderDayName(opts, day, abbr) { day += opts.firstDay; while (day >= 7) { day -= 7; } return abbr ? opts.i18n.weekdaysAbbrev[day] : opts.i18n.weekdays[day]; } /** * Set input value to the selected date and close Datepicker */ _finishSelection() { this.setInputValue(); this.close(); } /** * Open Datepicker */ open() { if (this.isOpen) { return; } this.isOpen = true; if (typeof this.options.onOpen === 'function') { this.options.onOpen.call(this); } this.draw(); this.modal.open(); return this; } /** * Close Datepicker */ close() { if (!this.isOpen) { return; } this.isOpen = false; if (typeof this.options.onClose === 'function') { this.options.onClose.call(this); } this.modal.close(); return this; } } Datepicker._template = [ '' ].join(''); M.Datepicker = Datepicker; if (M.jQueryLoaded) { M.initializeJqueryWrapper(Datepicker, 'datepicker', 'M_Datepicker'); } })(cash);