mirror of
https://github.com/frappe/air-datepicker.git
synced 2026-01-14 11:01:22 +08:00
1170 lines
38 KiB
JavaScript
1170 lines
38 KiB
JavaScript
var Datepicker;
|
||
|
||
(function (window, $, undefined) {
|
||
var 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 = {
|
||
inline: false,
|
||
language: 'ru',
|
||
startDate: new Date(),
|
||
firstDay: '',
|
||
weekends: [6, 0],
|
||
dateFormat: '',
|
||
toggleSelected: 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: ',',
|
||
|
||
todayButton: false,
|
||
clearButton: false,
|
||
|
||
showEvent: 'focus',
|
||
autoClose: false,
|
||
|
||
// navigation
|
||
monthsFiled: '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'
|
||
},
|
||
|
||
// events
|
||
onSelect: '',
|
||
onChangeMonth: '',
|
||
onChangeYear: '',
|
||
onChangeDecade: '',
|
||
onChangeView: '',
|
||
onRenderCell: ''
|
||
};
|
||
|
||
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;
|
||
}
|
||
|
||
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.init()
|
||
};
|
||
|
||
|
||
Datepicker.prototype = {
|
||
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()
|
||
}
|
||
}
|
||
|
||
this.views[this.currentView] = new Datepicker.Body(this, this.currentView, this.opts);
|
||
this.views[this.currentView].show();
|
||
this.nav = new Datepicker.Navigation(this, this.opts);
|
||
this.view = this.currentView;
|
||
|
||
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, this._onShowEvent.bind(this));
|
||
this.$el.on('blur', this._onBlur.bind(this));
|
||
this.$el.on('input', this._onInput.bind(this));
|
||
this.$datepicker.on('mousedown', this._onMouseDownDatepicker.bind(this));
|
||
this.$datepicker.on('mouseup', this._onMouseUpDatepicker.bind(this));
|
||
$(window).on('resize', this._onResize.bind(this))
|
||
},
|
||
|
||
isWeekend: function (day) {
|
||
return this.opts.weekends.indexOf(day) !== -1;
|
||
},
|
||
|
||
_defineLocale: function (lang) {
|
||
if (typeof lang == 'string') {
|
||
this.loc = Datepicker.language[lang];
|
||
if (!this.loc) {
|
||
console.warn('Can\'t find language "' + lang + '" in Datepicker.language, will use "ru" instead');
|
||
this.loc = $.extend(true, {}, Datepicker.language.ru)
|
||
}
|
||
|
||
this.loc = $.extend(true, {}, Datepicker.language.ru, Datepicker.language[lang])
|
||
} else {
|
||
this.loc = $.extend(true, {}, Datepicker.language.ru, lang)
|
||
}
|
||
|
||
if (this.opts.dateFormat) {
|
||
this.loc.dateFormat = this.opts.dateFormat
|
||
}
|
||
|
||
if (this.opts.firstDay) {
|
||
this.loc.firstDay = this.opts.firstDay
|
||
}
|
||
},
|
||
|
||
_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) {
|
||
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);
|
||
|
||
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) {
|
||
dates = selectedDates.map(function(date) {
|
||
var parsedDate = Datepicker.getParsedDate(date);
|
||
return new Date(parsedDate.year, parsedDate.month, parsedDate.date)
|
||
})
|
||
}
|
||
|
||
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,
|
||
locale = this.loc,
|
||
decade = Datepicker.getDecade(date),
|
||
d = Datepicker.getParsedDate(date);
|
||
|
||
switch (true) {
|
||
case /dd/.test(result):
|
||
result = result.replace(/\bdd\b/, d.fullDate);
|
||
case /d/.test(result):
|
||
result = result.replace(/\bd\b/, d.date);
|
||
case /DD/.test(result):
|
||
result = result.replace(/\bDD\b/, locale.days[d.day]);
|
||
case /D/.test(result):
|
||
result = result.replace(/\bD\b/, locale.daysShort[d.day]);
|
||
case /mm/.test(result):
|
||
result = result.replace(/\bmm\b/, d.fullMonth);
|
||
case /m/.test(result):
|
||
result = result.replace(/\bm\b/, d.month + 1);
|
||
case /MM/.test(result):
|
||
result = result.replace(/\bMM\b/, this.loc.months[d.month]);
|
||
case /M/.test(result):
|
||
result = result.replace(/\bM\b/, locale.monthsShort[d.month]);
|
||
case /yyyy/.test(result):
|
||
result = result.replace(/\byyyy\b/, d.year);
|
||
case /yyyy1/.test(result):
|
||
result = result.replace(/\byyyy1\b/, decade[0]);
|
||
case /yyyy2/.test(result):
|
||
result = result.replace(/\byyyy2\b/, decade[1]);
|
||
case /yy/.test(result):
|
||
result = result.replace(/\byy\b/, d.year.toString().slice(-2));
|
||
}
|
||
|
||
return result;
|
||
},
|
||
|
||
selectDate: function (date) {
|
||
var d = this.parsedDate,
|
||
newDate = '';
|
||
|
||
if (!(date instanceof Date)) return;
|
||
|
||
if (this.view == 'days') {
|
||
if (date.getMonth() != d.month && this.opts.moveToOtherMonthsOnSelect) {
|
||
newDate = new Date(date.getFullYear(), date.getMonth(), 1);
|
||
|
||
}
|
||
}
|
||
|
||
if (this.view == 'years') {
|
||
if (date.getFullYear() != d.year && this.opts.moveToOtherYearsOnSelect) {
|
||
newDate = new Date(date.getFullYear(), 0, 1);
|
||
}
|
||
}
|
||
|
||
if (newDate) {
|
||
this.silent = true;
|
||
this.date = newDate;
|
||
this.silent = false;
|
||
this.nav._render()
|
||
}
|
||
|
||
if (this.opts.multipleDates) {
|
||
if (this.selectedDates.length === this.opts.multipleDates) return;
|
||
if (!this._isSelected(date)) {
|
||
this.selectedDates.push(date);
|
||
}
|
||
} else {
|
||
this.selectedDates = [date];
|
||
}
|
||
|
||
this._setInputValue();
|
||
|
||
if (this.opts.onSelect) {
|
||
this._triggerOnChange();
|
||
}
|
||
|
||
if (this.opts.autoClose) {
|
||
this.hide();
|
||
} else {
|
||
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);
|
||
_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();
|
||
},
|
||
|
||
clear: function () {
|
||
this.selectedDates = [];
|
||
this.views[this.currentView]._render();
|
||
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;
|
||
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();
|
||
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)
|
||
}
|
||
}
|
||
|
||
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) {
|
||
return this.selectedDates.some(function (date) {
|
||
return Datepicker.isSame(date, checkDate, cellType)
|
||
})
|
||
},
|
||
|
||
_setInputValue: function () {
|
||
var _this = this,
|
||
format = this.loc.dateFormat,
|
||
value = this.selectedDates.map(function (date) {
|
||
return _this.formatDate(format, date)
|
||
});
|
||
|
||
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
|
||
}
|
||
},
|
||
|
||
_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 () {
|
||
this.setPosition(this.opts.position);
|
||
this.$datepicker.addClass('active');
|
||
this.visible = true;
|
||
},
|
||
|
||
hide: function () {
|
||
this.$datepicker
|
||
.removeClass('active')
|
||
.css({
|
||
left: '-100000px'
|
||
});
|
||
|
||
this.inFocus = false;
|
||
this.visible = false;
|
||
this.$el.blur();
|
||
},
|
||
|
||
_onShowEvent: function () {
|
||
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;
|
||
this.$el.focus()
|
||
},
|
||
|
||
_onInput: function () {
|
||
var val = this.$el.val();
|
||
|
||
if (!val) {
|
||
this.clear();
|
||
}
|
||
},
|
||
|
||
_onResize: function () {
|
||
if (this.visible) {
|
||
this.setPosition();
|
||
}
|
||
},
|
||
|
||
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 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()
|
||
}
|
||
};
|
||
|
||
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) {
|
||
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.language = {
|
||
ru: {
|
||
days: ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'],
|
||
daysShort: ['Вос','Пон','Вто','Сре','Чет','Пят','Суб'],
|
||
daysMin: ['Вс','Пн','Вт','Ср','Чт','Пт','Сб'],
|
||
months: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
|
||
monthsShort: ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'],
|
||
today: 'Сегодня',
|
||
clear: 'Очистить',
|
||
dateFormat: 'dd.mm.yyyy',
|
||
firstDay: 1
|
||
}
|
||
};
|
||
|
||
$.fn[pluginName] = function ( options ) {
|
||
if (Datepicker.prototype[options]) {
|
||
Datepicker.prototype[options].apply(this.data(pluginName), Array.prototype.slice.call(arguments, 1));
|
||
} else {
|
||
return this.each(function () {
|
||
if (!$.data(this, pluginName)) {
|
||
$.data(this, pluginName,
|
||
new Datepicker( this, options ));
|
||
} else {
|
||
var _this = $.data(this, pluginName),
|
||
oldOpts = _this.opts;
|
||
|
||
_this.opts = $.extend({}, oldOpts, options);
|
||
}
|
||
});
|
||
}
|
||
};
|
||
|
||
$(function () {
|
||
$(autoInitSelector).datepicker();
|
||
})
|
||
|
||
})(window, jQuery);
|
||
;(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.Body = function (d, type, opts) {
|
||
this.d = d;
|
||
this.type = type;
|
||
this.opts = opts;
|
||
|
||
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);
|
||
},
|
||
|
||
/**
|
||
* Calculates days number to render. Generates days html and returns it.
|
||
* @param {object} date - Date object
|
||
* @returns {string}
|
||
* @private
|
||
*/
|
||
_getDaysHtml: function (date) {
|
||
var totalMonthDays = Datepicker.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 _class = "datepicker--cell datepicker--cell-day",
|
||
currentDate = new Date(),
|
||
d = Datepicker.getParsedDate(date),
|
||
render = {},
|
||
html = d.date;
|
||
|
||
if (this.opts.onRenderCell) {
|
||
render = this.opts.onRenderCell(date, 'day') || {};
|
||
html = render.html ? render.html : html;
|
||
_class += render.classes ? ' ' + render.classes : '';
|
||
}
|
||
|
||
if (this.d.isWeekend(d.day)) _class += " -weekend-";
|
||
if (Datepicker.isSame(currentDate, date)) _class += ' -current-';
|
||
if (this.d._isSelected(date, 'day')) _class += ' -selected-';
|
||
if (!this.d._isInRange(date) || render.disabled) _class += ' -disabled-';
|
||
if (d.month != this.d.parsedDate.month) {
|
||
_class += " -other-month-";
|
||
|
||
if (!this.opts.selectOtherMonths) {
|
||
_class += " -disabled-";
|
||
}
|
||
|
||
if (!this.opts.showOtherMonths) html = '';
|
||
}
|
||
|
||
return '<div class="' + _class + '" ' +
|
||
'data-date="' + date.getDate() + '" ' +
|
||
'data-month="' + date.getMonth() + '" ' +
|
||
'data-year="' + date.getFullYear() + '">' + html + '</div>';
|
||
},
|
||
|
||
/**
|
||
* Generates months html
|
||
* @param {object} date - date instance
|
||
* @returns {string}
|
||
* @private
|
||
*/
|
||
_getMonthsHtml: function (date) {
|
||
var html = '',
|
||
d = Datepicker.getParsedDate(date),
|
||
i = 0;
|
||
|
||
while(i < 12) {
|
||
html += this._getMonthHtml(new Date(d.year, i));
|
||
i++
|
||
}
|
||
|
||
return html;
|
||
},
|
||
|
||
_getMonthHtml: function (date) {
|
||
var _class = "datepicker--cell datepicker--cell-month",
|
||
d = Datepicker.getParsedDate(date),
|
||
currentDate = new Date(),
|
||
loc = this.d.loc,
|
||
html = loc[this.opts.monthsFiled][d.month],
|
||
render = {};
|
||
|
||
if (this.opts.onRenderCell) {
|
||
render = this.opts.onRenderCell(date, 'month') || {};
|
||
html = render.html ? render.html : html;
|
||
_class += render.classes ? ' ' + render.classes : '';
|
||
}
|
||
|
||
if (Datepicker.isSame(currentDate, date, 'month')) _class += ' -current-';
|
||
if (this.d._isSelected(date, 'month')) _class += ' -selected-';
|
||
if (!this.d._isInRange(date, 'month') || render.disabled) _class += ' -disabled-';
|
||
|
||
return '<div class="' + _class + '" data-month="' + d.month + '">' + html + '</div>'
|
||
},
|
||
|
||
_getYearsHtml: function (date) {
|
||
var d = Datepicker.getParsedDate(date),
|
||
decade = Datepicker.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 _class = "datepicker--cell datepicker--cell-year",
|
||
decade = Datepicker.getDecade(this.d.date),
|
||
currentDate = new Date(),
|
||
d = Datepicker.getParsedDate(date),
|
||
html = d.year,
|
||
render = {};
|
||
|
||
if (this.opts.onRenderCell) {
|
||
render = this.opts.onRenderCell(date, 'year') || {};
|
||
html = render.html ? render.html : html;
|
||
_class += render.classes ? ' ' + render.classes : '';
|
||
}
|
||
|
||
if (d.year < decade[0] || d.year > decade[1]) {
|
||
_class += ' -other-decade-';
|
||
|
||
if (!this.opts.selectOtherYears) {
|
||
_class += " -disabled-";
|
||
}
|
||
|
||
if (!this.opts.showOtherYears) html = '';
|
||
}
|
||
|
||
if (Datepicker.isSame(currentDate, date, 'year')) _class += ' -current-';
|
||
if (this.d._isSelected(date, 'year')) _class += ' -selected-';
|
||
if (!this.d._isInRange(date, 'year') || render.disabled) _class += ' -disabled-';
|
||
|
||
return '<div class="' + _class + '" data-year="' + d.year + '">' + 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 () {
|
||
this._renderTypes[this.type].bind(this)();
|
||
},
|
||
|
||
show: function () {
|
||
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;
|
||
|
||
// Change view if min view does not reach yet
|
||
if (this.d.view != this.opts.minView) {
|
||
var nextViewIndex = this.d.viewIndex - 1;
|
||
|
||
this.d.silent = true;
|
||
this.d.date = new Date(year, month, date);
|
||
this.d.silent = false;
|
||
this.d.view = this.d.viewIndexes[nextViewIndex];
|
||
|
||
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) {
|
||
this.d.selectDate(selectedDate);
|
||
} else if (alreadySelected && this.opts.toggleSelected){
|
||
this.d.removeDate(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.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 () {
|
||
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 = Datepicker.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 = Datepicker.template(button, data);
|
||
|
||
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, d), 'month')) {
|
||
this._disableNav('prev')
|
||
}
|
||
if (!this.d._isInRange(new Date(y, m+1, d), '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':
|
||
if (!this.d._isInRange(new Date(y-10, m, d), 'year')) {
|
||
this._disableNav('prev')
|
||
}
|
||
if (!this.d._isInRange(new Date(y+10, m, d), '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';
|
||
}
|
||
}
|
||
|
||
})();
|