976 lines
27 KiB
JavaScript
976 lines
27 KiB
JavaScript
(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 '<td class="is-empty"></td>';
|
||
}
|
||
}
|
||
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 (
|
||
`<td data-day="${opts.day}" class="${arr.join(' ')}" aria-selected="${ariaSelected}">` +
|
||
`<button class="datepicker-day-button" type="button" data-year="${opts.year}" data-month="${
|
||
opts.month
|
||
}" data-day="${opts.day}">${opts.day}</button>` +
|
||
'</td>'
|
||
);
|
||
}
|
||
|
||
renderRow(days, isRTL, isRowSelected) {
|
||
return (
|
||
'<tr class="datepicker-row' +
|
||
(isRowSelected ? ' is-selected' : '') +
|
||
'">' +
|
||
(isRTL ? days.reverse() : days).join('') +
|
||
'</tr>'
|
||
);
|
||
}
|
||
|
||
renderTable(opts, data, randId) {
|
||
return (
|
||
'<div class="datepicker-table-wrapper"><table cellpadding="0" cellspacing="0" class="datepicker-table" role="grid" aria-labelledby="' +
|
||
randId +
|
||
'">' +
|
||
this.renderHead(opts) +
|
||
this.renderBody(data) +
|
||
'</table></div>'
|
||
);
|
||
}
|
||
|
||
renderHead(opts) {
|
||
let i,
|
||
arr = [];
|
||
for (i = 0; i < 7; i++) {
|
||
arr.push(
|
||
`<th scope="col"><abbr title="${this.renderDayName(opts, i)}">${this.renderDayName(
|
||
opts,
|
||
i,
|
||
true
|
||
)}</abbr></th>`
|
||
);
|
||
}
|
||
return '<thead><tr>' + (opts.isRTL ? arr.reverse() : arr).join('') + '</tr></thead>';
|
||
}
|
||
|
||
renderBody(rows) {
|
||
return '<tbody>' + rows.join('') + '</tbody>';
|
||
}
|
||
|
||
renderTitle(instance, c, year, month, refYear, randId) {
|
||
let i,
|
||
j,
|
||
arr,
|
||
opts = this.options,
|
||
isMinYear = year === opts.minYear,
|
||
isMaxYear = year === opts.maxYear,
|
||
html =
|
||
'<div id="' +
|
||
randId +
|
||
'" class="datepicker-controls" role="heading" aria-live="assertive">',
|
||
monthHtml,
|
||
yearHtml,
|
||
prev = true,
|
||
next = true;
|
||
|
||
for (arr = [], i = 0; i < 12; i++) {
|
||
arr.push(
|
||
'<option value="' +
|
||
(year === refYear ? i - c : 12 + i - c) +
|
||
'"' +
|
||
(i === month ? ' selected="selected"' : '') +
|
||
((isMinYear && i < opts.minMonth) || (isMaxYear && i > opts.maxMonth)
|
||
? 'disabled="disabled"'
|
||
: '') +
|
||
'>' +
|
||
opts.i18n.months[i] +
|
||
'</option>'
|
||
);
|
||
}
|
||
|
||
monthHtml =
|
||
'<select class="datepicker-select orig-select-month" tabindex="-1">' +
|
||
arr.join('') +
|
||
'</select>';
|
||
|
||
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(`<option value="${i}" ${i === year ? 'selected="selected"' : ''}>${i}</option>`);
|
||
}
|
||
}
|
||
|
||
yearHtml = `<select class="datepicker-select orig-select-year" tabindex="-1">${arr.join(
|
||
''
|
||
)}</select>`;
|
||
|
||
let leftArrow =
|
||
'<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z"/><path d="M0-.5h24v24H0z" fill="none"/></svg>';
|
||
html += `<button class="month-prev${
|
||
prev ? '' : ' is-disabled'
|
||
}" type="button">${leftArrow}</button>`;
|
||
|
||
html += '<div class="selects-container">';
|
||
if (opts.showMonthAfterYear) {
|
||
html += yearHtml + monthHtml;
|
||
} else {
|
||
html += monthHtml + yearHtml;
|
||
}
|
||
html += '</div>';
|
||
|
||
if (isMinYear && (month === 0 || opts.minMonth >= month)) {
|
||
prev = false;
|
||
}
|
||
|
||
if (isMaxYear && (month === 11 || opts.maxMonth <= month)) {
|
||
next = false;
|
||
}
|
||
|
||
let rightArrow =
|
||
'<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"/><path d="M0-.25h24v24H0z" fill="none"/></svg>';
|
||
html += `<button class="month-next${
|
||
next ? '' : ' is-disabled'
|
||
}" type="button">${rightArrow}</button>`;
|
||
|
||
return (html += '</div>');
|
||
}
|
||
|
||
/**
|
||
* 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 = [
|
||
'<div class= "modal datepicker-modal">',
|
||
'<div class="modal-content datepicker-container">',
|
||
'<div class="datepicker-date-display">',
|
||
'<span class="year-text"></span>',
|
||
'<span class="date-text"></span>',
|
||
'</div>',
|
||
'<div class="datepicker-calendar-container">',
|
||
'<div class="datepicker-calendar"></div>',
|
||
'<div class="datepicker-footer">',
|
||
'<button class="btn-flat datepicker-clear waves-effect" style="visibility: hidden;" type="button"></button>',
|
||
'<div class="confirmation-btns">',
|
||
'<button class="btn-flat datepicker-cancel waves-effect" type="button"></button>',
|
||
'<button class="btn-flat datepicker-done waves-effect" type="button"></button>',
|
||
'</div>',
|
||
'</div>',
|
||
'</div>',
|
||
'</div>',
|
||
'</div>'
|
||
].join('');
|
||
|
||
M.Datepicker = Datepicker;
|
||
|
||
if (M.jQueryLoaded) {
|
||
M.initializeJqueryWrapper(Datepicker, 'datepicker', 'M_Datepicker');
|
||
}
|
||
})(cash);
|