2236 lines
75 KiB
JavaScript
2236 lines
75 KiB
JavaScript
|
;(function (window, $, undefined) { ;(function () {
|
|||
|
var VERSION = '2.2.3',
|
|||
|
pluginName = 'datepicker',
|
|||
|
autoInitSelector = '.datepicker-here',
|
|||
|
$body, $datepickersContainer,
|
|||
|
containerBuilt = false,
|
|||
|
baseTemplate = '' +
|
|||
|
'<div class="datepicker">' +
|
|||
|
'<i class="datepicker--pointer"></i>' +
|
|||
|
'<nav class="datepicker--nav"></nav>' +
|
|||
|
'<div class="datepicker--content"></div>' +
|
|||
|
'</div>',
|
|||
|
defaults = {
|
|||
|
classes: '',
|
|||
|
inline: false,
|
|||
|
language: 'ru',
|
|||
|
startDate: new Date(),
|
|||
|
firstDay: '',
|
|||
|
weekends: [6, 0],
|
|||
|
dateFormat: '',
|
|||
|
altField: '',
|
|||
|
altFieldDateFormat: '@',
|
|||
|
toggleSelected: true,
|
|||
|
keyboardNav: true,
|
|||
|
|
|||
|
position: 'bottom left',
|
|||
|
offset: 12,
|
|||
|
|
|||
|
view: 'days',
|
|||
|
minView: 'days',
|
|||
|
|
|||
|
showOtherMonths: true,
|
|||
|
selectOtherMonths: true,
|
|||
|
moveToOtherMonthsOnSelect: true,
|
|||
|
|
|||
|
showOtherYears: true,
|
|||
|
selectOtherYears: true,
|
|||
|
moveToOtherYearsOnSelect: true,
|
|||
|
|
|||
|
minDate: '',
|
|||
|
maxDate: '',
|
|||
|
disableNavWhenOutOfRange: true,
|
|||
|
|
|||
|
multipleDates: false, // Boolean or Number
|
|||
|
multipleDatesSeparator: ',',
|
|||
|
range: false,
|
|||
|
|
|||
|
todayButton: false,
|
|||
|
clearButton: false,
|
|||
|
|
|||
|
showEvent: 'focus',
|
|||
|
autoClose: false,
|
|||
|
|
|||
|
// navigation
|
|||
|
monthsField: 'monthsShort',
|
|||
|
prevHtml: '<svg><path d="M 17,12 l -5,5 l 5,5"></path></svg>',
|
|||
|
nextHtml: '<svg><path d="M 14,12 l 5,5 l -5,5"></path></svg>',
|
|||
|
navTitles: {
|
|||
|
days: 'MM, <i>yyyy</i>',
|
|||
|
months: 'yyyy',
|
|||
|
years: 'yyyy1 - yyyy2'
|
|||
|
},
|
|||
|
|
|||
|
// timepicker
|
|||
|
timepicker: false,
|
|||
|
onlyTimepicker: false,
|
|||
|
dateTimeSeparator: ' ',
|
|||
|
timeFormat: '',
|
|||
|
minHours: 0,
|
|||
|
maxHours: 24,
|
|||
|
minMinutes: 0,
|
|||
|
maxMinutes: 59,
|
|||
|
hoursStep: 1,
|
|||
|
minutesStep: 1,
|
|||
|
|
|||
|
// events
|
|||
|
onSelect: '',
|
|||
|
onShow: '',
|
|||
|
onHide: '',
|
|||
|
onChangeMonth: '',
|
|||
|
onChangeYear: '',
|
|||
|
onChangeDecade: '',
|
|||
|
onChangeView: '',
|
|||
|
onRenderCell: ''
|
|||
|
},
|
|||
|
hotKeys = {
|
|||
|
'ctrlRight': [17, 39],
|
|||
|
'ctrlUp': [17, 38],
|
|||
|
'ctrlLeft': [17, 37],
|
|||
|
'ctrlDown': [17, 40],
|
|||
|
'shiftRight': [16, 39],
|
|||
|
'shiftUp': [16, 38],
|
|||
|
'shiftLeft': [16, 37],
|
|||
|
'shiftDown': [16, 40],
|
|||
|
'altUp': [18, 38],
|
|||
|
'altRight': [18, 39],
|
|||
|
'altLeft': [18, 37],
|
|||
|
'altDown': [18, 40],
|
|||
|
'ctrlShiftUp': [16, 17, 38]
|
|||
|
},
|
|||
|
datepicker;
|
|||
|
|
|||
|
var Datepicker = function (el, options) {
|
|||
|
this.el = el;
|
|||
|
this.$el = $(el);
|
|||
|
|
|||
|
this.opts = $.extend(true, {}, defaults, options, this.$el.data());
|
|||
|
|
|||
|
if ($body == undefined) {
|
|||
|
$body = $('body');
|
|||
|
}
|
|||
|
|
|||
|
if (!this.opts.startDate) {
|
|||
|
this.opts.startDate = new Date();
|
|||
|
}
|
|||
|
|
|||
|
if (this.el.nodeName == 'INPUT') {
|
|||
|
this.elIsInput = true;
|
|||
|
}
|
|||
|
|
|||
|
if (this.opts.altField) {
|
|||
|
this.$altField = typeof this.opts.altField == 'string' ? $(this.opts.altField) : this.opts.altField;
|
|||
|
}
|
|||
|
|
|||
|
this.inited = false;
|
|||
|
this.visible = false;
|
|||
|
this.silent = false; // Need to prevent unnecessary rendering
|
|||
|
|
|||
|
this.currentDate = this.opts.startDate;
|
|||
|
this.currentView = this.opts.view;
|
|||
|
this._createShortCuts();
|
|||
|
this.selectedDates = [];
|
|||
|
this.views = {};
|
|||
|
this.keys = [];
|
|||
|
this.minRange = '';
|
|||
|
this.maxRange = '';
|
|||
|
this._prevOnSelectValue = '';
|
|||
|
|
|||
|
this.init()
|
|||
|
};
|
|||
|
|
|||
|
datepicker = Datepicker;
|
|||
|
|
|||
|
datepicker.prototype = {
|
|||
|
VERSION: VERSION,
|
|||
|
viewIndexes: ['days', 'months', 'years'],
|
|||
|
|
|||
|
init: function () {
|
|||
|
if (!containerBuilt && !this.opts.inline && this.elIsInput) {
|
|||
|
this._buildDatepickersContainer();
|
|||
|
}
|
|||
|
this._buildBaseHtml();
|
|||
|
this._defineLocale(this.opts.language);
|
|||
|
this._syncWithMinMaxDates();
|
|||
|
|
|||
|
if (this.elIsInput) {
|
|||
|
if (!this.opts.inline) {
|
|||
|
// Set extra classes for proper transitions
|
|||
|
this._setPositionClasses(this.opts.position);
|
|||
|
this._bindEvents()
|
|||
|
}
|
|||
|
if (this.opts.keyboardNav && !this.opts.onlyTimepicker) {
|
|||
|
this._bindKeyboardEvents();
|
|||
|
}
|
|||
|
this.$datepicker.on('mousedown', this._onMouseDownDatepicker.bind(this));
|
|||
|
this.$datepicker.on('mouseup', this._onMouseUpDatepicker.bind(this));
|
|||
|
}
|
|||
|
|
|||
|
if (this.opts.classes) {
|
|||
|
this.$datepicker.addClass(this.opts.classes)
|
|||
|
}
|
|||
|
|
|||
|
if (this.opts.timepicker) {
|
|||
|
this.timepicker = new $.fn.datepicker.Timepicker(this, this.opts);
|
|||
|
this._bindTimepickerEvents();
|
|||
|
}
|
|||
|
|
|||
|
if (this.opts.onlyTimepicker) {
|
|||
|
this.$datepicker.addClass('-only-timepicker-');
|
|||
|
}
|
|||
|
|
|||
|
this.views[this.currentView] = new $.fn.datepicker.Body(this, this.currentView, this.opts);
|
|||
|
this.views[this.currentView].show();
|
|||
|
this.nav = new $.fn.datepicker.Navigation(this, this.opts);
|
|||
|
this.view = this.currentView;
|
|||
|
|
|||
|
this.$el.on('clickCell.adp', this._onClickCell.bind(this));
|
|||
|
this.$datepicker.on('mouseenter', '.datepicker--cell', this._onMouseEnterCell.bind(this));
|
|||
|
this.$datepicker.on('mouseleave', '.datepicker--cell', this._onMouseLeaveCell.bind(this));
|
|||
|
|
|||
|
this.inited = true;
|
|||
|
},
|
|||
|
|
|||
|
_createShortCuts: function () {
|
|||
|
this.minDate = this.opts.minDate ? this.opts.minDate : new Date(-8639999913600000);
|
|||
|
this.maxDate = this.opts.maxDate ? this.opts.maxDate : new Date(8639999913600000);
|
|||
|
},
|
|||
|
|
|||
|
_bindEvents : function () {
|
|||
|
this.$el.on(this.opts.showEvent + '.adp', this._onShowEvent.bind(this));
|
|||
|
this.$el.on('mouseup.adp', this._onMouseUpEl.bind(this));
|
|||
|
this.$el.on('blur.adp', this._onBlur.bind(this));
|
|||
|
this.$el.on('keyup.adp', this._onKeyUpGeneral.bind(this));
|
|||
|
$(window).on('resize.adp', this._onResize.bind(this));
|
|||
|
$('body').on('mouseup.adp', this._onMouseUpBody.bind(this));
|
|||
|
},
|
|||
|
|
|||
|
_bindKeyboardEvents: function () {
|
|||
|
this.$el.on('keydown.adp', this._onKeyDown.bind(this));
|
|||
|
this.$el.on('keyup.adp', this._onKeyUp.bind(this));
|
|||
|
this.$el.on('hotKey.adp', this._onHotKey.bind(this));
|
|||
|
},
|
|||
|
|
|||
|
_bindTimepickerEvents: function () {
|
|||
|
this.$el.on('timeChange.adp', this._onTimeChange.bind(this));
|
|||
|
},
|
|||
|
|
|||
|
isWeekend: function (day) {
|
|||
|
return this.opts.weekends.indexOf(day) !== -1;
|
|||
|
},
|
|||
|
|
|||
|
_defineLocale: function (lang) {
|
|||
|
if (typeof lang == 'string') {
|
|||
|
this.loc = $.fn.datepicker.language[lang];
|
|||
|
if (!this.loc) {
|
|||
|
console.warn('Can\'t find language "' + lang + '" in Datepicker.language, will use "ru" instead');
|
|||
|
this.loc = $.extend(true, {}, $.fn.datepicker.language.ru)
|
|||
|
}
|
|||
|
|
|||
|
this.loc = $.extend(true, {}, $.fn.datepicker.language.ru, $.fn.datepicker.language[lang])
|
|||
|
} else {
|
|||
|
this.loc = $.extend(true, {}, $.fn.datepicker.language.ru, lang)
|
|||
|
}
|
|||
|
|
|||
|
if (this.opts.dateFormat) {
|
|||
|
this.loc.dateFormat = this.opts.dateFormat
|
|||
|
}
|
|||
|
|
|||
|
if (this.opts.timeFormat) {
|
|||
|
this.loc.timeFormat = this.opts.timeFormat
|
|||
|
}
|
|||
|
|
|||
|
if (this.opts.firstDay !== '') {
|
|||
|
this.loc.firstDay = this.opts.firstDay
|
|||
|
}
|
|||
|
|
|||
|
if (this.opts.timepicker) {
|
|||
|
this.loc.dateFormat = [this.loc.dateFormat, this.loc.timeFormat].join(this.opts.dateTimeSeparator);
|
|||
|
}
|
|||
|
|
|||
|
if (this.opts.onlyTimepicker) {
|
|||
|
this.loc.dateFormat = this.loc.timeFormat;
|
|||
|
}
|
|||
|
|
|||
|
var boundary = this._getWordBoundaryRegExp;
|
|||
|
if (this.loc.timeFormat.match(boundary('aa')) ||
|
|||
|
this.loc.timeFormat.match(boundary('AA'))
|
|||
|
) {
|
|||
|
this.ampm = true;
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_buildDatepickersContainer: function () {
|
|||
|
containerBuilt = true;
|
|||
|
$body.append('<div class="datepickers-container" id="datepickers-container"></div>');
|
|||
|
$datepickersContainer = $('#datepickers-container');
|
|||
|
},
|
|||
|
|
|||
|
_buildBaseHtml: function () {
|
|||
|
var $appendTarget,
|
|||
|
$inline = $('<div class="datepicker-inline">');
|
|||
|
|
|||
|
if(this.el.nodeName == 'INPUT') {
|
|||
|
if (!this.opts.inline) {
|
|||
|
$appendTarget = $datepickersContainer;
|
|||
|
} else {
|
|||
|
$appendTarget = $inline.insertAfter(this.$el)
|
|||
|
}
|
|||
|
} else {
|
|||
|
$appendTarget = $inline.appendTo(this.$el)
|
|||
|
}
|
|||
|
|
|||
|
this.$datepicker = $(baseTemplate).appendTo($appendTarget);
|
|||
|
this.$content = $('.datepicker--content', this.$datepicker);
|
|||
|
this.$nav = $('.datepicker--nav', this.$datepicker);
|
|||
|
},
|
|||
|
|
|||
|
_triggerOnChange: function () {
|
|||
|
if (!this.selectedDates.length) {
|
|||
|
// Prevent from triggering multiple onSelect callback with same argument (empty string) in IE10-11
|
|||
|
if (this._prevOnSelectValue === '') return;
|
|||
|
this._prevOnSelectValue = '';
|
|||
|
return this.opts.onSelect('', '', this);
|
|||
|
}
|
|||
|
|
|||
|
var selectedDates = this.selectedDates,
|
|||
|
parsedSelected = datepicker.getParsedDate(selectedDates[0]),
|
|||
|
formattedDates,
|
|||
|
_this = this,
|
|||
|
dates = new Date(
|
|||
|
parsedSelected.year,
|
|||
|
parsedSelected.month,
|
|||
|
parsedSelected.date,
|
|||
|
parsedSelected.hours,
|
|||
|
parsedSelected.minutes
|
|||
|
);
|
|||
|
|
|||
|
formattedDates = selectedDates.map(function (date) {
|
|||
|
return _this.formatDate(_this.loc.dateFormat, date)
|
|||
|
}).join(this.opts.multipleDatesSeparator);
|
|||
|
|
|||
|
// Create new dates array, to separate it from original selectedDates
|
|||
|
if (this.opts.multipleDates || this.opts.range) {
|
|||
|
dates = selectedDates.map(function(date) {
|
|||
|
var parsedDate = datepicker.getParsedDate(date);
|
|||
|
return new Date(
|
|||
|
parsedDate.year,
|
|||
|
parsedDate.month,
|
|||
|
parsedDate.date,
|
|||
|
parsedDate.hours,
|
|||
|
parsedDate.minutes
|
|||
|
);
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
this._prevOnSelectValue = formattedDates;
|
|||
|
this.opts.onSelect(formattedDates, dates, this);
|
|||
|
},
|
|||
|
|
|||
|
next: function () {
|
|||
|
var d = this.parsedDate,
|
|||
|
o = this.opts;
|
|||
|
switch (this.view) {
|
|||
|
case 'days':
|
|||
|
this.date = new Date(d.year, d.month + 1, 1);
|
|||
|
if (o.onChangeMonth) o.onChangeMonth(this.parsedDate.month, this.parsedDate.year);
|
|||
|
break;
|
|||
|
case 'months':
|
|||
|
this.date = new Date(d.year + 1, d.month, 1);
|
|||
|
if (o.onChangeYear) o.onChangeYear(this.parsedDate.year);
|
|||
|
break;
|
|||
|
case 'years':
|
|||
|
this.date = new Date(d.year + 10, 0, 1);
|
|||
|
if (o.onChangeDecade) o.onChangeDecade(this.curDecade);
|
|||
|
break;
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
prev: function () {
|
|||
|
var d = this.parsedDate,
|
|||
|
o = this.opts;
|
|||
|
switch (this.view) {
|
|||
|
case 'days':
|
|||
|
this.date = new Date(d.year, d.month - 1, 1);
|
|||
|
if (o.onChangeMonth) o.onChangeMonth(this.parsedDate.month, this.parsedDate.year);
|
|||
|
break;
|
|||
|
case 'months':
|
|||
|
this.date = new Date(d.year - 1, d.month, 1);
|
|||
|
if (o.onChangeYear) o.onChangeYear(this.parsedDate.year);
|
|||
|
break;
|
|||
|
case 'years':
|
|||
|
this.date = new Date(d.year - 10, 0, 1);
|
|||
|
if (o.onChangeDecade) o.onChangeDecade(this.curDecade);
|
|||
|
break;
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
formatDate: function (string, date) {
|
|||
|
date = date || this.date;
|
|||
|
var result = string,
|
|||
|
boundary = this._getWordBoundaryRegExp,
|
|||
|
locale = this.loc,
|
|||
|
leadingZero = datepicker.getLeadingZeroNum,
|
|||
|
decade = datepicker.getDecade(date),
|
|||
|
d = datepicker.getParsedDate(date),
|
|||
|
fullHours = d.fullHours,
|
|||
|
hours = d.hours,
|
|||
|
ampm = string.match(boundary('aa')) || string.match(boundary('AA')),
|
|||
|
dayPeriod = 'am',
|
|||
|
replacer = this._replacer,
|
|||
|
validHours;
|
|||
|
|
|||
|
if (this.opts.timepicker && this.timepicker && ampm) {
|
|||
|
validHours = this.timepicker._getValidHoursFromDate(date, ampm);
|
|||
|
fullHours = leadingZero(validHours.hours);
|
|||
|
hours = validHours.hours;
|
|||
|
dayPeriod = validHours.dayPeriod;
|
|||
|
}
|
|||
|
|
|||
|
switch (true) {
|
|||
|
case /@/.test(result):
|
|||
|
result = result.replace(/@/, date.getTime());
|
|||
|
case /aa/.test(result):
|
|||
|
result = replacer(result, boundary('aa'), dayPeriod);
|
|||
|
case /AA/.test(result):
|
|||
|
result = replacer(result, boundary('AA'), dayPeriod.toUpperCase());
|
|||
|
case /dd/.test(result):
|
|||
|
result = replacer(result, boundary('dd'), d.fullDate);
|
|||
|
case /d/.test(result):
|
|||
|
result = replacer(result, boundary('d'), d.date);
|
|||
|
case /DD/.test(result):
|
|||
|
result = replacer(result, boundary('DD'), locale.days[d.day]);
|
|||
|
case /D/.test(result):
|
|||
|
result = replacer(result, boundary('D'), locale.daysShort[d.day]);
|
|||
|
case /mm/.test(result):
|
|||
|
result = replacer(result, boundary('mm'), d.fullMonth);
|
|||
|
case /m/.test(result):
|
|||
|
result = replacer(result, boundary('m'), d.month + 1);
|
|||
|
case /MM/.test(result):
|
|||
|
result = replacer(result, boundary('MM'), this.loc.months[d.month]);
|
|||
|
case /M/.test(result):
|
|||
|
result = replacer(result, boundary('M'), locale.monthsShort[d.month]);
|
|||
|
case /ii/.test(result):
|
|||
|
result = replacer(result, boundary('ii'), d.fullMinutes);
|
|||
|
case /i/.test(result):
|
|||
|
result = replacer(result, boundary('i'), d.minutes);
|
|||
|
case /hh/.test(result):
|
|||
|
result = replacer(result, boundary('hh'), fullHours);
|
|||
|
case /h/.test(result):
|
|||
|
result = replacer(result, boundary('h'), hours);
|
|||
|
case /yyyy/.test(result):
|
|||
|
result = replacer(result, boundary('yyyy'), d.year);
|
|||
|
case /yyyy1/.test(result):
|
|||
|
result = replacer(result, boundary('yyyy1'), decade[0]);
|
|||
|
case /yyyy2/.test(result):
|
|||
|
result = replacer(result, boundary('yyyy2'), decade[1]);
|
|||
|
case /yy/.test(result):
|
|||
|
result = replacer(result, boundary('yy'), d.year.toString().slice(-2));
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
},
|
|||
|
|
|||
|
_replacer: function (str, reg, data) {
|
|||
|
return str.replace(reg, function (match, p1,p2,p3) {
|
|||
|
return p1 + data + p3;
|
|||
|
})
|
|||
|
},
|
|||
|
|
|||
|
_getWordBoundaryRegExp: function (sign) {
|
|||
|
var symbols = '\\s|\\.|-|/|\\\\|,|\\$|\\!|\\?|:|;';
|
|||
|
|
|||
|
return new RegExp('(^|>|' + symbols + ')(' + sign + ')($|<|' + symbols + ')', 'g');
|
|||
|
},
|
|||
|
|
|||
|
|
|||
|
selectDate: function (date) {
|
|||
|
var _this = this,
|
|||
|
opts = _this.opts,
|
|||
|
d = _this.parsedDate,
|
|||
|
selectedDates = _this.selectedDates,
|
|||
|
len = selectedDates.length,
|
|||
|
newDate = '';
|
|||
|
|
|||
|
if (Array.isArray(date)) {
|
|||
|
date.forEach(function (d) {
|
|||
|
_this.selectDate(d)
|
|||
|
});
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (!(date instanceof Date)) return;
|
|||
|
|
|||
|
this.lastSelectedDate = date;
|
|||
|
|
|||
|
// Set new time values from Date
|
|||
|
if (this.timepicker) {
|
|||
|
this.timepicker._setTime(date);
|
|||
|
}
|
|||
|
|
|||
|
// On this step timepicker will set valid values in it's instance
|
|||
|
_this._trigger('selectDate', date);
|
|||
|
|
|||
|
// Set correct time values after timepicker's validation
|
|||
|
// Prevent from setting hours or minutes which values are lesser then `min` value or
|
|||
|
// greater then `max` value
|
|||
|
if (this.timepicker) {
|
|||
|
date.setHours(this.timepicker.hours);
|
|||
|
date.setMinutes(this.timepicker.minutes)
|
|||
|
}
|
|||
|
|
|||
|
if (_this.view == 'days') {
|
|||
|
if (date.getMonth() != d.month && opts.moveToOtherMonthsOnSelect) {
|
|||
|
newDate = new Date(date.getFullYear(), date.getMonth(), 1);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (_this.view == 'years') {
|
|||
|
if (date.getFullYear() != d.year && opts.moveToOtherYearsOnSelect) {
|
|||
|
newDate = new Date(date.getFullYear(), 0, 1);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (newDate) {
|
|||
|
_this.silent = true;
|
|||
|
_this.date = newDate;
|
|||
|
_this.silent = false;
|
|||
|
_this.nav._render()
|
|||
|
}
|
|||
|
|
|||
|
if (opts.multipleDates && !opts.range) { // Set priority to range functionality
|
|||
|
if (len === opts.multipleDates) return;
|
|||
|
if (!_this._isSelected(date)) {
|
|||
|
_this.selectedDates.push(date);
|
|||
|
}
|
|||
|
} else if (opts.range) {
|
|||
|
if (len == 2) {
|
|||
|
_this.selectedDates = [date];
|
|||
|
_this.minRange = date;
|
|||
|
_this.maxRange = '';
|
|||
|
} else if (len == 1) {
|
|||
|
_this.selectedDates.push(date);
|
|||
|
if (!_this.maxRange){
|
|||
|
_this.maxRange = date;
|
|||
|
} else {
|
|||
|
_this.minRange = date;
|
|||
|
}
|
|||
|
// Swap dates if they were selected via dp.selectDate() and second date was smaller then first
|
|||
|
if (datepicker.bigger(_this.maxRange, _this.minRange)) {
|
|||
|
_this.maxRange = _this.minRange;
|
|||
|
_this.minRange = date;
|
|||
|
}
|
|||
|
_this.selectedDates = [_this.minRange, _this.maxRange]
|
|||
|
|
|||
|
} else {
|
|||
|
_this.selectedDates = [date];
|
|||
|
_this.minRange = date;
|
|||
|
}
|
|||
|
} else {
|
|||
|
_this.selectedDates = [date];
|
|||
|
}
|
|||
|
|
|||
|
_this._setInputValue();
|
|||
|
|
|||
|
if (opts.onSelect) {
|
|||
|
_this._triggerOnChange();
|
|||
|
}
|
|||
|
|
|||
|
if (opts.autoClose && !this.timepickerIsActive) {
|
|||
|
if (!opts.multipleDates && !opts.range) {
|
|||
|
_this.hide();
|
|||
|
} else if (opts.range && _this.selectedDates.length == 2) {
|
|||
|
_this.hide();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_this.views[this.currentView]._render()
|
|||
|
},
|
|||
|
|
|||
|
removeDate: function (date) {
|
|||
|
var selected = this.selectedDates,
|
|||
|
_this = this;
|
|||
|
|
|||
|
if (!(date instanceof Date)) return;
|
|||
|
|
|||
|
return selected.some(function (curDate, i) {
|
|||
|
if (datepicker.isSame(curDate, date)) {
|
|||
|
selected.splice(i, 1);
|
|||
|
|
|||
|
if (!_this.selectedDates.length) {
|
|||
|
_this.minRange = '';
|
|||
|
_this.maxRange = '';
|
|||
|
_this.lastSelectedDate = '';
|
|||
|
} else {
|
|||
|
_this.lastSelectedDate = _this.selectedDates[_this.selectedDates.length - 1];
|
|||
|
}
|
|||
|
|
|||
|
_this.views[_this.currentView]._render();
|
|||
|
_this._setInputValue();
|
|||
|
|
|||
|
if (_this.opts.onSelect) {
|
|||
|
_this._triggerOnChange();
|
|||
|
}
|
|||
|
|
|||
|
return true
|
|||
|
}
|
|||
|
})
|
|||
|
},
|
|||
|
|
|||
|
today: function () {
|
|||
|
this.silent = true;
|
|||
|
this.view = this.opts.minView;
|
|||
|
this.silent = false;
|
|||
|
this.date = new Date();
|
|||
|
|
|||
|
if (this.opts.todayButton instanceof Date) {
|
|||
|
this.selectDate(this.opts.todayButton)
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
clear: function () {
|
|||
|
this.selectedDates = [];
|
|||
|
this.minRange = '';
|
|||
|
this.maxRange = '';
|
|||
|
this.views[this.currentView]._render();
|
|||
|
this._setInputValue();
|
|||
|
if (this.opts.onSelect) {
|
|||
|
this._triggerOnChange()
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Updates datepicker options
|
|||
|
* @param {String|Object} param - parameter's name to update. If object then it will extend current options
|
|||
|
* @param {String|Number|Object} [value] - new param value
|
|||
|
*/
|
|||
|
update: function (param, value) {
|
|||
|
var len = arguments.length,
|
|||
|
lastSelectedDate = this.lastSelectedDate;
|
|||
|
|
|||
|
if (len == 2) {
|
|||
|
this.opts[param] = value;
|
|||
|
} else if (len == 1 && typeof param == 'object') {
|
|||
|
this.opts = $.extend(true, this.opts, param)
|
|||
|
}
|
|||
|
|
|||
|
this._createShortCuts();
|
|||
|
this._syncWithMinMaxDates();
|
|||
|
this._defineLocale(this.opts.language);
|
|||
|
this.nav._addButtonsIfNeed();
|
|||
|
if (!this.opts.onlyTimepicker) this.nav._render();
|
|||
|
this.views[this.currentView]._render();
|
|||
|
|
|||
|
if (this.elIsInput && !this.opts.inline) {
|
|||
|
this._setPositionClasses(this.opts.position);
|
|||
|
if (this.visible) {
|
|||
|
this.setPosition(this.opts.position)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (this.opts.classes) {
|
|||
|
this.$datepicker.addClass(this.opts.classes)
|
|||
|
}
|
|||
|
|
|||
|
if (this.opts.onlyTimepicker) {
|
|||
|
this.$datepicker.addClass('-only-timepicker-');
|
|||
|
}
|
|||
|
|
|||
|
if (this.opts.timepicker) {
|
|||
|
if (lastSelectedDate) this.timepicker._handleDate(lastSelectedDate);
|
|||
|
this.timepicker._updateRanges();
|
|||
|
this.timepicker._updateCurrentTime();
|
|||
|
// Change hours and minutes if it's values have been changed through min/max hours/minutes
|
|||
|
if (lastSelectedDate) {
|
|||
|
lastSelectedDate.setHours(this.timepicker.hours);
|
|||
|
lastSelectedDate.setMinutes(this.timepicker.minutes);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
this._setInputValue();
|
|||
|
|
|||
|
return this;
|
|||
|
},
|
|||
|
|
|||
|
_syncWithMinMaxDates: function () {
|
|||
|
var curTime = this.date.getTime();
|
|||
|
this.silent = true;
|
|||
|
if (this.minTime > curTime) {
|
|||
|
this.date = this.minDate;
|
|||
|
}
|
|||
|
|
|||
|
if (this.maxTime < curTime) {
|
|||
|
this.date = this.maxDate;
|
|||
|
}
|
|||
|
this.silent = false;
|
|||
|
},
|
|||
|
|
|||
|
_isSelected: function (checkDate, cellType) {
|
|||
|
var res = false;
|
|||
|
this.selectedDates.some(function (date) {
|
|||
|
if (datepicker.isSame(date, checkDate, cellType)) {
|
|||
|
res = date;
|
|||
|
return true;
|
|||
|
}
|
|||
|
});
|
|||
|
return res;
|
|||
|
},
|
|||
|
|
|||
|
_setInputValue: function () {
|
|||
|
var _this = this,
|
|||
|
opts = _this.opts,
|
|||
|
format = _this.loc.dateFormat,
|
|||
|
altFormat = opts.altFieldDateFormat,
|
|||
|
value = _this.selectedDates.map(function (date) {
|
|||
|
return _this.formatDate(format, date)
|
|||
|
}),
|
|||
|
altValues;
|
|||
|
|
|||
|
if (opts.altField && _this.$altField.length) {
|
|||
|
altValues = this.selectedDates.map(function (date) {
|
|||
|
return _this.formatDate(altFormat, date)
|
|||
|
});
|
|||
|
altValues = altValues.join(this.opts.multipleDatesSeparator);
|
|||
|
this.$altField.val(altValues);
|
|||
|
}
|
|||
|
|
|||
|
value = value.join(this.opts.multipleDatesSeparator);
|
|||
|
|
|||
|
this.$el.val(value)
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Check if date is between minDate and maxDate
|
|||
|
* @param date {object} - date object
|
|||
|
* @param type {string} - cell type
|
|||
|
* @returns {boolean}
|
|||
|
* @private
|
|||
|
*/
|
|||
|
_isInRange: function (date, type) {
|
|||
|
var time = date.getTime(),
|
|||
|
d = datepicker.getParsedDate(date),
|
|||
|
min = datepicker.getParsedDate(this.minDate),
|
|||
|
max = datepicker.getParsedDate(this.maxDate),
|
|||
|
dMinTime = new Date(d.year, d.month, min.date).getTime(),
|
|||
|
dMaxTime = new Date(d.year, d.month, max.date).getTime(),
|
|||
|
types = {
|
|||
|
day: time >= this.minTime && time <= this.maxTime,
|
|||
|
month: dMinTime >= this.minTime && dMaxTime <= this.maxTime,
|
|||
|
year: d.year >= min.year && d.year <= max.year
|
|||
|
};
|
|||
|
return type ? types[type] : types.day
|
|||
|
},
|
|||
|
|
|||
|
_getDimensions: function ($el) {
|
|||
|
var offset = $el.offset();
|
|||
|
|
|||
|
return {
|
|||
|
width: $el.outerWidth(),
|
|||
|
height: $el.outerHeight(),
|
|||
|
left: offset.left,
|
|||
|
top: offset.top
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_getDateFromCell: function (cell) {
|
|||
|
var curDate = this.parsedDate,
|
|||
|
year = cell.data('year') || curDate.year,
|
|||
|
month = cell.data('month') == undefined ? curDate.month : cell.data('month'),
|
|||
|
date = cell.data('date') || 1;
|
|||
|
|
|||
|
return new Date(year, month, date);
|
|||
|
},
|
|||
|
|
|||
|
_setPositionClasses: function (pos) {
|
|||
|
pos = pos.split(' ');
|
|||
|
var main = pos[0],
|
|||
|
sec = pos[1],
|
|||
|
classes = 'datepicker -' + main + '-' + sec + '- -from-' + main + '-';
|
|||
|
|
|||
|
if (this.visible) classes += ' active';
|
|||
|
|
|||
|
this.$datepicker
|
|||
|
.removeAttr('class')
|
|||
|
.addClass(classes);
|
|||
|
},
|
|||
|
|
|||
|
setPosition: function (position) {
|
|||
|
position = position || this.opts.position;
|
|||
|
|
|||
|
var dims = this._getDimensions(this.$el),
|
|||
|
selfDims = this._getDimensions(this.$datepicker),
|
|||
|
pos = position.split(' '),
|
|||
|
top, left,
|
|||
|
offset = this.opts.offset,
|
|||
|
main = pos[0],
|
|||
|
secondary = pos[1];
|
|||
|
|
|||
|
switch (main) {
|
|||
|
case 'top':
|
|||
|
top = dims.top - selfDims.height - offset;
|
|||
|
break;
|
|||
|
case 'right':
|
|||
|
left = dims.left + dims.width + offset;
|
|||
|
break;
|
|||
|
case 'bottom':
|
|||
|
top = dims.top + dims.height + offset;
|
|||
|
break;
|
|||
|
case 'left':
|
|||
|
left = dims.left - selfDims.width - offset;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
switch(secondary) {
|
|||
|
case 'top':
|
|||
|
top = dims.top;
|
|||
|
break;
|
|||
|
case 'right':
|
|||
|
left = dims.left + dims.width - selfDims.width;
|
|||
|
break;
|
|||
|
case 'bottom':
|
|||
|
top = dims.top + dims.height - selfDims.height;
|
|||
|
break;
|
|||
|
case 'left':
|
|||
|
left = dims.left;
|
|||
|
break;
|
|||
|
case 'center':
|
|||
|
if (/left|right/.test(main)) {
|
|||
|
top = dims.top + dims.height/2 - selfDims.height/2;
|
|||
|
} else {
|
|||
|
left = dims.left + dims.width/2 - selfDims.width/2;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
this.$datepicker
|
|||
|
.css({
|
|||
|
left: left,
|
|||
|
top: top
|
|||
|
})
|
|||
|
},
|
|||
|
|
|||
|
show: function () {
|
|||
|
var onShow = this.opts.onShow;
|
|||
|
|
|||
|
this.setPosition(this.opts.position);
|
|||
|
this.$datepicker.addClass('active');
|
|||
|
this.visible = true;
|
|||
|
|
|||
|
if (onShow) {
|
|||
|
this._bindVisionEvents(onShow)
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
hide: function () {
|
|||
|
var onHide = this.opts.onHide;
|
|||
|
|
|||
|
this.$datepicker
|
|||
|
.removeClass('active')
|
|||
|
.css({
|
|||
|
left: '-100000px'
|
|||
|
});
|
|||
|
|
|||
|
this.focused = '';
|
|||
|
this.keys = [];
|
|||
|
|
|||
|
this.inFocus = false;
|
|||
|
this.visible = false;
|
|||
|
this.$el.blur();
|
|||
|
|
|||
|
if (onHide) {
|
|||
|
this._bindVisionEvents(onHide)
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
down: function (date) {
|
|||
|
this._changeView(date, 'down');
|
|||
|
},
|
|||
|
|
|||
|
up: function (date) {
|
|||
|
this._changeView(date, 'up');
|
|||
|
},
|
|||
|
|
|||
|
_bindVisionEvents: function (event) {
|
|||
|
this.$datepicker.off('transitionend.dp');
|
|||
|
event(this, false);
|
|||
|
this.$datepicker.one('transitionend.dp', event.bind(this, this, true))
|
|||
|
},
|
|||
|
|
|||
|
_changeView: function (date, dir) {
|
|||
|
date = date || this.focused || this.date;
|
|||
|
|
|||
|
var nextView = dir == 'up' ? this.viewIndex + 1 : this.viewIndex - 1;
|
|||
|
if (nextView > 2) nextView = 2;
|
|||
|
if (nextView < 0) nextView = 0;
|
|||
|
|
|||
|
this.silent = true;
|
|||
|
this.date = new Date(date.getFullYear(), date.getMonth(), 1);
|
|||
|
this.silent = false;
|
|||
|
this.view = this.viewIndexes[nextView];
|
|||
|
|
|||
|
},
|
|||
|
|
|||
|
_handleHotKey: function (key) {
|
|||
|
var date = datepicker.getParsedDate(this._getFocusedDate()),
|
|||
|
focusedParsed,
|
|||
|
o = this.opts,
|
|||
|
newDate,
|
|||
|
totalDaysInNextMonth,
|
|||
|
monthChanged = false,
|
|||
|
yearChanged = false,
|
|||
|
decadeChanged = false,
|
|||
|
y = date.year,
|
|||
|
m = date.month,
|
|||
|
d = date.date;
|
|||
|
|
|||
|
switch (key) {
|
|||
|
case 'ctrlRight':
|
|||
|
case 'ctrlUp':
|
|||
|
m += 1;
|
|||
|
monthChanged = true;
|
|||
|
break;
|
|||
|
case 'ctrlLeft':
|
|||
|
case 'ctrlDown':
|
|||
|
m -= 1;
|
|||
|
monthChanged = true;
|
|||
|
break;
|
|||
|
case 'shiftRight':
|
|||
|
case 'shiftUp':
|
|||
|
yearChanged = true;
|
|||
|
y += 1;
|
|||
|
break;
|
|||
|
case 'shiftLeft':
|
|||
|
case 'shiftDown':
|
|||
|
yearChanged = true;
|
|||
|
y -= 1;
|
|||
|
break;
|
|||
|
case 'altRight':
|
|||
|
case 'altUp':
|
|||
|
decadeChanged = true;
|
|||
|
y += 10;
|
|||
|
break;
|
|||
|
case 'altLeft':
|
|||
|
case 'altDown':
|
|||
|
decadeChanged = true;
|
|||
|
y -= 10;
|
|||
|
break;
|
|||
|
case 'ctrlShiftUp':
|
|||
|
this.up();
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
totalDaysInNextMonth = datepicker.getDaysCount(new Date(y,m));
|
|||
|
newDate = new Date(y,m,d);
|
|||
|
|
|||
|
// If next month has less days than current, set date to total days in that month
|
|||
|
if (totalDaysInNextMonth < d) d = totalDaysInNextMonth;
|
|||
|
|
|||
|
// Check if newDate is in valid range
|
|||
|
if (newDate.getTime() < this.minTime) {
|
|||
|
newDate = this.minDate;
|
|||
|
} else if (newDate.getTime() > this.maxTime) {
|
|||
|
newDate = this.maxDate;
|
|||
|
}
|
|||
|
|
|||
|
this.focused = newDate;
|
|||
|
|
|||
|
focusedParsed = datepicker.getParsedDate(newDate);
|
|||
|
if (monthChanged && o.onChangeMonth) {
|
|||
|
o.onChangeMonth(focusedParsed.month, focusedParsed.year)
|
|||
|
}
|
|||
|
if (yearChanged && o.onChangeYear) {
|
|||
|
o.onChangeYear(focusedParsed.year)
|
|||
|
}
|
|||
|
if (decadeChanged && o.onChangeDecade) {
|
|||
|
o.onChangeDecade(this.curDecade)
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_registerKey: function (key) {
|
|||
|
var exists = this.keys.some(function (curKey) {
|
|||
|
return curKey == key;
|
|||
|
});
|
|||
|
|
|||
|
if (!exists) {
|
|||
|
this.keys.push(key)
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_unRegisterKey: function (key) {
|
|||
|
var index = this.keys.indexOf(key);
|
|||
|
|
|||
|
this.keys.splice(index, 1);
|
|||
|
},
|
|||
|
|
|||
|
_isHotKeyPressed: function () {
|
|||
|
var currentHotKey,
|
|||
|
found = false,
|
|||
|
_this = this,
|
|||
|
pressedKeys = this.keys.sort();
|
|||
|
|
|||
|
for (var hotKey in hotKeys) {
|
|||
|
currentHotKey = hotKeys[hotKey];
|
|||
|
if (pressedKeys.length != currentHotKey.length) continue;
|
|||
|
|
|||
|
if (currentHotKey.every(function (key, i) { return key == pressedKeys[i]})) {
|
|||
|
_this._trigger('hotKey', hotKey);
|
|||
|
found = true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return found;
|
|||
|
},
|
|||
|
|
|||
|
_trigger: function (event, args) {
|
|||
|
this.$el.trigger(event, args)
|
|||
|
},
|
|||
|
|
|||
|
_focusNextCell: function (keyCode, type) {
|
|||
|
type = type || this.cellType;
|
|||
|
|
|||
|
var date = datepicker.getParsedDate(this._getFocusedDate()),
|
|||
|
y = date.year,
|
|||
|
m = date.month,
|
|||
|
d = date.date;
|
|||
|
|
|||
|
if (this._isHotKeyPressed()){
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
switch(keyCode) {
|
|||
|
case 37: // left
|
|||
|
type == 'day' ? (d -= 1) : '';
|
|||
|
type == 'month' ? (m -= 1) : '';
|
|||
|
type == 'year' ? (y -= 1) : '';
|
|||
|
break;
|
|||
|
case 38: // up
|
|||
|
type == 'day' ? (d -= 7) : '';
|
|||
|
type == 'month' ? (m -= 3) : '';
|
|||
|
type == 'year' ? (y -= 4) : '';
|
|||
|
break;
|
|||
|
case 39: // right
|
|||
|
type == 'day' ? (d += 1) : '';
|
|||
|
type == 'month' ? (m += 1) : '';
|
|||
|
type == 'year' ? (y += 1) : '';
|
|||
|
break;
|
|||
|
case 40: // down
|
|||
|
type == 'day' ? (d += 7) : '';
|
|||
|
type == 'month' ? (m += 3) : '';
|
|||
|
type == 'year' ? (y += 4) : '';
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
var nd = new Date(y,m,d);
|
|||
|
if (nd.getTime() < this.minTime) {
|
|||
|
nd = this.minDate;
|
|||
|
} else if (nd.getTime() > this.maxTime) {
|
|||
|
nd = this.maxDate;
|
|||
|
}
|
|||
|
|
|||
|
this.focused = nd;
|
|||
|
|
|||
|
},
|
|||
|
|
|||
|
_getFocusedDate: function () {
|
|||
|
var focused = this.focused || this.selectedDates[this.selectedDates.length - 1],
|
|||
|
d = this.parsedDate;
|
|||
|
|
|||
|
if (!focused) {
|
|||
|
switch (this.view) {
|
|||
|
case 'days':
|
|||
|
focused = new Date(d.year, d.month, new Date().getDate());
|
|||
|
break;
|
|||
|
case 'months':
|
|||
|
focused = new Date(d.year, d.month, 1);
|
|||
|
break;
|
|||
|
case 'years':
|
|||
|
focused = new Date(d.year, 0, 1);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return focused;
|
|||
|
},
|
|||
|
|
|||
|
_getCell: function (date, type) {
|
|||
|
type = type || this.cellType;
|
|||
|
|
|||
|
var d = datepicker.getParsedDate(date),
|
|||
|
selector = '.datepicker--cell[data-year="' + d.year + '"]',
|
|||
|
$cell;
|
|||
|
|
|||
|
switch (type) {
|
|||
|
case 'month':
|
|||
|
selector = '[data-month="' + d.month + '"]';
|
|||
|
break;
|
|||
|
case 'day':
|
|||
|
selector += '[data-month="' + d.month + '"][data-date="' + d.date + '"]';
|
|||
|
break;
|
|||
|
}
|
|||
|
$cell = this.views[this.currentView].$el.find(selector);
|
|||
|
|
|||
|
return $cell.length ? $cell : $('');
|
|||
|
},
|
|||
|
|
|||
|
destroy: function () {
|
|||
|
var _this = this;
|
|||
|
_this.$el
|
|||
|
.off('.adp')
|
|||
|
.data('datepicker', '');
|
|||
|
|
|||
|
_this.selectedDates = [];
|
|||
|
_this.focused = '';
|
|||
|
_this.views = {};
|
|||
|
_this.keys = [];
|
|||
|
_this.minRange = '';
|
|||
|
_this.maxRange = '';
|
|||
|
|
|||
|
if (_this.opts.inline || !_this.elIsInput) {
|
|||
|
_this.$datepicker.closest('.datepicker-inline').remove();
|
|||
|
} else {
|
|||
|
_this.$datepicker.remove();
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_handleAlreadySelectedDates: function (alreadySelected, selectedDate) {
|
|||
|
if (this.opts.range) {
|
|||
|
if (!this.opts.toggleSelected) {
|
|||
|
// Add possibility to select same date when range is true
|
|||
|
if (this.selectedDates.length != 2) {
|
|||
|
this._trigger('clickCell', selectedDate);
|
|||
|
}
|
|||
|
} else {
|
|||
|
this.removeDate(selectedDate);
|
|||
|
}
|
|||
|
} else if (this.opts.toggleSelected){
|
|||
|
this.removeDate(selectedDate);
|
|||
|
}
|
|||
|
|
|||
|
// Change last selected date to be able to change time when clicking on this cell
|
|||
|
if (!this.opts.toggleSelected) {
|
|||
|
this.lastSelectedDate = alreadySelected;
|
|||
|
if (this.opts.timepicker) {
|
|||
|
this.timepicker._setTime(alreadySelected);
|
|||
|
this.timepicker.update();
|
|||
|
}
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_onShowEvent: function (e) {
|
|||
|
if (!this.visible) {
|
|||
|
this.show();
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_onBlur: function () {
|
|||
|
if (!this.inFocus && this.visible) {
|
|||
|
this.hide();
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_onMouseDownDatepicker: function (e) {
|
|||
|
this.inFocus = true;
|
|||
|
},
|
|||
|
|
|||
|
_onMouseUpDatepicker: function (e) {
|
|||
|
this.inFocus = false;
|
|||
|
e.originalEvent.inFocus = true;
|
|||
|
if (!e.originalEvent.timepickerFocus) this.$el.focus();
|
|||
|
},
|
|||
|
|
|||
|
_onKeyUpGeneral: function (e) {
|
|||
|
var val = this.$el.val();
|
|||
|
|
|||
|
if (!val) {
|
|||
|
this.clear();
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_onResize: function () {
|
|||
|
if (this.visible) {
|
|||
|
this.setPosition();
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_onMouseUpBody: function (e) {
|
|||
|
if (e.originalEvent.inFocus) return;
|
|||
|
|
|||
|
if (this.visible && !this.inFocus) {
|
|||
|
this.hide();
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_onMouseUpEl: function (e) {
|
|||
|
e.originalEvent.inFocus = true;
|
|||
|
setTimeout(this._onKeyUpGeneral.bind(this),4);
|
|||
|
},
|
|||
|
|
|||
|
_onKeyDown: function (e) {
|
|||
|
var code = e.which;
|
|||
|
this._registerKey(code);
|
|||
|
|
|||
|
// Arrows
|
|||
|
if (code >= 37 && code <= 40) {
|
|||
|
e.preventDefault();
|
|||
|
this._focusNextCell(code);
|
|||
|
}
|
|||
|
|
|||
|
// Enter
|
|||
|
if (code == 13) {
|
|||
|
if (this.focused) {
|
|||
|
if (this._getCell(this.focused).hasClass('-disabled-')) return;
|
|||
|
if (this.view != this.opts.minView) {
|
|||
|
this.down()
|
|||
|
} else {
|
|||
|
var alreadySelected = this._isSelected(this.focused, this.cellType);
|
|||
|
|
|||
|
if (!alreadySelected) {
|
|||
|
if (this.timepicker) {
|
|||
|
this.focused.setHours(this.timepicker.hours);
|
|||
|
this.focused.setMinutes(this.timepicker.minutes);
|
|||
|
}
|
|||
|
this.selectDate(this.focused);
|
|||
|
return;
|
|||
|
}
|
|||
|
this._handleAlreadySelectedDates(alreadySelected, this.focused)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Esc
|
|||
|
if (code == 27) {
|
|||
|
this.hide();
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_onKeyUp: function (e) {
|
|||
|
var code = e.which;
|
|||
|
this._unRegisterKey(code);
|
|||
|
},
|
|||
|
|
|||
|
_onHotKey: function (e, hotKey) {
|
|||
|
this._handleHotKey(hotKey);
|
|||
|
},
|
|||
|
|
|||
|
_onMouseEnterCell: function (e) {
|
|||
|
var $cell = $(e.target).closest('.datepicker--cell'),
|
|||
|
date = this._getDateFromCell($cell);
|
|||
|
|
|||
|
// Prevent from unnecessary rendering and setting new currentDate
|
|||
|
this.silent = true;
|
|||
|
|
|||
|
if (this.focused) {
|
|||
|
this.focused = ''
|
|||
|
}
|
|||
|
|
|||
|
$cell.addClass('-focus-');
|
|||
|
|
|||
|
this.focused = date;
|
|||
|
this.silent = false;
|
|||
|
|
|||
|
if (this.opts.range && this.selectedDates.length == 1) {
|
|||
|
this.minRange = this.selectedDates[0];
|
|||
|
this.maxRange = '';
|
|||
|
if (datepicker.less(this.minRange, this.focused)) {
|
|||
|
this.maxRange = this.minRange;
|
|||
|
this.minRange = '';
|
|||
|
}
|
|||
|
this.views[this.currentView]._update();
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_onMouseLeaveCell: function (e) {
|
|||
|
var $cell = $(e.target).closest('.datepicker--cell');
|
|||
|
|
|||
|
$cell.removeClass('-focus-');
|
|||
|
|
|||
|
this.silent = true;
|
|||
|
this.focused = '';
|
|||
|
this.silent = false;
|
|||
|
},
|
|||
|
|
|||
|
_onTimeChange: function (e, h, m) {
|
|||
|
var date = new Date(),
|
|||
|
selectedDates = this.selectedDates,
|
|||
|
selected = false;
|
|||
|
|
|||
|
if (selectedDates.length) {
|
|||
|
selected = true;
|
|||
|
date = this.lastSelectedDate;
|
|||
|
}
|
|||
|
|
|||
|
date.setHours(h);
|
|||
|
date.setMinutes(m);
|
|||
|
|
|||
|
if (!selected && !this._getCell(date).hasClass('-disabled-')) {
|
|||
|
this.selectDate(date);
|
|||
|
} else {
|
|||
|
this._setInputValue();
|
|||
|
if (this.opts.onSelect) {
|
|||
|
this._triggerOnChange();
|
|||
|
}
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_onClickCell: function (e, date) {
|
|||
|
if (this.timepicker) {
|
|||
|
date.setHours(this.timepicker.hours);
|
|||
|
date.setMinutes(this.timepicker.minutes);
|
|||
|
}
|
|||
|
this.selectDate(date);
|
|||
|
},
|
|||
|
|
|||
|
set focused(val) {
|
|||
|
if (!val && this.focused) {
|
|||
|
var $cell = this._getCell(this.focused);
|
|||
|
|
|||
|
if ($cell.length) {
|
|||
|
$cell.removeClass('-focus-')
|
|||
|
}
|
|||
|
}
|
|||
|
this._focused = val;
|
|||
|
if (this.opts.range && this.selectedDates.length == 1) {
|
|||
|
this.minRange = this.selectedDates[0];
|
|||
|
this.maxRange = '';
|
|||
|
if (datepicker.less(this.minRange, this._focused)) {
|
|||
|
this.maxRange = this.minRange;
|
|||
|
this.minRange = '';
|
|||
|
}
|
|||
|
}
|
|||
|
if (this.silent) return;
|
|||
|
this.date = val;
|
|||
|
},
|
|||
|
|
|||
|
get focused() {
|
|||
|
return this._focused;
|
|||
|
},
|
|||
|
|
|||
|
get parsedDate() {
|
|||
|
return datepicker.getParsedDate(this.date);
|
|||
|
},
|
|||
|
|
|||
|
set date (val) {
|
|||
|
if (!(val instanceof Date)) return;
|
|||
|
|
|||
|
this.currentDate = val;
|
|||
|
|
|||
|
if (this.inited && !this.silent) {
|
|||
|
this.views[this.view]._render();
|
|||
|
this.nav._render();
|
|||
|
if (this.visible && this.elIsInput) {
|
|||
|
this.setPosition();
|
|||
|
}
|
|||
|
}
|
|||
|
return val;
|
|||
|
},
|
|||
|
|
|||
|
get date () {
|
|||
|
return this.currentDate
|
|||
|
},
|
|||
|
|
|||
|
set view (val) {
|
|||
|
this.viewIndex = this.viewIndexes.indexOf(val);
|
|||
|
|
|||
|
if (this.viewIndex < 0) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
this.prevView = this.currentView;
|
|||
|
this.currentView = val;
|
|||
|
|
|||
|
if (this.inited) {
|
|||
|
if (!this.views[val]) {
|
|||
|
this.views[val] = new $.fn.datepicker.Body(this, val, this.opts)
|
|||
|
} else {
|
|||
|
this.views[val]._render();
|
|||
|
}
|
|||
|
|
|||
|
this.views[this.prevView].hide();
|
|||
|
this.views[val].show();
|
|||
|
this.nav._render();
|
|||
|
|
|||
|
if (this.opts.onChangeView) {
|
|||
|
this.opts.onChangeView(val)
|
|||
|
}
|
|||
|
if (this.elIsInput && this.visible) this.setPosition();
|
|||
|
}
|
|||
|
|
|||
|
return val
|
|||
|
},
|
|||
|
|
|||
|
get view() {
|
|||
|
return this.currentView;
|
|||
|
},
|
|||
|
|
|||
|
get cellType() {
|
|||
|
return this.view.substring(0, this.view.length - 1)
|
|||
|
},
|
|||
|
|
|||
|
get minTime() {
|
|||
|
var min = datepicker.getParsedDate(this.minDate);
|
|||
|
return new Date(min.year, min.month, min.date).getTime()
|
|||
|
},
|
|||
|
|
|||
|
get maxTime() {
|
|||
|
var max = datepicker.getParsedDate(this.maxDate);
|
|||
|
return new Date(max.year, max.month, max.date).getTime()
|
|||
|
},
|
|||
|
|
|||
|
get curDecade() {
|
|||
|
return datepicker.getDecade(this.date)
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
// Utils
|
|||
|
// -------------------------------------------------
|
|||
|
|
|||
|
datepicker.getDaysCount = function (date) {
|
|||
|
return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
|
|||
|
};
|
|||
|
|
|||
|
datepicker.getParsedDate = function (date) {
|
|||
|
return {
|
|||
|
year: date.getFullYear(),
|
|||
|
month: date.getMonth(),
|
|||
|
fullMonth: (date.getMonth() + 1) < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1, // One based
|
|||
|
date: date.getDate(),
|
|||
|
fullDate: date.getDate() < 10 ? '0' + date.getDate() : date.getDate(),
|
|||
|
day: date.getDay(),
|
|||
|
hours: date.getHours(),
|
|||
|
fullHours: date.getHours() < 10 ? '0' + date.getHours() : date.getHours() ,
|
|||
|
minutes: date.getMinutes(),
|
|||
|
fullMinutes: date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
datepicker.getDecade = function (date) {
|
|||
|
var firstYear = Math.floor(date.getFullYear() / 10) * 10;
|
|||
|
|
|||
|
return [firstYear, firstYear + 9];
|
|||
|
};
|
|||
|
|
|||
|
datepicker.template = function (str, data) {
|
|||
|
return str.replace(/#\{([\w]+)\}/g, function (source, match) {
|
|||
|
if (data[match] || data[match] === 0) {
|
|||
|
return data[match]
|
|||
|
}
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
datepicker.isSame = function (date1, date2, type) {
|
|||
|
if (!date1 || !date2) return false;
|
|||
|
var d1 = datepicker.getParsedDate(date1),
|
|||
|
d2 = datepicker.getParsedDate(date2),
|
|||
|
_type = type ? type : 'day',
|
|||
|
|
|||
|
conditions = {
|
|||
|
day: d1.date == d2.date && d1.month == d2.month && d1.year == d2.year,
|
|||
|
month: d1.month == d2.month && d1.year == d2.year,
|
|||
|
year: d1.year == d2.year
|
|||
|
};
|
|||
|
|
|||
|
return conditions[_type];
|
|||
|
};
|
|||
|
|
|||
|
datepicker.less = function (dateCompareTo, date, type) {
|
|||
|
if (!dateCompareTo || !date) return false;
|
|||
|
return date.getTime() < dateCompareTo.getTime();
|
|||
|
};
|
|||
|
|
|||
|
datepicker.bigger = function (dateCompareTo, date, type) {
|
|||
|
if (!dateCompareTo || !date) return false;
|
|||
|
return date.getTime() > dateCompareTo.getTime();
|
|||
|
};
|
|||
|
|
|||
|
datepicker.getLeadingZeroNum = function (num) {
|
|||
|
return parseInt(num) < 10 ? '0' + num : num;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Returns copy of date with hours and minutes equals to 0
|
|||
|
* @param date {Date}
|
|||
|
*/
|
|||
|
datepicker.resetTime = function (date) {
|
|||
|
if (typeof date != 'object') return;
|
|||
|
date = datepicker.getParsedDate(date);
|
|||
|
return new Date(date.year, date.month, date.date)
|
|||
|
};
|
|||
|
|
|||
|
$.fn.datepicker = function ( options ) {
|
|||
|
return this.each(function () {
|
|||
|
if (!$.data(this, pluginName)) {
|
|||
|
$.data(this, pluginName,
|
|||
|
new Datepicker( this, options ));
|
|||
|
} else {
|
|||
|
var _this = $.data(this, pluginName);
|
|||
|
|
|||
|
_this.opts = $.extend(true, _this.opts, options);
|
|||
|
_this.update();
|
|||
|
}
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
$.fn.datepicker.Constructor = Datepicker;
|
|||
|
|
|||
|
$.fn.datepicker.language = {
|
|||
|
ru: {
|
|||
|
days: ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'],
|
|||
|
daysShort: ['Вос','Пон','Вто','Сре','Чет','Пят','Суб'],
|
|||
|
daysMin: ['Вс','Пн','Вт','Ср','Чт','Пт','Сб'],
|
|||
|
months: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
|
|||
|
monthsShort: ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'],
|
|||
|
today: 'Сегодня',
|
|||
|
clear: 'Очистить',
|
|||
|
dateFormat: 'dd.mm.yyyy',
|
|||
|
timeFormat: 'hh:ii',
|
|||
|
firstDay: 1
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
$(function () {
|
|||
|
$(autoInitSelector).datepicker();
|
|||
|
})
|
|||
|
|
|||
|
})();
|
|||
|
|
|||
|
;(function () {
|
|||
|
var templates = {
|
|||
|
days:'' +
|
|||
|
'<div class="datepicker--days datepicker--body">' +
|
|||
|
'<div class="datepicker--days-names"></div>' +
|
|||
|
'<div class="datepicker--cells datepicker--cells-days"></div>' +
|
|||
|
'</div>',
|
|||
|
months: '' +
|
|||
|
'<div class="datepicker--months datepicker--body">' +
|
|||
|
'<div class="datepicker--cells datepicker--cells-months"></div>' +
|
|||
|
'</div>',
|
|||
|
years: '' +
|
|||
|
'<div class="datepicker--years datepicker--body">' +
|
|||
|
'<div class="datepicker--cells datepicker--cells-years"></div>' +
|
|||
|
'</div>'
|
|||
|
},
|
|||
|
datepicker = $.fn.datepicker,
|
|||
|
dp = datepicker.Constructor;
|
|||
|
|
|||
|
datepicker.Body = function (d, type, opts) {
|
|||
|
this.d = d;
|
|||
|
this.type = type;
|
|||
|
this.opts = opts;
|
|||
|
this.$el = $('');
|
|||
|
|
|||
|
if (this.opts.onlyTimepicker) return;
|
|||
|
this.init();
|
|||
|
};
|
|||
|
|
|||
|
datepicker.Body.prototype = {
|
|||
|
init: function () {
|
|||
|
this._buildBaseHtml();
|
|||
|
this._render();
|
|||
|
|
|||
|
this._bindEvents();
|
|||
|
},
|
|||
|
|
|||
|
_bindEvents: function () {
|
|||
|
this.$el.on('click', '.datepicker--cell', $.proxy(this._onClickCell, this));
|
|||
|
},
|
|||
|
|
|||
|
_buildBaseHtml: function () {
|
|||
|
this.$el = $(templates[this.type]).appendTo(this.d.$content);
|
|||
|
this.$names = $('.datepicker--days-names', this.$el);
|
|||
|
this.$cells = $('.datepicker--cells', this.$el);
|
|||
|
},
|
|||
|
|
|||
|
_getDayNamesHtml: function (firstDay, curDay, html, i) {
|
|||
|
curDay = curDay != undefined ? curDay : firstDay;
|
|||
|
html = html ? html : '';
|
|||
|
i = i != undefined ? i : 0;
|
|||
|
|
|||
|
if (i > 7) return html;
|
|||
|
if (curDay == 7) return this._getDayNamesHtml(firstDay, 0, html, ++i);
|
|||
|
|
|||
|
html += '<div class="datepicker--day-name' + (this.d.isWeekend(curDay) ? " -weekend-" : "") + '">' + this.d.loc.daysMin[curDay] + '</div>';
|
|||
|
|
|||
|
return this._getDayNamesHtml(firstDay, ++curDay, html, ++i);
|
|||
|
},
|
|||
|
|
|||
|
_getCellContents: function (date, type) {
|
|||
|
var classes = "datepicker--cell datepicker--cell-" + type,
|
|||
|
currentDate = new Date(),
|
|||
|
parent = this.d,
|
|||
|
minRange = dp.resetTime(parent.minRange),
|
|||
|
maxRange = dp.resetTime(parent.maxRange),
|
|||
|
opts = parent.opts,
|
|||
|
d = dp.getParsedDate(date),
|
|||
|
render = {},
|
|||
|
html = d.date;
|
|||
|
|
|||
|
switch (type) {
|
|||
|
case 'day':
|
|||
|
if (parent.isWeekend(d.day)) classes += " -weekend-";
|
|||
|
if (d.month != this.d.parsedDate.month) {
|
|||
|
classes += " -other-month-";
|
|||
|
if (!opts.selectOtherMonths) {
|
|||
|
classes += " -disabled-";
|
|||
|
}
|
|||
|
if (!opts.showOtherMonths) html = '';
|
|||
|
}
|
|||
|
break;
|
|||
|
case 'month':
|
|||
|
html = parent.loc[parent.opts.monthsField][d.month];
|
|||
|
break;
|
|||
|
case 'year':
|
|||
|
var decade = parent.curDecade;
|
|||
|
html = d.year;
|
|||
|
if (d.year < decade[0] || d.year > decade[1]) {
|
|||
|
classes += ' -other-decade-';
|
|||
|
if (!opts.selectOtherYears) {
|
|||
|
classes += " -disabled-";
|
|||
|
}
|
|||
|
if (!opts.showOtherYears) html = '';
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
if (opts.onRenderCell) {
|
|||
|
render = opts.onRenderCell(date, type) || {};
|
|||
|
html = render.html ? render.html : html;
|
|||
|
classes += render.classes ? ' ' + render.classes : '';
|
|||
|
}
|
|||
|
|
|||
|
if (opts.range) {
|
|||
|
if (dp.isSame(minRange, date, type)) classes += ' -range-from-';
|
|||
|
if (dp.isSame(maxRange, date, type)) classes += ' -range-to-';
|
|||
|
|
|||
|
if (parent.selectedDates.length == 1 && parent.focused) {
|
|||
|
if (
|
|||
|
(dp.bigger(minRange, date) && dp.less(parent.focused, date)) ||
|
|||
|
(dp.less(maxRange, date) && dp.bigger(parent.focused, date)))
|
|||
|
{
|
|||
|
classes += ' -in-range-'
|
|||
|
}
|
|||
|
|
|||
|
if (dp.less(maxRange, date) && dp.isSame(parent.focused, date)) {
|
|||
|
classes += ' -range-from-'
|
|||
|
}
|
|||
|
if (dp.bigger(minRange, date) && dp.isSame(parent.focused, date)) {
|
|||
|
classes += ' -range-to-'
|
|||
|
}
|
|||
|
|
|||
|
} else if (parent.selectedDates.length == 2) {
|
|||
|
if (dp.bigger(minRange, date) && dp.less(maxRange, date)) {
|
|||
|
classes += ' -in-range-'
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if (dp.isSame(currentDate, date, type)) classes += ' -current-';
|
|||
|
if (parent.focused && dp.isSame(date, parent.focused, type)) classes += ' -focus-';
|
|||
|
if (parent._isSelected(date, type)) classes += ' -selected-';
|
|||
|
if (!parent._isInRange(date, type) || render.disabled) classes += ' -disabled-';
|
|||
|
|
|||
|
return {
|
|||
|
html: html,
|
|||
|
classes: classes
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Calculates days number to render. Generates days html and returns it.
|
|||
|
* @param {object} date - Date object
|
|||
|
* @returns {string}
|
|||
|
* @private
|
|||
|
*/
|
|||
|
_getDaysHtml: function (date) {
|
|||
|
var totalMonthDays = dp.getDaysCount(date),
|
|||
|
firstMonthDay = new Date(date.getFullYear(), date.getMonth(), 1).getDay(),
|
|||
|
lastMonthDay = new Date(date.getFullYear(), date.getMonth(), totalMonthDays).getDay(),
|
|||
|
daysFromPevMonth = firstMonthDay - this.d.loc.firstDay,
|
|||
|
daysFromNextMonth = 6 - lastMonthDay + this.d.loc.firstDay;
|
|||
|
|
|||
|
daysFromPevMonth = daysFromPevMonth < 0 ? daysFromPevMonth + 7 : daysFromPevMonth;
|
|||
|
daysFromNextMonth = daysFromNextMonth > 6 ? daysFromNextMonth - 7 : daysFromNextMonth;
|
|||
|
|
|||
|
var startDayIndex = -daysFromPevMonth + 1,
|
|||
|
m, y,
|
|||
|
html = '';
|
|||
|
|
|||
|
for (var i = startDayIndex, max = totalMonthDays + daysFromNextMonth; i <= max; i++) {
|
|||
|
y = date.getFullYear();
|
|||
|
m = date.getMonth();
|
|||
|
|
|||
|
html += this._getDayHtml(new Date(y, m, i))
|
|||
|
}
|
|||
|
|
|||
|
return html;
|
|||
|
},
|
|||
|
|
|||
|
_getDayHtml: function (date) {
|
|||
|
var content = this._getCellContents(date, 'day');
|
|||
|
|
|||
|
return '<div class="' + content.classes + '" ' +
|
|||
|
'data-date="' + date.getDate() + '" ' +
|
|||
|
'data-month="' + date.getMonth() + '" ' +
|
|||
|
'data-year="' + date.getFullYear() + '">' + content.html + '</div>';
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Generates months html
|
|||
|
* @param {object} date - date instance
|
|||
|
* @returns {string}
|
|||
|
* @private
|
|||
|
*/
|
|||
|
_getMonthsHtml: function (date) {
|
|||
|
var html = '',
|
|||
|
d = dp.getParsedDate(date),
|
|||
|
i = 0;
|
|||
|
|
|||
|
while(i < 12) {
|
|||
|
html += this._getMonthHtml(new Date(d.year, i));
|
|||
|
i++
|
|||
|
}
|
|||
|
|
|||
|
return html;
|
|||
|
},
|
|||
|
|
|||
|
_getMonthHtml: function (date) {
|
|||
|
var content = this._getCellContents(date, 'month');
|
|||
|
|
|||
|
return '<div class="' + content.classes + '" data-month="' + date.getMonth() + '">' + content.html + '</div>'
|
|||
|
},
|
|||
|
|
|||
|
_getYearsHtml: function (date) {
|
|||
|
var d = dp.getParsedDate(date),
|
|||
|
decade = dp.getDecade(date),
|
|||
|
firstYear = decade[0] - 1,
|
|||
|
html = '',
|
|||
|
i = firstYear;
|
|||
|
|
|||
|
for (i; i <= decade[1] + 1; i++) {
|
|||
|
html += this._getYearHtml(new Date(i , 0));
|
|||
|
}
|
|||
|
|
|||
|
return html;
|
|||
|
},
|
|||
|
|
|||
|
_getYearHtml: function (date) {
|
|||
|
var content = this._getCellContents(date, 'year');
|
|||
|
|
|||
|
return '<div class="' + content.classes + '" data-year="' + date.getFullYear() + '">' + content.html + '</div>'
|
|||
|
},
|
|||
|
|
|||
|
_renderTypes: {
|
|||
|
days: function () {
|
|||
|
var dayNames = this._getDayNamesHtml(this.d.loc.firstDay),
|
|||
|
days = this._getDaysHtml(this.d.currentDate);
|
|||
|
|
|||
|
this.$cells.html(days);
|
|||
|
this.$names.html(dayNames)
|
|||
|
},
|
|||
|
months: function () {
|
|||
|
var html = this._getMonthsHtml(this.d.currentDate);
|
|||
|
|
|||
|
this.$cells.html(html)
|
|||
|
},
|
|||
|
years: function () {
|
|||
|
var html = this._getYearsHtml(this.d.currentDate);
|
|||
|
|
|||
|
this.$cells.html(html)
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_render: function () {
|
|||
|
if (this.opts.onlyTimepicker) return;
|
|||
|
this._renderTypes[this.type].bind(this)();
|
|||
|
},
|
|||
|
|
|||
|
_update: function () {
|
|||
|
var $cells = $('.datepicker--cell', this.$cells),
|
|||
|
_this = this,
|
|||
|
classes,
|
|||
|
$cell,
|
|||
|
date;
|
|||
|
$cells.each(function (cell, i) {
|
|||
|
$cell = $(this);
|
|||
|
date = _this.d._getDateFromCell($(this));
|
|||
|
classes = _this._getCellContents(date, _this.d.cellType);
|
|||
|
$cell.attr('class',classes.classes)
|
|||
|
});
|
|||
|
},
|
|||
|
|
|||
|
show: function () {
|
|||
|
if (this.opts.onlyTimepicker) return;
|
|||
|
this.$el.addClass('active');
|
|||
|
this.acitve = true;
|
|||
|
},
|
|||
|
|
|||
|
hide: function () {
|
|||
|
this.$el.removeClass('active');
|
|||
|
this.active = false;
|
|||
|
},
|
|||
|
|
|||
|
// Events
|
|||
|
// -------------------------------------------------
|
|||
|
|
|||
|
_handleClick: function (el) {
|
|||
|
var date = el.data('date') || 1,
|
|||
|
month = el.data('month') || 0,
|
|||
|
year = el.data('year') || this.d.parsedDate.year,
|
|||
|
dp = this.d;
|
|||
|
// Change view if min view does not reach yet
|
|||
|
if (dp.view != this.opts.minView) {
|
|||
|
dp.down(new Date(year, month, date));
|
|||
|
return;
|
|||
|
}
|
|||
|
// Select date if min view is reached
|
|||
|
var selectedDate = new Date(year, month, date),
|
|||
|
alreadySelected = this.d._isSelected(selectedDate, this.d.cellType);
|
|||
|
|
|||
|
if (!alreadySelected) {
|
|||
|
dp._trigger('clickCell', selectedDate);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
dp._handleAlreadySelectedDates.bind(dp, alreadySelected, selectedDate)();
|
|||
|
|
|||
|
},
|
|||
|
|
|||
|
_onClickCell: function (e) {
|
|||
|
var $el = $(e.target).closest('.datepicker--cell');
|
|||
|
|
|||
|
if ($el.hasClass('-disabled-')) return;
|
|||
|
|
|||
|
this._handleClick.bind(this)($el);
|
|||
|
}
|
|||
|
};
|
|||
|
})();
|
|||
|
|
|||
|
;(function () {
|
|||
|
var template = '' +
|
|||
|
'<div class="datepicker--nav-action" data-action="prev">#{prevHtml}</div>' +
|
|||
|
'<div class="datepicker--nav-title">#{title}</div>' +
|
|||
|
'<div class="datepicker--nav-action" data-action="next">#{nextHtml}</div>',
|
|||
|
buttonsContainerTemplate = '<div class="datepicker--buttons"></div>',
|
|||
|
button = '<span class="datepicker--button" data-action="#{action}">#{label}</span>',
|
|||
|
datepicker = $.fn.datepicker,
|
|||
|
dp = datepicker.Constructor;
|
|||
|
|
|||
|
datepicker.Navigation = function (d, opts) {
|
|||
|
this.d = d;
|
|||
|
this.opts = opts;
|
|||
|
|
|||
|
this.$buttonsContainer = '';
|
|||
|
|
|||
|
this.init();
|
|||
|
};
|
|||
|
|
|||
|
datepicker.Navigation.prototype = {
|
|||
|
init: function () {
|
|||
|
this._buildBaseHtml();
|
|||
|
this._bindEvents();
|
|||
|
},
|
|||
|
|
|||
|
_bindEvents: function () {
|
|||
|
this.d.$nav.on('click', '.datepicker--nav-action', $.proxy(this._onClickNavButton, this));
|
|||
|
this.d.$nav.on('click', '.datepicker--nav-title', $.proxy(this._onClickNavTitle, this));
|
|||
|
this.d.$datepicker.on('click', '.datepicker--button', $.proxy(this._onClickNavButton, this));
|
|||
|
},
|
|||
|
|
|||
|
_buildBaseHtml: function () {
|
|||
|
if (!this.opts.onlyTimepicker) {
|
|||
|
this._render();
|
|||
|
}
|
|||
|
this._addButtonsIfNeed();
|
|||
|
},
|
|||
|
|
|||
|
_addButtonsIfNeed: function () {
|
|||
|
if (this.opts.todayButton) {
|
|||
|
this._addButton('today')
|
|||
|
}
|
|||
|
if (this.opts.clearButton) {
|
|||
|
this._addButton('clear')
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_render: function () {
|
|||
|
var title = this._getTitle(this.d.currentDate),
|
|||
|
html = dp.template(template, $.extend({title: title}, this.opts));
|
|||
|
this.d.$nav.html(html);
|
|||
|
if (this.d.view == 'years') {
|
|||
|
$('.datepicker--nav-title', this.d.$nav).addClass('-disabled-');
|
|||
|
}
|
|||
|
this.setNavStatus();
|
|||
|
},
|
|||
|
|
|||
|
_getTitle: function (date) {
|
|||
|
return this.d.formatDate(this.opts.navTitles[this.d.view], date)
|
|||
|
},
|
|||
|
|
|||
|
_addButton: function (type) {
|
|||
|
if (!this.$buttonsContainer.length) {
|
|||
|
this._addButtonsContainer();
|
|||
|
}
|
|||
|
|
|||
|
var data = {
|
|||
|
action: type,
|
|||
|
label: this.d.loc[type]
|
|||
|
},
|
|||
|
html = dp.template(button, data);
|
|||
|
|
|||
|
if ($('[data-action=' + type + ']', this.$buttonsContainer).length) return;
|
|||
|
this.$buttonsContainer.append(html);
|
|||
|
},
|
|||
|
|
|||
|
_addButtonsContainer: function () {
|
|||
|
this.d.$datepicker.append(buttonsContainerTemplate);
|
|||
|
this.$buttonsContainer = $('.datepicker--buttons', this.d.$datepicker);
|
|||
|
},
|
|||
|
|
|||
|
setNavStatus: function () {
|
|||
|
if (!(this.opts.minDate || this.opts.maxDate) || !this.opts.disableNavWhenOutOfRange) return;
|
|||
|
|
|||
|
var date = this.d.parsedDate,
|
|||
|
m = date.month,
|
|||
|
y = date.year,
|
|||
|
d = date.date;
|
|||
|
|
|||
|
switch (this.d.view) {
|
|||
|
case 'days':
|
|||
|
if (!this.d._isInRange(new Date(y, m-1, 1), 'month')) {
|
|||
|
this._disableNav('prev')
|
|||
|
}
|
|||
|
if (!this.d._isInRange(new Date(y, m+1, 1), 'month')) {
|
|||
|
this._disableNav('next')
|
|||
|
}
|
|||
|
break;
|
|||
|
case 'months':
|
|||
|
if (!this.d._isInRange(new Date(y-1, m, d), 'year')) {
|
|||
|
this._disableNav('prev')
|
|||
|
}
|
|||
|
if (!this.d._isInRange(new Date(y+1, m, d), 'year')) {
|
|||
|
this._disableNav('next')
|
|||
|
}
|
|||
|
break;
|
|||
|
case 'years':
|
|||
|
var decade = dp.getDecade(this.d.date);
|
|||
|
if (!this.d._isInRange(new Date(decade[0] - 1, 0, 1), 'year')) {
|
|||
|
this._disableNav('prev')
|
|||
|
}
|
|||
|
if (!this.d._isInRange(new Date(decade[1] + 1, 0, 1), 'year')) {
|
|||
|
this._disableNav('next')
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_disableNav: function (nav) {
|
|||
|
$('[data-action="' + nav + '"]', this.d.$nav).addClass('-disabled-')
|
|||
|
},
|
|||
|
|
|||
|
_activateNav: function (nav) {
|
|||
|
$('[data-action="' + nav + '"]', this.d.$nav).removeClass('-disabled-')
|
|||
|
},
|
|||
|
|
|||
|
_onClickNavButton: function (e) {
|
|||
|
var $el = $(e.target).closest('[data-action]'),
|
|||
|
action = $el.data('action');
|
|||
|
|
|||
|
this.d[action]();
|
|||
|
},
|
|||
|
|
|||
|
_onClickNavTitle: function (e) {
|
|||
|
if ($(e.target).hasClass('-disabled-')) return;
|
|||
|
|
|||
|
if (this.d.view == 'days') {
|
|||
|
return this.d.view = 'months'
|
|||
|
}
|
|||
|
|
|||
|
this.d.view = 'years';
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
})();
|
|||
|
|
|||
|
;(function () {
|
|||
|
var template = '<div class="datepicker--time">' +
|
|||
|
'<div class="datepicker--time-current">' +
|
|||
|
' <span class="datepicker--time-current-hours">#{hourVisible}</span>' +
|
|||
|
' <span class="datepicker--time-current-colon">:</span>' +
|
|||
|
' <span class="datepicker--time-current-minutes">#{minValue}</span>' +
|
|||
|
'</div>' +
|
|||
|
'<div class="datepicker--time-sliders">' +
|
|||
|
' <div class="datepicker--time-row">' +
|
|||
|
' <input type="range" name="hours" value="#{hourValue}" min="#{hourMin}" max="#{hourMax}" step="#{hourStep}"/>' +
|
|||
|
' </div>' +
|
|||
|
' <div class="datepicker--time-row">' +
|
|||
|
' <input type="range" name="minutes" value="#{minValue}" min="#{minMin}" max="#{minMax}" step="#{minStep}"/>' +
|
|||
|
' </div>' +
|
|||
|
'</div>' +
|
|||
|
'</div>',
|
|||
|
datepicker = $.fn.datepicker,
|
|||
|
dp = datepicker.Constructor;
|
|||
|
|
|||
|
datepicker.Timepicker = function (inst, opts) {
|
|||
|
this.d = inst;
|
|||
|
this.opts = opts;
|
|||
|
|
|||
|
this.init();
|
|||
|
};
|
|||
|
|
|||
|
datepicker.Timepicker.prototype = {
|
|||
|
init: function () {
|
|||
|
var input = 'input';
|
|||
|
this._setTime(this.d.date);
|
|||
|
this._buildHTML();
|
|||
|
|
|||
|
if (navigator.userAgent.match(/trident/gi)) {
|
|||
|
input = 'change';
|
|||
|
}
|
|||
|
|
|||
|
this.d.$el.on('selectDate', this._onSelectDate.bind(this));
|
|||
|
this.$ranges.on(input, this._onChangeRange.bind(this));
|
|||
|
this.$ranges.on('mouseup', this._onMouseUpRange.bind(this));
|
|||
|
this.$ranges.on('mousemove focus ', this._onMouseEnterRange.bind(this));
|
|||
|
this.$ranges.on('mouseout blur', this._onMouseOutRange.bind(this));
|
|||
|
},
|
|||
|
|
|||
|
_setTime: function (date) {
|
|||
|
var _date = dp.getParsedDate(date);
|
|||
|
|
|||
|
this._handleDate(date);
|
|||
|
this.hours = _date.hours < this.minHours ? this.minHours : _date.hours;
|
|||
|
this.minutes = _date.minutes < this.minMinutes ? this.minMinutes : _date.minutes;
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Sets minHours and minMinutes from date (usually it's a minDate)
|
|||
|
* Also changes minMinutes if current hours are bigger then @date hours
|
|||
|
* @param date {Date}
|
|||
|
* @private
|
|||
|
*/
|
|||
|
_setMinTimeFromDate: function (date) {
|
|||
|
this.minHours = date.getHours();
|
|||
|
this.minMinutes = date.getMinutes();
|
|||
|
|
|||
|
// If, for example, min hours are 10, and current hours are 12,
|
|||
|
// update minMinutes to default value, to be able to choose whole range of values
|
|||
|
if (this.d.lastSelectedDate) {
|
|||
|
if (this.d.lastSelectedDate.getHours() > date.getHours()) {
|
|||
|
this.minMinutes = this.opts.minMinutes;
|
|||
|
}
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_setMaxTimeFromDate: function (date) {
|
|||
|
this.maxHours = date.getHours();
|
|||
|
this.maxMinutes = date.getMinutes();
|
|||
|
|
|||
|
if (this.d.lastSelectedDate) {
|
|||
|
if (this.d.lastSelectedDate.getHours() < date.getHours()) {
|
|||
|
this.maxMinutes = this.opts.maxMinutes;
|
|||
|
}
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_setDefaultMinMaxTime: function () {
|
|||
|
var maxHours = 23,
|
|||
|
maxMinutes = 59,
|
|||
|
opts = this.opts;
|
|||
|
|
|||
|
this.minHours = opts.minHours < 0 || opts.minHours > maxHours ? 0 : opts.minHours;
|
|||
|
this.minMinutes = opts.minMinutes < 0 || opts.minMinutes > maxMinutes ? 0 : opts.minMinutes;
|
|||
|
this.maxHours = opts.maxHours < 0 || opts.maxHours > maxHours ? maxHours : opts.maxHours;
|
|||
|
this.maxMinutes = opts.maxMinutes < 0 || opts.maxMinutes > maxMinutes ? maxMinutes : opts.maxMinutes;
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Looks for min/max hours/minutes and if current values
|
|||
|
* are out of range sets valid values.
|
|||
|
* @private
|
|||
|
*/
|
|||
|
_validateHoursMinutes: function (date) {
|
|||
|
if (this.hours < this.minHours) {
|
|||
|
this.hours = this.minHours;
|
|||
|
} else if (this.hours > this.maxHours) {
|
|||
|
this.hours = this.maxHours;
|
|||
|
}
|
|||
|
|
|||
|
if (this.minutes < this.minMinutes) {
|
|||
|
this.minutes = this.minMinutes;
|
|||
|
} else if (this.minutes > this.maxMinutes) {
|
|||
|
this.minutes = this.maxMinutes;
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_buildHTML: function () {
|
|||
|
var lz = dp.getLeadingZeroNum,
|
|||
|
data = {
|
|||
|
hourMin: this.minHours,
|
|||
|
hourMax: lz(this.maxHours),
|
|||
|
hourStep: this.opts.hoursStep,
|
|||
|
hourValue: this.hours,
|
|||
|
hourVisible: lz(this.displayHours),
|
|||
|
minMin: this.minMinutes,
|
|||
|
minMax: lz(this.maxMinutes),
|
|||
|
minStep: this.opts.minutesStep,
|
|||
|
minValue: lz(this.minutes)
|
|||
|
},
|
|||
|
_template = dp.template(template, data);
|
|||
|
|
|||
|
this.$timepicker = $(_template).appendTo(this.d.$datepicker);
|
|||
|
this.$ranges = $('[type="range"]', this.$timepicker);
|
|||
|
this.$hours = $('[name="hours"]', this.$timepicker);
|
|||
|
this.$minutes = $('[name="minutes"]', this.$timepicker);
|
|||
|
this.$hoursText = $('.datepicker--time-current-hours', this.$timepicker);
|
|||
|
this.$minutesText = $('.datepicker--time-current-minutes', this.$timepicker);
|
|||
|
|
|||
|
if (this.d.ampm) {
|
|||
|
this.$ampm = $('<span class="datepicker--time-current-ampm">')
|
|||
|
.appendTo($('.datepicker--time-current', this.$timepicker))
|
|||
|
.html(this.dayPeriod);
|
|||
|
|
|||
|
this.$timepicker.addClass('-am-pm-');
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_updateCurrentTime: function () {
|
|||
|
var h = dp.getLeadingZeroNum(this.displayHours),
|
|||
|
m = dp.getLeadingZeroNum(this.minutes);
|
|||
|
|
|||
|
this.$hoursText.html(h);
|
|||
|
this.$minutesText.html(m);
|
|||
|
|
|||
|
if (this.d.ampm) {
|
|||
|
this.$ampm.html(this.dayPeriod);
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_updateRanges: function () {
|
|||
|
this.$hours.attr({
|
|||
|
min: this.minHours,
|
|||
|
max: this.maxHours
|
|||
|
}).val(this.hours);
|
|||
|
|
|||
|
this.$minutes.attr({
|
|||
|
min: this.minMinutes,
|
|||
|
max: this.maxMinutes
|
|||
|
}).val(this.minutes)
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Sets minHours, minMinutes etc. from date. If date is not passed, than sets
|
|||
|
* values from options
|
|||
|
* @param [date] {object} - Date object, to get values from
|
|||
|
* @private
|
|||
|
*/
|
|||
|
_handleDate: function (date) {
|
|||
|
this._setDefaultMinMaxTime();
|
|||
|
if (date) {
|
|||
|
if (dp.isSame(date, this.d.opts.minDate)) {
|
|||
|
this._setMinTimeFromDate(this.d.opts.minDate);
|
|||
|
} else if (dp.isSame(date, this.d.opts.maxDate)) {
|
|||
|
this._setMaxTimeFromDate(this.d.opts.maxDate);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
this._validateHoursMinutes(date);
|
|||
|
},
|
|||
|
|
|||
|
update: function () {
|
|||
|
this._updateRanges();
|
|||
|
this._updateCurrentTime();
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Calculates valid hour value to display in text input and datepicker's body.
|
|||
|
* @param date {Date|Number} - date or hours
|
|||
|
* @param [ampm] {Boolean} - 12 hours mode
|
|||
|
* @returns {{hours: *, dayPeriod: string}}
|
|||
|
* @private
|
|||
|
*/
|
|||
|
_getValidHoursFromDate: function (date, ampm) {
|
|||
|
var d = date,
|
|||
|
hours = date;
|
|||
|
|
|||
|
if (date instanceof Date) {
|
|||
|
d = dp.getParsedDate(date);
|
|||
|
hours = d.hours;
|
|||
|
}
|
|||
|
|
|||
|
var _ampm = ampm || this.d.ampm,
|
|||
|
dayPeriod = 'am';
|
|||
|
|
|||
|
if (_ampm) {
|
|||
|
switch(true) {
|
|||
|
case hours == 0:
|
|||
|
hours = 12;
|
|||
|
break;
|
|||
|
case hours == 12:
|
|||
|
dayPeriod = 'pm';
|
|||
|
break;
|
|||
|
case hours > 11:
|
|||
|
hours = hours - 12;
|
|||
|
dayPeriod = 'pm';
|
|||
|
break;
|
|||
|
default:
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return {
|
|||
|
hours: hours,
|
|||
|
dayPeriod: dayPeriod
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
set hours (val) {
|
|||
|
this._hours = val;
|
|||
|
|
|||
|
var displayHours = this._getValidHoursFromDate(val);
|
|||
|
|
|||
|
this.displayHours = displayHours.hours;
|
|||
|
this.dayPeriod = displayHours.dayPeriod;
|
|||
|
},
|
|||
|
|
|||
|
get hours() {
|
|||
|
return this._hours;
|
|||
|
},
|
|||
|
|
|||
|
// Events
|
|||
|
// -------------------------------------------------
|
|||
|
|
|||
|
_onChangeRange: function (e) {
|
|||
|
var $target = $(e.target),
|
|||
|
name = $target.attr('name');
|
|||
|
|
|||
|
this.d.timepickerIsActive = true;
|
|||
|
|
|||
|
this[name] = $target.val();
|
|||
|
this._updateCurrentTime();
|
|||
|
this.d._trigger('timeChange', [this.hours, this.minutes]);
|
|||
|
|
|||
|
this._handleDate(this.d.lastSelectedDate);
|
|||
|
this.update()
|
|||
|
},
|
|||
|
|
|||
|
_onSelectDate: function (e, data) {
|
|||
|
this._handleDate(data);
|
|||
|
this.update();
|
|||
|
},
|
|||
|
|
|||
|
_onMouseEnterRange: function (e) {
|
|||
|
var name = $(e.target).attr('name');
|
|||
|
$('.datepicker--time-current-' + name, this.$timepicker).addClass('-focus-');
|
|||
|
},
|
|||
|
|
|||
|
_onMouseOutRange: function (e) {
|
|||
|
var name = $(e.target).attr('name');
|
|||
|
if (this.d.inFocus) return; // Prevent removing focus when mouse out of range slider
|
|||
|
$('.datepicker--time-current-' + name, this.$timepicker).removeClass('-focus-');
|
|||
|
},
|
|||
|
|
|||
|
_onMouseUpRange: function (e) {
|
|||
|
this.d.timepickerIsActive = false;
|
|||
|
}
|
|||
|
};
|
|||
|
})();
|
|||
|
})(window, jQuery);
|