UNPKG

@dmuy/jquery-datepicker

Version:

jQuery date picker plugin for input fields.

1,067 lines (892 loc) 53.4 kB
/* -- DO NOT REMOVE -- * jQuery duDatePicker v1.2.2 plugin * https://github.com/dmuy/duDatepicker * * Author: Dionlee Uy * Email: dionleeuy@gmail.com * * @requires jQuery * -- DO NOT REMOVE -- */ (function (factory) { if ( typeof define === 'function' && define.amd ) { define(['jquery'], factory); } else if (typeof exports === 'object') { module.exports = factory(require('jquery'));; } else { factory(jQuery); } })(function ($) { if (typeof $ === 'undefined') throw new Error('duDatePicker: This plugin requires jQuery'); /** * Gets the number of days based on the month of the given date * @param {Date} date Date object */ function getDaysCount(date) { return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); } /** * Calculates date difference * @param {Date} from Date from * @param {Date} to Date to */ function dateDiff(from, to) { // Take the difference between the dates and divide by milliseconds per day. // Round to nearest whole number to deal with DST. return Math.round((to - from) / (1000 * 60 * 60 * 24)); } /** * Returns the document width and height */ function screenDim() { return { height: $(document).outerHeight(), width: $(document).outerWidth() }; } /** * Calculates the offset of the given html element * @param {Element} el Html element */ function calcOffset(el) { var offset = el.offset(), dim = { height: el.outerHeight(), width: el.outerWidth() }, screen = screenDim(); return { top: offset.top + dim.height, left: offset.left, right: screen.width - (offset.left + dim.width), bottom: screen.height - (offset.top) } } var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], SHORT_MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], DAYS_OF_WEEK = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], SHORT_DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], WEEK_DAYS_HTML = "<span>" + SHORT_DAYS.map(function (x) { return x.substr(0, 2) }).join("</span><span>") + "</span>", EX_KEYS = [9, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123], DCAL_DATA = '_duDatepicker', SELECTED_FORMAT = 'D, mmm d', DUDatePicker = function (elem, options) { var _ = this; _.animating = false; _.visible = false; _.config = options; _.input = $(elem); _.fromEl = _.config.fromTarget ? $(_.config.fromTarget) : null; _.toEl = _.config.toTarget ? $(_.config.toTarget) : null; _.input.prop('hidden', _.config.range && (_.fromEl || _.toEl)) _.viewMode = 'calendar'; _.datepicker = { container: $('<div class="dcalendarpicker"></div>'), wrapper: $('<div class="dudp__wrapper" tabindex="0"></div>'), header: { wrapper: $('<section class="dudp__calendar-header"></section>'), selectedYear: $('<span class="dudp__sel-year"></span>'), selectedDate: $('<span class="dcp_sel-date"></span>') }, calendarHolder: { wrapper: $('<section class="dudp__cal-container"></section>'), btnPrev: $('<span class="dudp__btn-cal-prev">&lsaquo;</span>'), btnNext: $('<span class="dudp__btn-cal-next">&rsaquo;</span>'), calendarViews: { wrapper: $('<div class="dudp__calendar-views"></div>'), calendars: [] }, yearsView: $('<div class="dudp__years-view dp__hidden"></div>'), monthsView: $('<div class="dudp__months-view dp__hidden"></div>'), buttons: { wrapper: $('<div class="dudp__buttons"></div>'), btnClear: $('<span class="dudp__button clear">Clear</span>'), btnCancel: $('<span class="dudp__button cancel">Cancel</span>'), btnOk: $('<span class="dudp__button ok">Ok</span>') } } }; // set default value if (_.config.value) _.input.val(_.config.value) .attr('value', _.config.value); _.minDate = _.input.data('mindate') || _.config.minDate; _.maxDate = _.input.data('maxdate') || _.config.maxDate; // current selected date, default is today if no value given var _date = new Date(); if (_.config.range) { var value = _.input.val(), _range = value.split(_.config.rangeDelim); if (value !== '' && _range.length < 2) throw new Error('duDatePicker: Invalid date range value'); var _from = value === '' ? null : _.parseDate(_range[0]).date, _to = value === '' ? null : _.parseDate(_range[1]).date; _.dateFrom = _from; _.dateTo = _to; _.rangeFrom = null; _.rangeTo = null; _.viewMonth = (_from ? _from : _date).getMonth(); _.viewYear = (_from ? _from : _date).getFullYear(); // set default value if (value) { var valueDisp = _.config.rangeFormatter ? _.config.rangeFormatter.call(_, _from, _to) : value, formattedFrom = _.formatDate(_from, _.config.format), outFrom = _.formatDate(_from, _.config.outFormat || _.config.format), formattedTo = _.formatDate(_to, _.config.format), outTo = _.formatDate(_to, _.config.outFormat || _.config.format); _.input.val(valueDisp).attr('value', valueDisp) .attr('data-range-from', _.formatDate(_from, _.config.outFormat || _.config.format)) .attr('data-range-to', _.formatDate(_to, _.config.outFormat || _.config.format)); if (_.fromEl) { _.fromEl.val(formattedFrom).attr('value', formattedFrom) .attr('data-value', outFrom); } if (_.toEl) { _.toEl.val(formattedTo).attr('value', formattedTo) .attr('data-value', outTo); } } } else { _.date = _.input.val() === '' ? _date : _.parseDate(_.input.val()).date; _.selected = { year: _.date.getFullYear(), month: _.date.getMonth(), date: _.date.getDate() }; _.viewMonth = _.date.getMonth(); _.viewYear = _.date.getFullYear(); } _.inputClick = function() { _.showInFromEl = _.config.inline && _.fromEl && this === _.fromEl[0]; _.showInToEl = _.config.inline && _.toEl && this === _.toEl[0]; _.show(); } _.inputKeydown = function(e) { if (e.keyCode === 13) { _.showInFromEl = _.config.inline && _.fromEl && this === _.fromEl[0]; _.showInToEl = _.config.inline && _.toEl && this === _.toEl[0]; _.show(); } return !(EX_KEYS.indexOf(e.which) < 0); } /** * Unbinds input element(s) */ _.unbindInput = function() { _.input.removeData(DCAL_DATA) .off('click', _.inputClick) .off('keydown', _.inputKeydown) .prop('readonly', false); if (_.fromEl) { _.fromEl.off('click', _.inputClick) .off('keydown', _.inputKeydown) .prop('readonly', false); } if (_.toEl) { _.toEl.off('click', _.inputClick) .off('keydown', _.inputKeydown) .prop('readonly', false); } } _.setupPicker(); _.setSelection(); }; DUDatePicker.prototype = { constructor: DUDatePicker, /** * Initializes the date picker */ setupPicker: function () { var _ = this, picker = _.datepicker, header = picker.header, calendarHolder = picker.calendarHolder, buttons = calendarHolder.buttons, _selected = _.selected ? _.selected : new Date(); // Setup header if (!_.config.inline) { header.wrapper.append(header.selectedYear) .append(header.selectedDate) .appendTo(picker.wrapper); // Switch to years view header.selectedYear.click(function (e) { if (_.viewMode !== 'years') _.switchView('years'); }); // Switch to calendar view (of the selected date) header.selectedDate.click(function (e) { var now = new Date(), _month = _.config.range ? now.getMonth() : _.selected.month, _year = _.config.range ? now.getFullYear() : _.selected.year; if ((_.viewMonth !== _month || _.viewYear !== _year) || _.viewMode !== 'calendar') { _.viewMonth = _month; _.viewYear = _year; _.setupCalendar(); _.switchView('calendar'); } }); } // Setup months view var _month = 0; for (var r = 1; r < 4; r++) { var monthRow = $('<div class="dudp__month-row"></div>'); for (var i = 0; i < 4; i++) { var monthElem = $('<span class="dudp__month"></span>'); if (_month === _selected.month) monthElem.addClass('selected'); monthElem.text(SHORT_MONTHS[_month]) .data('month', _month) .appendTo(monthRow) .on('click', function (e) { var _this = $(this), _data = _this.data('month'); _.viewMonth = _data; _.setupCalendar(); _.switchView('calendar'); }); _month++; } calendarHolder.monthsView.append(monthRow); } // Setup years view calendarHolder.yearsView.html(_.getYears()); if (_.config.clearBtn) buttons.wrapper.append(buttons.btnClear); if (_.config.cancelBtn) buttons.wrapper.append(buttons.btnCancel); if (!_.config.auto || _.config.range) buttons.wrapper.append(buttons.btnOk); calendarHolder.wrapper.append(calendarHolder.btnPrev) .append(calendarHolder.btnNext) .append(calendarHolder.calendarViews.wrapper) .append(calendarHolder.monthsView) .append(calendarHolder.yearsView) .append(buttons.wrapper) .appendTo(picker.wrapper); picker.container.append(picker.wrapper).appendTo('body'); if (_.config.inline) picker.container.attr('inline', true) // Setup theme picker.wrapper.attr('data-theme', _.config.theme || $.fn.duDatepicker.defaults.theme); _.input.on('click', _.inputClick) .on('keydown', _.inputKeydown) .prop('readonly', true); if (_.fromEl) { _.fromEl.on('click', _.inputClick) .on('keydown', _.inputKeydown) .prop('readonly', true); } if (_.toEl) { _.toEl.on('click', _.inputClick) .on('keydown', _.inputKeydown) .prop('readonly', true); } calendarHolder.btnPrev.click(function (e) { _.move('prev') }); calendarHolder.btnNext.click(function (e) { _.move('next') }); // Switch view to months view calendarHolder.calendarViews.wrapper .on('click', '.dudp__cal-month-year', function (e) { var target = $(e.target) if (target.hasClass('cal-year')) { _.switchView('years'); } else if (target.hasClass('cal-month')) { _.switchView('months'); } }); // clear button click if (_.config.clearBtn) { buttons.btnClear.click(function () { if (_.config.range) { _.dateFrom = null; _.dateTo = null; _.input.val('').attr('value', '') .attr('data-range-from', null).attr('data-range-to', null); if (_.fromEl) _.fromEl.val('').attr('value', '').attr('data-value', null); if (_.toEl) _.toEl.val('').attr('value', '').attr('data-value', null); _.triggerChange($.Event('datechanged', { _dateFrom: null, _dateTo: null, dateFrom: null, dateTo: null, value: '' })); } else { var now = new Date(); _.date = now; _.input.val('').attr('value', ''); _.triggerChange($.Event('datechanged', { _date: null, date: null })); } _.hide(); }); } // overlay click if (_.config.overlayClose) { picker.container.click(function (e) { _.hide() }); picker.wrapper.click(function (e) { e.stopPropagation() }); } picker.wrapper.on('keydown', function (e) { if (e.keyCode === 27) _.hide() }) if (_.config.inline) { picker.wrapper.on('blur', function () { _.hide() }) } if (_.config.cancelBtn) buttons.btnCancel.click(function () { _.hide() }); // OK button click buttons.btnOk.click(function () { if (_.config.range) { if (!_.rangeFrom || !_.rangeTo) return; var _from = new Date(_.rangeFrom.year, _.rangeFrom.month, _.rangeFrom.date), _to = new Date(_.rangeTo.year, _.rangeTo.month, _.rangeTo.date); if (_.disabledDate(_from) || _.disabledDate(_to)) return; _.dateFrom = _from; _.dateTo = _to; _.setValue([_.formatDate(_from, _.config.format), _.formatDate(_to, _.config.format)].join(_.config.rangeDelim)); } else { var _date = new Date(_.selected.year, _.selected.month, _.selected.date); if (_.disabledDate(_date)) return; _.date = _date; _.setValue(_.date); } _.hide(); }); }, /** * Determines if date is in the selected date range * @param {Date} date Date object */ inRange: function (date) { if (!this.config.range) return false; var _ = this, _from = _.rangeFrom ? new Date(_.rangeFrom.year, _.rangeFrom.month, _.rangeFrom.date) : null, _to = _.rangeTo ? new Date(_.rangeTo.year, _.rangeTo.month, _.rangeTo.date) : null; return (_from && date > _from) && (_to && date < _to); }, /** * Determines if date is disabled * @param {Date} date Date object */ disabledDate: function (date) { var _ = this, min = null, max = null, now = new Date(), today = new Date(now.getFullYear(), now.getMonth(), now.getDate()), dsabldDates = _.config.disabledDates, dsabldDays = _.config.disabledDays, inDsabldDates = dsabldDates.filter(function (x) { if (x.indexOf('-') >= 0) return (date >= _.parseDate(x.split('-')[0]).date && date <= _.parseDate(x.split('-')[1]).date) else return _.parseDate(x).date.getTime() === date.getTime() }).length > 0, inDsabledDays = dsabldDays.indexOf(DAYS_OF_WEEK[date.getDay()]) >= 0 || dsabldDays.indexOf(SHORT_DAYS[date.getDay()]) >= 0 || dsabldDays.indexOf(SHORT_DAYS.map(function (x) { return x.substr(0, 2) })[date.getDay()]) >= 0; if (_.minDate) min = _.minDate === "today" ? today : new Date(_.minDate); if (_.maxDate) max = _.maxDate === "today" ? today : new Date(_.maxDate); return (min && date < min) || (max && date > max) || (inDsabldDates || inDsabledDays); }, /** * Returns the dates (DOM) of the specified month and year * @param {number} year Year value * @param {number} month Month value */ getDates: function (year, month) { var _ = this, day = 1, now = new Date(), today = new Date(now.getFullYear(), now.getMonth(), now.getDate()), selected = _.config.range ? null : new Date(_.selected.year, _.selected.month, _.selected.date), rangeFrom = _.rangeFrom ? new Date(_.rangeFrom.year, _.rangeFrom.month, _.rangeFrom.date) : null, rangeTo = _.rangeTo ? new Date(_.rangeTo.year, _.rangeTo.month, _.rangeTo.date) : null, date = new Date(year, month, day), totalDays = getDaysCount(date), nmStartDay = 1, weeks = []; for (var week = 1; week <= 6; week++) { var daysOfWeek = [$('<span class="dudp__date"></span>'), $('<span class="dudp__date"></span>'), $('<span class="dudp__date"></span>'), $('<span class="dudp__date"></span>'), $('<span class="dudp__date"></span>'), $('<span class="dudp__date"></span>'), $('<span class="dudp__date"></span>')]; while (day <= totalDays) { date.setDate(day); var dayOfWeek = date.getDay(); daysOfWeek[dayOfWeek].data('date', day).data('month', month).data('year', year); if (date.getTime() === today.getTime()) daysOfWeek[dayOfWeek].addClass('current').attr('title', 'Today'); if (_.disabledDate(date)) daysOfWeek[dayOfWeek].addClass('disabled'); if (_.inRange(date)) daysOfWeek[dayOfWeek].addClass('in-range'); if (week === 1 && dayOfWeek === 0) { break; } else if (dayOfWeek < 6) { if (!_.config.range && date.getTime() === selected.getTime()) daysOfWeek[dayOfWeek].addClass('selected'); if (_.config.range && rangeFrom && date.getTime() === rangeFrom.getTime()) daysOfWeek[dayOfWeek].addClass('range-from'); if (_.config.range && rangeTo && date.getTime() === rangeTo.getTime()) daysOfWeek[dayOfWeek].addClass('range-to'); daysOfWeek[dayOfWeek].text(day++); } else { if (!_.config.range && date.getTime() === selected.getTime()) daysOfWeek[dayOfWeek].addClass('selected'); if (_.config.range && rangeFrom && date.getTime() === rangeFrom.getTime()) daysOfWeek[dayOfWeek].addClass('range-from'); if (_.config.range && rangeTo && date.getTime() === rangeTo.getTime()) daysOfWeek[dayOfWeek].addClass('range-to'); daysOfWeek[dayOfWeek].text(day++); break; } } /* For days of previous and next month */ if (week === 1 || week > 4) { // First week if (week === 1) { var prevMonth = new Date(year, month - 1, 1), prevMonthDays = getDaysCount(prevMonth); for (var a = 6; a >= 0; a--) { if (daysOfWeek[a].text() !== '') continue; daysOfWeek[a].data('date', prevMonthDays).data('month', month - 1).data('year', year); prevMonth.setDate(prevMonthDays); daysOfWeek[a].text((prevMonthDays--)).addClass('dudp__pm'); if (_.disabledDate(prevMonth)) daysOfWeek[a].addClass('disabled'); if (_.inRange(prevMonth)) daysOfWeek[a].addClass('in-range'); if (prevMonth.getTime() === today.getTime()) daysOfWeek[a].addClass('current').attr('title', 'Today'); if (!_.config.range && prevMonth.getTime() === selected.getTime()) daysOfWeek[a].addClass('selected'); if (_.config.range && rangeFrom && prevMonth.getTime() === rangeFrom.getTime()) daysOfWeek[a].addClass('range-from'); if (_.config.range && rangeTo && prevMonth.getTime() === rangeTo.getTime()) daysOfWeek[a].addClass('range-to'); } } // Last week else if (week > 4) { var nextMonth = new Date(year, month + 1, 1); for (var a = 0; a <= 6; a++) { if (daysOfWeek[a].text() !== '') continue; daysOfWeek[a].data('date', nmStartDay).data('month', month + 1).data('year', year); nextMonth.setDate(nmStartDay); daysOfWeek[a].text((nmStartDay++)).addClass('dudp__nm'); if (_.disabledDate(nextMonth)) daysOfWeek[a].addClass('disabled'); if (_.inRange(nextMonth)) daysOfWeek[a].addClass('in-range'); if (nextMonth.getTime() === today.getTime()) daysOfWeek[a].addClass('current').attr('title', 'Today'); if (!_.config.range && nextMonth.getTime() === selected.getTime()) daysOfWeek[a].addClass('selected'); if (_.config.range && rangeFrom && nextMonth.getTime() === rangeFrom.getTime()) daysOfWeek[a].addClass('range-from'); if (_.config.range && rangeTo && nextMonth.getTime() === rangeTo.getTime()) daysOfWeek[a].addClass('range-to'); } } } weeks.push(daysOfWeek); } var calDates = []; $.each(weeks, function (idx, dow) { var calWeek = $('<div class="dudp__cal-week"></div>'); for (var i = 0; i < dow.length; i++) { var dateElem = dow[i]; // Attach click handler for dates dateElem.click(function (e) { var _this = $(this), _year = _this.data('year'), _month = _this.data('month'), _date = _this.data('date'), _selected = new Date(_year, _month, _date), isFrom = false; if (_.disabledDate(_selected)) return; if (_.config.range) { var rangeFrom = _.rangeFrom ? new Date(_.rangeFrom.year, _.rangeFrom.month, _.rangeFrom.date) : null, rangeTo = _.rangeTo ? new Date(_.rangeTo.year, _.rangeTo.month, _.rangeTo.date) : null; if (!_.rangeFrom || (_.rangeFrom && _selected < rangeFrom) || (_.rangeFrom && _.rangeTo && dateDiff(rangeFrom, _selected) <= dateDiff(_selected, rangeTo) && dateDiff(rangeFrom, _selected) !== 0) || (_.rangeFrom && _.rangeTo && rangeTo.getTime() === _selected.getTime())) { _.rangeFrom = { year: _year, month: _month, date: _date }; isFrom = true; } else if (!_.rangeTo || (_.rangeTo && _selected > rangeTo) || (_.rangeFrom && _.rangeTo && dateDiff(_selected, rangeTo) < dateDiff(rangeFrom, _selected) && dateDiff(_selected, rangeTo) !== 0) || (_.rangeFrom && _.rangeTo && rangeFrom.getTime() === _selected.getTime())) { _.rangeTo = { year: _year, month: _month, date: _date }; isFrom = false; } _this.parents('.dudp__calendar-views').find('.dudp__date').each(function (idx, delem) { var _elem = $(delem), _deYear = _elem.data('year'), _deMonth = _elem.data('month'), _deDate = _elem.data('date'), _inRange = _.inRange(new Date(_deYear, _deMonth, _deDate)); _elem[(_year === _deYear && _month === _deMonth && _date === _deDate) ? 'addClass' : 'removeClass'](isFrom ? 'range-from' : 'range-to'); if (_inRange) _elem.addClass('in-range'); else _elem.removeClass('in-range'); }); } else { _this.parents('.dudp__calendar-views').find('.dudp__date').each(function (idx, delem) { var _deYear = $(delem).data('year'), _deMonth = $(delem).data('month'), _deDate = $(delem).data('date'); $(delem)[(_year === _deYear && _month === _deMonth && _date === _deDate) ? 'addClass' : 'removeClass']('selected'); }); _.selected = { year: _year, month: _month, date: _date }; _.setSelection(); if (_.config.auto) { _.date = _selected; _.setValue(_.date); _.hide(); } } _this.parents('.dudp__cal-container').find('.dudp__month').each(function (idx, melem) { var _meMonth = $(melem).data('month'); $(melem)[_meMonth === _month ? 'addClass' : 'removeClass']('selected'); }); }); calWeek.append(dateElem); } calDates.push(calWeek); }); return calDates; }, /** * Returns years (DOM) range for the years view */ getYears: function () { var _ = this, _minYear = _.viewYear - 50, _maxYear = _.viewYear + 50, _years = []; for (var y = _minYear; y <= _maxYear; y++) { var yearElem = $('<span class="dudp__year"></span>'); if (y === _.viewYear) yearElem.addClass('selected'); yearElem.text(y) .data('year', y) .on('click', function (e) { var _this = $(this), _data = _this.data('year'); _.viewYear = _data; if (!_.config.range) _.selected.year = _data; _.setSelection(); _.setupCalendar(); _.switchView('calendar'); }); _years.push(yearElem); } return _years; }, /** * Sets up the calendar views */ setupCalendar: function () { var _ = this, viewsHolder = _.datepicker.calendarHolder.calendarViews, _year = _.viewYear, _month = _.viewMonth; viewsHolder.calendars.length = 0; var inView = { wrapper: $('<div class="dudp__calendar"></div>'), header: $('<div class="dudp__cal-month-year"></div>'), weekDays: $('<div class="dudp__weekdays">' + WEEK_DAYS_HTML + '</div>'), datesHolder: $('<div class="dudp__dates-holder"></div>') }, prev = { wrapper: $('<div class="dudp__calendar"></div>'), header: $('<div class="dudp__cal-month-year"></div>'), weekDays: $('<div class="dudp__weekdays">' + WEEK_DAYS_HTML + '</div>'), datesHolder: $('<div class="dudp__dates-holder"></div>') }, next = { wrapper: $('<div class="dudp__calendar"></div>'), header: $('<div class="dudp__cal-month-year"></div>'), weekDays: $('<div class="dudp__weekdays">' + WEEK_DAYS_HTML + '</div>'), datesHolder: $('<div class="dudp__dates-holder"></div>') }, prevMonth = _month === 0 ? 11 : _month - 1, nextMonth = _month === 11 ? 0 : _month + 1, prevYear = _month === 0 ? _year - 1 : _year, nextYear = _month === 11 ? _year + 1 : _year; prev.header.append('<span class="cal-month">' + MONTHS[prevMonth] + '</span>') .append('<span class="cal-year">' + prevYear + '</span>') .appendTo(prev.wrapper); prev.wrapper.append(prev.weekDays); prev.datesHolder.html(_.getDates(prevYear, prevMonth)).appendTo(prev.wrapper); viewsHolder.calendars.push(prev); inView.header.append('<span class="cal-month">' + MONTHS[_month] + '</span>') .append('<span class="cal-year">' + _year + '</span>') .appendTo(inView.wrapper); inView.wrapper.append(inView.weekDays); inView.datesHolder.html(_.getDates(_year, _month)).appendTo(inView.wrapper); viewsHolder.calendars.push(inView); next.header.append('<span class="cal-month">' + MONTHS[nextMonth] + '</span>') .append('<span class="cal-year">' + nextYear + '</span>') .appendTo(next.wrapper); next.wrapper.append(next.weekDays); next.datesHolder.html(_.getDates(nextYear, nextMonth)).appendTo(next.wrapper); viewsHolder.calendars.push(next); viewsHolder.wrapper.empty() .append(prev.wrapper) .append(inView.wrapper) .append(next.wrapper); }, /** * Moves the calendar to specified direction (previous or next) * @param {string} direction Move direction (next | prev) */ move: function (direction) { if (direction !== 'next' && direction !== 'prev') return; if (this.animating) return; var _ = this, picker = _.datepicker, viewsHolder = picker.calendarHolder.calendarViews, _animDuration = 250, _isNext = direction === 'next'; if (_isNext ? _.viewMonth + 1 > 11 : _.viewMonth - 1 < 0) _.viewYear += (_isNext ? 1 : -1); _.viewMonth = _isNext ? (_.viewMonth + 1 > 11 ? 0 : _.viewMonth + 1) : (_.viewMonth - 1 < 0 ? 11 : _.viewMonth - 1); _.animating = true; //Start animation var animateClass = 'dp__animate-' + (_isNext ? 'left' : 'right'); viewsHolder.wrapper.find('.dudp__calendar').addClass(animateClass); //Setup new (previos or next) month calendar var _year = _.viewYear, _month = _isNext ? _.viewMonth + 1 : _.viewMonth - 1; if (_isNext ? _month > 11 : _month < 0) { _month = _isNext ? 0 : 11; _year += _isNext ? 1 : -1; } var newCalDates = _.getDates(_year, _month), newCalEl = { wrapper: $('<div class="dudp__calendar"></div>'), header: $('<div class="dudp__cal-month-year"></div>'), weekDays: $('<div class="dudp__weekdays">' + WEEK_DAYS_HTML + '</div>'), datesHolder: $('<div class="dudp__dates-holder"></div>') }, calDate = new Date(_year, _month, 1); newCalEl.header.append('<span class="cal-month">' + _.formatDate(calDate, 'mmmm') + '</span>') .append('<span class="cal-year">' + _.formatDate(calDate, 'yyyy') + '</span>') .appendTo(newCalEl.wrapper); newCalEl.wrapper.append(newCalEl.weekDays); newCalEl.datesHolder.html(newCalDates).appendTo(newCalEl.wrapper); setTimeout(function () { viewsHolder.wrapper[_isNext ? 'append' : 'prepend'](newCalEl.wrapper); viewsHolder.wrapper.find('.dudp__calendar').removeClass(animateClass); viewsHolder.calendars[_isNext ? 0 : 2].wrapper.remove(); viewsHolder.calendars[_isNext ? 'shift' : 'pop'](); viewsHolder.calendars[_isNext ? 'push' : 'unshift'](newCalEl); _.animating = false; }, _animDuration); }, /** * Switches view of picker (calendar, months, years) * @param {string} view View name */ switchView: function (view) { if (view !== 'calendar' && view !== 'months' && view !== 'years') return; var _ = this, picker = _.datepicker, monthsView = picker.calendarHolder.monthsView, yearsView = picker.calendarHolder.yearsView, calViews = picker.calendarHolder.calendarViews.wrapper, _animDuration = 250; _.viewMode = view; switch (view) { case 'calendar': var _calendar = calViews.find('.dudp__calendar:eq(1)'); // current month in view calViews.addClass('dp__animate-out').removeClass('dp__hidden'); _calendar.addClass('dp__zooming dp__animate-zoom'); picker.calendarHolder.btnPrev.removeClass('dp__hidden'); picker.calendarHolder.btnNext.removeClass('dp__hidden'); picker.calendarHolder.buttons.wrapper.removeClass('dp__hidden'); setTimeout(function () { calViews.removeClass('dp__animate-out'); _calendar.removeClass('dp__animate-zoom'); }, 10); monthsView.addClass('dp__animate-out'); yearsView.addClass('dp__hidden'); setTimeout(function () { _calendar.removeClass('dp__zooming'); monthsView.addClass('dp__hidden').removeClass('dp__animate-out'); }, _animDuration); break; case 'months': picker.calendarHolder.btnPrev.addClass('dp__hidden'); picker.calendarHolder.btnNext.addClass('dp__hidden'); picker.calendarHolder.buttons.wrapper.addClass('dp__hidden'); calViews.addClass('dp__animate-out'); monthsView.addClass('dp__animate-out').removeClass('dp__hidden'); setTimeout(function () { monthsView.removeClass('dp__animate-out'); }, 10); setTimeout(function () { calViews.addClass('dp__hidden').removeClass('dp__animate-out'); }, _animDuration); break; case 'years': yearsView.html(_.getYears()); var _selYear = yearsView.find('.dudp__year.selected'); yearsView.scrollTop(_selYear[0].offsetTop - 120); picker.calendarHolder.btnPrev.addClass('dp__hidden'); picker.calendarHolder.btnNext.addClass('dp__hidden'); picker.calendarHolder.buttons.wrapper.addClass('dp__hidden'); yearsView.removeClass('dp__hidden'); monthsView.addClass('dp__animate-out'); calViews.addClass('dp__animate-out'); setTimeout(function () { calViews.addClass('dp__hidden').removeClass('dp__animate-out'); monthsView.addClass('dp__hidden').removeClass('dp__animate-out'); }, _animDuration); break; } }, /** * Resets the selection to the date value of the input */ resetSelection: function () { var _ = this; if (_.config.range) { var _date = _.dateFrom ? _.dateFrom : new Date(); _.rangeFrom = _.dateFrom ? { year: _.dateFrom.getFullYear(), month: _.dateFrom.getMonth(), date: _.dateFrom.getDate() } : null; _.rangeTo = _.dateTo ? { year: _.dateTo.getFullYear(), month: _.dateTo.getMonth(), date: _.dateTo.getDate() } : null; _.viewYear = _date.getFullYear(); _.viewMonth = _date.getMonth(); } else { _.selected = { year: _.date.getFullYear(), month: _.date.getMonth(), date: _.date.getDate() }; _.viewYear = _.selected.year; _.viewMonth = _.selected.month; } _.datepicker.calendarHolder.monthsView.find('.dudp__month').each(function (idx, melem) { var _meMonth = $(melem).data('month'), _month = _.config.range ? _.dateFrom ? _.dateFrom.getMonth() : null : _.selected.month; $(melem)[_meMonth === _month ? 'addClass' : 'removeClass']('selected'); }); }, /* Sets the selection display (datepicker header) */ setSelection: function () { var _ = this, picker = _.datepicker, selected = _.config.range ? new Date() : new Date(_.selected.year, _.selected.month, _.selected.date); picker.header.selectedYear.text(selected.getFullYear()); picker.header.selectedDate.text(_.formatDate(selected, SELECTED_FORMAT)); }, /** * Sets the value of the input (either Date object or string) * @param {string|Date} value Date picker value */ setValue: function (value) { if (typeof value === 'undefined') throw new Error('Expecting a value.'); var _ = this, _empty = typeof value === 'string' && value === '', changeData = null; if (_.config.range) { var _range = _empty ? [] : value.split(_.config.rangeDelim); if (value !== '' && _range.length < 2) throw new Error('duDatePicker: Invalid date range value'); var now = new Date(), _from = _empty ? null : _.parseDate(_range[0]).date, _to = _empty ? null : _.parseDate(_range[1]).date, formattedFrom = _empty ? '' : _.formatDate(_from, _.config.format), outFrom = _empty ? '' : _.formatDate(_from, _.config.outFormat || _.config.format), formattedTo = _empty ? '' : _.formatDate(_to, _.config.format), outTo = _empty ? '' : _.formatDate(_to, _.config.outFormat || _.config.format), valueDisp = _empty ? '' : ( _.config.rangeFormatter ? _.config.rangeFormatter.call(_, _from, _to) : _range[0] === _range[1] ? _range[0] : value); _.dateFrom = _from; _.dateTo = _to; _.viewYear = (_from ? _from : now).getFullYear(); _.viewMonth = (_from ? _from : now).getMonth(); _.input.val(valueDisp).attr('value', valueDisp) .attr('data-range-from', formattedFrom).attr('data-range-to', formattedTo); if (_.fromEl) { _.fromEl.val(formattedFrom).attr('value', formattedFrom) .attr('data-value', outFrom); } if (_.toEl) { _.toEl.val(formattedTo).attr('value', formattedTo) .attr('data-value', outTo); } changeData = { _dateFrom: _from, _dateTo: _to, dateFrom: formattedFrom, dateTo: formattedTo, value: valueDisp }; } else { var date = typeof value === 'string' ? _.parseDate(value, _.config.format).date : value, formatted = _.formatDate(date, _.config.format); _.date = date; _.viewYear = date.getFullYear(); _.viewMonth = date.getMonth(); _.input.val(formatted) .attr('value', formatted); changeData = { _date: date, date: _.formatDate(_.date, _.config.outFormat || _.config.format) }; } _.triggerChange($.Event('datechanged', changeData)); }, /** * Triggers the datechanged and onchange (for asp.net) events * @param {Event} evt Event object */ triggerChange: function (evt) { this.input.trigger(evt).trigger('onchange').trigger('change'); }, /** * Parses date string using default or specified format * @param {string} date Date string * @param {string} dateFormat Date format */ parseDate: function (date, dateFormat) { var _ = this, format = typeof dateFormat === 'undefined' ? _.config.format : dateFormat, dayLength = (format.match(/d/g) || []).length, monthLength = (format.match(/m/g) || []).length, yearLength = (format.match(/y/g) || []).length, isFullMonth = monthLength === 4, isMonthNoPadding = monthLength === 1, isDayNoPadding = dayLength === 1, lastIndex = date.length, firstM = format.indexOf('m'), firstD = format.indexOf('d'), firstY = format.indexOf('y'), month = '', day = '', year = ''; if (date === '') return { m: null, d: null, y: null, date: new Date('') }; // Get month on given date string using the format (default or specified) if (isFullMonth) { var monthIdx = -1; $.each(MONTHS, function (i, m) { if (date.indexOf(m) >= 0) monthIdx = i; }); month = MONTHS[monthIdx]; format = format.replace('mmmm', month); firstD = format.indexOf('d'); firstY = firstY < firstM ? format.indexOf('y') : format.indexOf('y', format.indexOf(month) + month.length); } else if (!isDayNoPadding && !isMonthNoPadding || (isDayNoPadding && !isMonthNoPadding && firstM < firstD)) { month = date.substr(firstM, monthLength); } else { var lastIndexM = format.lastIndexOf('m'), before = format.substring(firstM - 1, firstM), after = format.substring(lastIndexM + 1, lastIndexM + 2); if (lastIndexM === format.length - 1) { month = date.substring(date.indexOf(before, firstM - 1) + 1, lastIndex); } else if (firstM === 0) { month = date.substring(0, date.indexOf(after, firstM)); } else { month = date.substring(date.indexOf(before, firstM - 1) + 1, date.indexOf(after, firstM + 1)); } } // Get date on given date string using the format (default or specified) if (!isDayNoPadding && !isMonthNoPadding || (!isDayNoPadding && isMonthNoPadding && firstD < firstM)) { day = date.substr(firstD, dayLength); } else { var lastIndexD = format.lastIndexOf('d'), before = format.substring(firstD - 1, firstD), after = format.substring(lastIndexD + 1, lastIndexD + 2); if (lastIndexD === format.length - 1) { day = date.substring(date.indexOf(before, firstD - 1) + 1, lastIndex); } else if (firstD === 0) { day = date.substring(0, date.indexOf(after, firstD)); } else { day = date.substring(date.indexOf(before, firstD - 1) + 1, date.indexOf(after, firstD + 1)); } } // Get year on given date string using the format (default or specified) if (!isMonthNoPadding && !isDayNoPadding || (isMonthNoPadding && isDayNoPadding && firstY < firstM && firstY < firstD) || (!isMonthNoPadding && isDayNoPadding && firstY < firstD) || (isMonthNoPadding && !isDayNoPadding && firstY < firstM)) { year = date.substr(firstY, yearLength); } else { before = format.substring(firstY - 1, firstY); year = date.substr(date.indexOf(before, firstY - 1) + 1, yearLength); } return { m: month, d: day, y: year, date: isNaN(parseInt(month)) ? new Date(month + " " + day + ", " + year) : new Date(year, month - 1, day) }; }, /** * Returns formatted string representation of specified date * @param {Date} date Date object * @param {string} format Date format */ formatDate: function (date, format) { var d = new Date(date), day = d.getDate(), m = d.getMonth(), y = d.getFullYear(); return format.replace(/(yyyy|yy|mmmm|mmm|mm|m|DD|D|dd|d)/g, function (e) { switch (e) { case 'd': return day; case 'dd': return (day < 10 ? "0" + day : day); case 'D': return SHORT_DAYS[d.getDay()]; case 'DD': return DAYS_OF_WEEK[d.getDay()]; case 'm': return m + 1; case 'mm': return (m + 1 < 10 ? "0" + (m + 1) : (m + 1)); case 'mmm': return SHORT_MONTHS[m]; case 'mmmm': return MONTHS[m]; case 'yy': return y.toString().substr(2, 2); case 'yyyy': return y; } }); }, /** * Shows the date picker */ show: function () { var _ = this; $('body').attr('datepicker-display', 'on'); _.resetSelection(); _.setSelection(); _.setupCalendar(); _.datepicker.container.addClass('dp__open'); if (_.config.inline) { var inputRef = _.showInFromEl ? _.fromEl : _.showInToEl ? _.toEl : _.input, offset = calcOffset(inputRef), picker_dim = { height: _.datepicker.wrapper.outerHeight(), width: _.datepicker.wrapper.outerWidth() }, screen_dim = screenDim(), below = offset.top + picker_dim.height < screen_dim.height, left_side = offset.left + picker_dim.width < screen_dim.width, offsetCss = {}, scroll = { y: window.scrollY, x: window.scrollX }; offsetCss[below ? 'top' : 'bottom'] = below ? offset.top - scroll.y : offset.bottom; offsetCss[left_