var Datepicker;
(function (window, $, undefined) {
var pluginName = 'datepicker',
$body, $datepickersContainer,
baseTemplate = '' +
'
',
defaults = {
//TODO сделать работу с инпутом
inline: false,
language: 'ru',
startDate: new Date(),
firstDay: '',
weekends: [6, 0],
dateFormat: '',
toggleSelected: true,
position: 'bottom left',
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',
// navigation
prevHtml: '«',
nextHtml: '»',
// events
onChange: '',
onRenderCell: ''
};
Datepicker = function (el, options) {
this.el = el;
this.$el = $(el);
this.opts = $.extend({}, defaults, options);
if ($body == undefined) {
$body = $('body');
}
if (!this.opts.startDate) {
this.opts.startDate = new Date();
}
if (this.el.nodeName == 'INPUT') {
this.elIsInput = true;
}
if (!this.containerBuilt && !this.opts.inline && this.elIsInput) {
this._buildDatepickersContainer();
}
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.minDate = this.opts.minDate ? this.opts.minDate : new Date(-8639999913600000);
this.maxDate = this.opts.maxDate ? this.opts.maxDate : new Date(8639999913600000);
this.selectedDates = [];
this.views = {};
this.init()
};
Datepicker.prototype = {
containerBuilt: false,
viewIndexes: ['days', 'months', 'years'],
init: function () {
this._buildBaseHtml();
this._defineLocale(this.opts.language);
if (this.elIsInput) {
if (!this.opts.inline) {
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;
},
_bindEvents : function () {
this.$el.on(this.opts.showEvent, this._onShowEvent.bind(this));
this.$datepicker.on('mousedown', this._onMouseDownDatepicker.bind(this));
this.$datepicker.on('mouseup', this._onMouseUpDatepicker.bind(this));
$body.on('mousedown.datepicker', this._onMouseDownBody.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 = Datepicker.language.ru
}
} else {
this.loc = $.extend({}, 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 () {
this.containerBuilt = true;
$body.append('');
$datepickersContainer = $('#datepickers-container');
},
_buildBaseHtml: function () {
var $appendTarget = this.$el;
if(this.el.nodeName == 'INPUT') {
if (!this.opts.inline) {
$appendTarget = $datepickersContainer;
} else {
}
}
this.$datepicker = $(baseTemplate).appendTo($appendTarget);
this.$content = $('.datepicker--content', this.$datepicker);
this.$nav = $('.datepicker--nav', this.$datepicker);
},
_defineDOM: function () {
},
_triggerOnChange: function () {
if (!this.selectedDates.length) {
return this.opts.onChange('', '', this);
}
var selectedDates = this.selectedDates,
parsedSelected = Datepicker.getParsedDate(selectedDates[0]),
formattedDates = this.formatDate(this.loc.dateFormat, selectedDates[0]),
_this = this,
dates = new Date(parsedSelected.year, parsedSelected.month, parsedSelected.date);
if (this.opts.multipleDates) {
formattedDates = selectedDates.map(function (date) {
return _this.formatDate(_this.opts.dateFormat, date)
}).join(this.opts.multipleDatesSeparator);
// Create new dates array, to separate it from original selectedDates
dates = selectedDates.map(function(date) {
var parsedDate = Datepicker.getParsedDate(date);
return new Date(parsedDate.year, parsedDate.month, parsedDate.date)
})
}
this.opts.onChange(formattedDates, dates, this);
},
next: function () {
var d = this.parsedDate;
switch (this.view) {
case 'days':
this.date = new Date(d.year, d.month + 1, 1);
break;
case 'months':
this.date = new Date(d.year + 1, d.month, 1);
break;
case 'years':
this.date = new Date(d.year + 10, 0, 1);
break;
}
},
prev: function () {
var d = this.parsedDate;
switch (this.view) {
case 'days':
this.date = new Date(d.year, d.month - 1, 1);
break;
case 'months':
this.date = new Date(d.year - 1, d.month, 1);
break;
case 'years':
this.date = new Date(d.year - 10, 0, 1);
break;
}
},
formatDate: function (string, date) {
var result = string,
locale = this.loc,
d = Datepicker.getParsedDate(date);
switch (true) {
case /dd/.test(result):
result = result.replace('dd', d.fullDate);
case /d/.test(result):
result = result.replace('d', d.date);
case /DD/.test(result):
result = result.replace('DD', locale.days[d.day]);
case /D/.test(result):
result = result.replace('D', locale.daysShort[d.day]);
case /mm/.test(result):
result = result.replace('mm', d.fullMonth);
case /m/.test(result):
result = result.replace('m', d.month + 1);
case /MM/.test(result):
result = result.replace('MM', this.loc.months[d.month]);
case /M/.test(result):
result = result.replace('M', locale.monthsShort[d.month]);
case /yyyy/.test(result):
result = result.replace('yyyy', d.year);
case /yy/.test(result):
result = result.replace('yy', d.year.toString().slice(-2));
}
return result;
},
selectDate: function (date) {
var d = this.parsedDate,
newDate = '';
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.views[this.currentView]._render()
},
removeDate: function (date) {
var selected = this.selectedDates,
_this = this;
return selected.some(function (curDate, i) {
if (Datepicker.isSame(curDate, date)) {
selected.splice(i, 1);
_this.views[_this.currentView]._render();
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();
this._triggerOnChange()
},
_isSelected: function (checkDate, cellType) {
return this.selectedDates.some(function (date) {
return Datepicker.isSame(date, checkDate, cellType)
})
},
/**
* 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 () {
var offset = this.$el.offset();
return {
width: this.$el.outerWidth(),
height: this.$el.outerHeight(),
left: offset.left,
top: offset.top
}
},
setPosition: function (position) {
var dims = this._getDimensions(),
pos = position.split(' '),
top, left,
offset = 6,
main = pos[0],
secondary = pos[1];
switch (main) {
case 'bottom':
top = dims.top + dims.height + offset;
break;
}
switch(secondary) {
case 'left':
left = dims.left
}
this.$datepicker.css({
left: left,
top: top
})
},
show: function () {
this.$datepicker.addClass('active');
this.setPosition(this.opts.position);
this.visible = true;
},
hide: function () {
this.$datepicker.removeClass('active');
this.visible = false;
},
_onShowEvent: function () {
if (!this.visible) {
this.show();
}
},
_onMouseDownBody: function (e) {
if (!this.inFocus) {
this.hide();
}
},
_onMouseDownDatepicker: function (e) {
this.inFocus = true;
},
_onMouseUpDatepicker: function (e) {
this.inFocus = false;
this.$el.focus()
},
get parsedDate() {
return Datepicker.getParsedDate(this.date);
},
set date (val) {
this.currentDate = val;
if (this.inited && !this.silent) {
this.views[this.view]._render();
this.nav._render();
}
return val;
},
get date () {
return this.currentDate
},
set view (val) {
this.prevView = this.currentView;
this.currentView = val;
this.viewIndex = this.viewIndexes.indexOf(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();
}
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()
}
};
// 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);
}
});
}
};
})(window, jQuery);