Last active
July 7, 2016 08:11
-
-
Save klapec/96ced1c5018278f38f5a2b5fd8f2cbcf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ;(function (window, $, undefined) { ;(function () { | |
| 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 = { | |
| 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, | |
| dateTimeSeparator: ' ', | |
| timeFormat: '', | |
| minHours: 0, | |
| maxHours: 24, | |
| minMinutes: 0, | |
| maxMinutes: 59, | |
| hoursStep: 1, | |
| minutesStep: 1, | |
| // events | |
| onSelect: '', | |
| 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 = { | |
| 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._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(); | |
| } | |
| 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); | |
| } | |
| 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', | |
| 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 = result.replace(boundary('aa'), dayPeriod); | |
| case /AA/.test(result): | |
| result = result.replace(boundary('AA'), dayPeriod.toUpperCase()); | |
| case /dd/.test(result): | |
| result = result.replace(boundary('dd'), d.fullDate); | |
| case /d/.test(result): | |
| result = result.replace(boundary('d'), d.date); | |
| case /DD/.test(result): | |
| result = result.replace(boundary('DD'), locale.days[d.day]); | |
| case /D/.test(result): | |
| result = result.replace(boundary('D'), locale.daysShort[d.day]); | |
| case /mm/.test(result): | |
| result = result.replace(boundary('mm'), d.fullMonth); | |
| case /m/.test(result): | |
| result = result.replace(boundary('m'), d.month + 1); | |
| case /MM/.test(result): | |
| result = result.replace(boundary('MM'), this.loc.months[d.month]); | |
| case /M/.test(result): | |
| result = result.replace(boundary('M'), locale.monthsShort[d.month]); | |
| case /ii/.test(result): | |
| result = result.replace(boundary('ii'), d.fullMinutes); | |
| case /i/.test(result): | |
| result = result.replace(boundary('i'), d.minutes); | |
| case /hh/.test(result): | |
| result = result.replace(boundary('hh'), fullHours); | |
| case /h/.test(result): | |
| result = result.replace(boundary('h'), hours); | |
| case /yyyy/.test(result): | |
| result = result.replace(boundary('yyyy'), d.year); | |
| case /yyyy1/.test(result): | |
| result = result.replace(boundary('yyyy1'), decade[0]); | |
| case /yyyy2/.test(result): | |
| result = result.replace(boundary('yyyy2'), decade[1]); | |
| case /yy/.test(result): | |
| result = result.replace(boundary('yy'), d.year.toString().slice(-2)); | |
| } | |
| return result; | |
| }, | |
| _getWordBoundaryRegExp: function (sign) { | |
| return new RegExp('\\b(?=[a-zA-Z0-9äöüßÄÖÜ<])' + sign + '(?![>a-zA-Z0-9äöüßÄÖÜ])'); | |
| }, | |
| 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(); | |
| 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.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 () { | |
| this.setPosition(this.opts.position); | |
| this.$datepicker.addClass('active'); | |
| this.visible = true; | |
| }, | |
| hide: function () { | |
| this.$datepicker | |
| .removeClass('active') | |
| .css({ | |
| left: '-100000px' | |
| }); | |
| this.focused = ''; | |
| this.keys = []; | |
| this.inFocus = false; | |
| this.visible = false; | |
| this.$el.blur(); | |
| }, | |
| down: function (date) { | |
| this._changeView(date, 'down'); | |
| }, | |
| up: function (date) { | |
| this._changeView(date, 'up'); | |
| }, | |
| _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(); | |
| } | |
| }, | |
| _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); | |
| } else if (alreadySelected && this.opts.toggleSelected){ | |
| this.removeDate(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) { | |
| console.log('onTimeChange', e); | |
| 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.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; | |
| if (opts.onRenderCell) { | |
| render = opts.onRenderCell(date, type) || {}; | |
| html = render.html ? render.html : html; | |
| classes += render.classes ? ' ' + render.classes : ''; | |
| } | |
| 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 () { | |
| 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 () { | |
| 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); | |
| } | |
| if (alreadySelected && this.opts.range) { | |
| // Add possibility to select same date when range is true | |
| if (dp.selectedDates.length != 2 && !this.opts.toggleSelected || this.opts.toggleSelected) { | |
| dp._trigger('clickCell', selectedDate); | |
| // Change last selected date to be able to change time on last date | |
| dp.lastSelectedDate = alreadySelected; | |
| } | |
| } else if (alreadySelected && this.opts.toggleSelected){ | |
| dp.removeDate(selectedDate); | |
| } | |
| // Change last selected date to be able to change time when clicking on this cell | |
| if (alreadySelected && !this.opts.toggleSelected) { | |
| dp.lastSelectedDate = alreadySelected; | |
| if (dp.opts.timepicker) { | |
| dp.timepicker._setTime(alreadySelected); | |
| dp.timepicker.update(); | |
| } | |
| } | |
| }, | |
| _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 () { | |
| 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, 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'; | |
| } | |
| } | |
| })(); | |
| ;(function () { | |
| var template = '<div class="datepicker--time">' + | |
| '<div class="datepicker--time-current">' + | |
| ' <span class="datepicker--time-current-hours">#{hourValue}</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 change', 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: 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) { | |
| console.log(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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment