PageSourceSearch

https://static.sponsr.ru/js/libs/datepicker/datepicker.js

js sponsr.ru collected 2026-09-24 18:24:05 UTC 77,347 bytes, 2,253 lines download raw bytes

1;(function (window, $, undefined) { ;(function () {
2    var VERSION = '2.2.3',
3        pluginName = 'datepicker',
4        autoInitSelector = '.datepicker-here',
5        $body, $datepickersContainer,
6        containerBuilt = false,
7        baseTemplate = '' +
8            '<div class="datepicker">' +
9            '<i class="datepicker--pointer"></i>' +
10            '<nav class="datepicker--nav"></nav>' +
11            '<div class="datepicker--content"></div>' +
12            '</div>',
13        defaults = {
14            classes: '',
15            inline: false,
16            language: (typeof I18n !== 'undefined') ? I18n.getLocale() : 'ru',
17            startDate: new Date(),
18            firstDay: '',
19            weekends: [6, 0],
20            dateFormat: '',
21            altField: '',
22            altFieldDateFormat: '@',
23            toggleSelected: true,
24            keyboardNav: true,
25
26            position: 'bottom left',
27            offset: 12,
28
29            view: 'days',
30            minView: 'days',
31
32            showOtherMonths: true,
33            selectOtherMonths: true,
34            moveToOtherMonthsOnSelect: true,
35
36            showOtherYears: true,
37            selectOtherYears: true,
38            moveToOtherYearsOnSelect: true,
39
40            minDate: '',
41            maxDate: '',
42            disableNavWhenOutOfRange: true,
43
44            multipleDates: false, // Boolean or Number
45            multipleDatesSeparator: ',',
46            range: false,
47
48            todayButton: false,
49            clearButton: false,
50
51            showEvent: 'focus',
52            autoClose: false,
53
54            // navigation
55            monthsField: 'monthsShort',
56            prevHtml: '<svg><path d="M 17,12 l -5,5 l 5,5"></path></svg>',
57            nextHtml: '<svg><path d="M 14,12 l 5,5 l -5,5"></path></svg>',
58            navTitles: {
59                days: 'MM, <i>yyyy</i>',
60                months: 'yyyy',
61                years: 'yyyy1 - yyyy2'
62            },
63
64            // timepicker
65            timepicker: false,
66            onlyTimepicker: false,
67            dateTimeSeparator: ' ',
68            timeFormat: '',
69            minHours: 0,
70            maxHours: 24,
71            minMinutes: 0,
72            maxMinutes: 59,
73            hoursStep: 1,
74            minutesStep: 1,
75
76            // events
77            onSelect: '',
78            onShow: '',
79            onHide: '',
80            onChangeMonth: '',
81            onChangeYear: '',
82            onChangeDecade: '',
83            onChangeView: '',
84            onRenderCell: ''
85        },
86        hotKeys = {
87            'ctrlRight': [17, 39],
88            'ctrlUp': [17, 38],
89            'ctrlLeft': [17, 37],
90            'ctrlDown': [17, 40],
91            'shiftRight': [16, 39],
92            'shiftUp': [16, 38],
93            'shiftLeft': [16, 37],
94            'shiftDown': [16, 40],
95            'altUp': [18, 38],
96            'altRight': [18, 39],
97            'altLeft': [18, 37],
98            'altDown': [18, 40],
99            'ctrlShiftUp': [16, 17, 38]
100        },
101        datepicker;
102
103    var Datepicker  = function (el, options) {
104        this.el = el;
105        this.$el = $(el);
106
107        this.opts = $.extend(true, {}, defaults, options, this.$el.data());
108
109        if ($body == undefined) {
110            $body = $('body');
111        }
112
113        if (!this.opts.startDate) {
114            this.opts.startDate = new Date();
115        }
116
117        if (this.el.nodeName == 'INPUT') {
118            this.elIsInput = true;
119        }
120
121        if (this.opts.altField) {
122            this.$altField = typeof this.opts.altField == 'string' ? $(this.opts.altField) : this.opts.altField;
123        }
124
125        this.inited = false;
126        this.visible = false;
127        this.silent = false; // Need to prevent unnecessary rendering
128
129        this.currentDate = this.opts.startDate;
130        this.currentView = this.opts.view;
131        this._createShortCuts();
132        this.selectedDates = [];
133        this.views = {};
134        this.keys = [];
135        this.minRange = '';
136        this.maxRange = '';
137        this._prevOnSelectValue = '';
138
139        this.init()
140    };
141
142    datepicker = Datepicker;
143
144    datepicker.prototype = {
145        VERSION: VERSION,
146        viewIndexes: ['days', 'months', 'years'],
147
148        init: function () {
149            if (!containerBuilt && !this.opts.inline && this.elIsInput) {
150                this._buildDatepickersContainer();
151            }
152            this._buildBaseHtml();
153            this._defineLocale(this.opts.language);
154            this._syncWithMinMaxDates();
155
156            if (this.elIsInput) {
157                if (!this.opts.inline) {
158                    // Set extra classes for proper transitions
159                    this._setPositionClasses(this.opts.position);
160                    this._bindEvents()
161                }
162                if (this.opts.keyboardNav && !this.opts.onlyTimepicker) {
163                    this._bindKeyboardEvents();
164                }
165                this.$datepicker.on('mousedown', this._onMouseDownDatepicker.bind(this));
166                this.$datepicker.on('mouseup', this._onMouseUpDatepicker.bind(this));
167            }
168
169            if (this.opts.classes) {
170                this.$datepicker.addClass(this.opts.classes)
171            }
172
173            if (this.opts.timepicker) {
174                this.timepicker = new $.fn.datepicker.Timepicker(this, this.opts);
175                this._bindTimepickerEvents();
176            }
177
178            if (this.opts.onlyTimepicker) {
179                this.$datepicker.addClass('-only-timepicker-');
180            }
181
182            this.views[this.currentView] = new $.fn.datepicker.Body(this, this.currentView, this.opts);
183            this.views[this.currentView].show();
184            this.nav = new $.fn.datepicker.Navigation(this, this.opts);
185            this.view = this.currentView;
186
187            this.$el.on('clickCell.adp', this._onClickCell.bind(this));
188            this.$datepicker.on('mouseenter', '.datepicker--cell', this._onMouseEnterCell.bind(this));
189            this.$datepicker.on('mouseleave', '.datepicker--cell', this._onMouseLeaveCell.bind(this));
190
191            this.inited = true;
192        },
193
194        _createShortCuts: function () {
195            this.minDate = this.opts.minDate ? this.opts.minDate : new Date(-8639999913600000);
196            this.maxDate = this.opts.maxDate ? this.opts.maxDate : new Date(8639999913600000);
197        },
198
199        _bindEvents : function () {
200            this.$el.on(this.opts.showEvent + '.adp', this._onShowEvent.bind(this));
201            this.$el.on('mouseup.adp', this._onMouseUpEl.bind(this));
202            this.$el.on('blur.adp', this._onBlur.bind(this));
203            this.$el.on('keyup.adp', this._onKeyUpGeneral.bind(this));
204            $(window).on('resize.adp', this._onResize.bind(this));
205            $('body').on('mouseup.adp', this._onMouseUpBody.bind(this));
206        },
207
208        _bindKeyboardEvents: function () {
209            this.$el.on('keydown.adp', this._onKeyDown.bind(this));
210            this.$el.on('keyup.adp', this._onKeyUp.bind(this));
211            this.$el.on('hotKey.adp', this._onHotKey.bind(this));
212        },
213
214        _bindTimepickerEvents: function () {
215            this.$el.on('timeChange.adp', this._onTimeChange.bind(this));
216        },
217
218        isWeekend: function (day) {
219            return this.opts.weekends.indexOf(day) !== -1;
220        },
221
222        _defineLocale: function (lang) {
223            if (typeof lang == 'string') {
224                this.loc = $.fn.datepicker.language[lang];
225                if (!this.loc) {
226                    console.warn('Can\'t find language "' + lang + '" in Datepicker.language, will use "ru" instead');
227                    this.loc = $.extend(true, {}, $.fn.datepicker.language.ru)
228                }
229
230                this.loc = $.extend(true, {}, $.fn.datepicker.language.ru, $.fn.datepicker.language[lang])
231            } else {
232                this.loc = $.extend(true, {}, $.fn.datepicker.language.ru, lang)
233            }
234
235            if (this.opts.dateFormat) {
236                this.loc.dateFormat = this.opts.dateFormat
237            }
238
239            if (this.opts.timeFormat) {
240                this.loc.timeFormat = this.opts.timeFormat
241            }
242
243            if (this.opts.firstDay !== '') {
244                this.loc.firstDay = this.opts.firstDay
245            }
246
247            if (this.opts.timepicker) {
248                this.loc.dateFormat = [this.loc.dateFormat, this.loc.timeFormat].join(this.opts.dateTimeSeparator);
249            }
250
251            if (this.opts.onlyTimepicker) {
252                this.loc.dateFormat = this.loc.timeFormat;
253            }
254
255            var boundary = this._getWordBoundaryRegExp;
256            if (this.loc.timeFormat.match(boundary('aa')) ||
257                this.loc.timeFormat.match(boundary('AA'))
258            ) {
259               this.ampm = true;
260            }
261        },
262
263        _buildDatepickersContainer: function () {
264            containerBuilt = true;
265            $body.append('<div class="datepickers-container" id="datepickers-container"></div>');
266            $datepickersContainer = $('#datepickers-container');
267        },
268
269        _buildBaseHtml: function () {
270            var $appendTarget,
271                $inline = $('<div class="datepicker-inline">');
272
273            if(this.el.nodeName == 'INPUT') {
274                if (!this.opts.inline) {
275                    $appendTarget = $datepickersContainer;
276                } else {
277                    $appendTarget = $inline.insertAfter(this.$el)
278                }
279            } else {
280                $appendTarget = $inline.appendTo(this.$el)
281            }
282
283            this.$datepicker = $(baseTemplate).appendTo($appendTarget);
284            this.$content = $('.datepicker--content', this.$datepicker);
285            this.$nav = $('.datepicker--nav', this.$datepicker);
286        },
287
288        _triggerOnChange: function () {
289            if (!this.selectedDates.length) {
290                // Prevent from triggering multiple onSelect callback with same argument (empty string) in IE10-11
291                if (this._prevOnSelectValue === '') return;
292                this._prevOnSelectValue = '';
293                return this.opts.onSelect('', '', this);
294            }
295
296            var selectedDates = this.selectedDates,
297                parsedSelected = datepicker.getParsedDate(selectedDates[0]),
298                formattedDates,
299                _this = this,
300                dates = new Date(
301                    parsedSelected.year,
302                    parsedSelected.month,
303                    parsedSelected.date,
304                    parsedSelected.hours,
305                    parsedSelected.minutes
306                );
307
308                formattedDates = selectedDates.map(function (date) {
309                    return _this.formatDate(_this.loc.dateFormat, date)
310                }).join(this.opts.multipleDatesSeparator);
311
312            // Create new dates array, to separate it from original selectedDates
313            if (this.opts.multipleDates || this.opts.range) {
314                dates = selectedDates.map(function(date) {
315                    var parsedDate = datepicker.getParsedDate(date);
316                    return new Date(
317                        parsedDate.year,
318                        parsedDate.month,
319                        parsedDate.date,
320                        parsedDate.hours,
321                        parsedDate.minutes
322                    );
323                })
324            }
325
326            this._prevOnSelectValue = formattedDates;
327            this.opts.onSelect(formattedDates, dates, this);
328        },
329
330        next: function () {
331            var d = this.parsedDate,
332                o = this.opts;
333            switch (this.view) {
334                case 'days':
335                    this.date = new Date(d.year, d.month + 1, 1);
336                    if (o.onChangeMonth) o.onChangeMonth(this.parsedDate.month, this.parsedDate.year);
337                    break;
338                case 'months':
339                    this.date = new Date(d.year + 1, d.month, 1);
340                    if (o.onChangeYear) o.onChangeYear(this.parsedDate.year);
341                    break;
342                case 'years':
343                    this.date = new Date(d.year + 10, 0, 1);
344                    if (o.onChangeDecade) o.onChangeDecade(this.curDecade);
345                    break;
346            }
347        },
348
349        prev: function () {
350            var d = this.parsedDate,
351                o = this.opts;
352            switch (this.view) {
353                case 'days':
354                    this.date = new Date(d.year, d.month - 1, 1);
355                    if (o.onChangeMonth) o.onChangeMonth(this.parsedDate.month, this.parsedDate.year);
356                    break;
357                case 'months':
358                    this.date = new Date(d.year - 1, d.month, 1);
359                    if (o.onChangeYear) o.onChangeYear(this.parsedDate.year);
360                    break;
361                case 'years':
362                    this.date = new Date(d.year - 10, 0, 1);
363                    if (o.onChangeDecade) o.onChangeDecade(this.curDecade);
364                    break;
365            }
366        },
367
368        formatDate: function (string, date) {
369            date = date || this.date;
370            var result = string,
371                boundary = this._getWordBoundaryRegExp,
372                locale = this.loc,
373                leadingZero = datepicker.getLeadingZeroNum,
374                decade = datepicker.getDecade(date),
375                d = datepicker.getParsedDate(date),
376                fullHours = d.fullHours,
377                hours = d.hours,
378                ampm = string.match(boundary('aa')) || string.match(boundary('AA')),
379                dayPeriod = 'am',
380                replacer = this._replacer,
381                validHours;
382
383            if (this.opts.timepicker && this.timepicker && ampm) {
384                validHours = this.timepicker._getValidHoursFromDate(date, ampm);
385                fullHours = leadingZero(validHours.hours);
386                hours = validHours.hours;
387                dayPeriod = validHours.dayPeriod;
388            }
389
390            switch (true) {
391                case /@/.test(result):
392                    result = result.replace(/@/, date.getTime());
393                case /aa/.test(result):
394                    result = replacer(result, boundary('aa'), dayPeriod);
395                case /AA/.test(result):
396                    result = replacer(result, boundary('AA'), dayPeriod.toUpperCase());
397                case /dd/.test(result):
398                    result = replacer(result, boundary('dd'), d.fullDate);
399                case /d/.test(result):
400                    result = replacer(result, boundary('d'), d.date);
401                case /DD/.test(result):
402                    result = replacer(result, boundary('DD'), locale.days[d.day]);
403                case /D/.test(result):
404                    result = replacer(result, boundary('D'), locale.daysShort[d.day]);
405                case /mm/.test(result):
406                    result = replacer(result, boundary('mm'), d.fullMonth);
407                case /m/.test(result):
408                    result = replacer(result, boundary('m'), d.month + 1);
409                case /MM/.test(result):
410                    result = replacer(result, boundary('MM'), this.loc.months[d.month]);
411                case /M/.test(result):
412                    result = replacer(result, boundary('M'), locale.monthsShort[d.month]);
413                case /ii/.test(result):
414                    result = replacer(result, boundary('ii'), d.fullMinutes);
415                case /i/.test(result):
416                    result = replacer(result, boundary('i'), d.minutes);
417                case /hh/.test(result):
418                    result = replacer(result, boundary('hh'), fullHours);
419                case /h/.test(result):
420                    result = replacer(result, boundary('h'), hours);
421                case /yyyy/.test(result):
422                    result = replacer(result, boundary('yyyy'), d.year);
423                case /yyyy1/.test(result):
424                    result = replacer(result, boundary('yyyy1'), decade[0]);
425                case /yyyy2/.test(result):
426                    result = replacer(result, boundary('yyyy2'), decade[1]);
427                case /yy/.test(result):
428                    result = replacer(result, boundary('yy'), d.year.toString().slice(-2));
429            }
430
431            return result;
432        },
433
434        _replacer: function (str, reg, data) {
435            return str.replace(reg, function (match, p1,p2,p3) {
436                return p1 + data + p3;
437            })
438        },
439
440        _getWordBoundaryRegExp: function (sign) {
441            var symbols = '\\s|\\.|-|/|\\\\|,|\\$|\\!|\\?|:|;';
442
443            return new RegExp('(^|>|' + symbols + ')(' + sign + ')($|<|' + symbols + ')', 'g');
444        },
445
446
447        selectDate: function (date) {
448            var _this = this,
449                opts = _this.opts,
450                d = _this.parsedDate,
451                selectedDates = _this.selectedDates,
452                len = selectedDates.length,
453                newDate = '';
454
455            if (Array.isArray(date)) {
456                date.forEach(function (d) {
457                    _this.selectDate(d)
458                });
459                return;
460            }
461
462            if (!(date instanceof Date)) return;
463
464            this.lastSelectedDate = date;
465
466            // Set new time values from Date
467            if (this.timepicker) {
468                this.timepicker._setTime(date);
469            }
470
471            // On this step timepicker will set valid values in it's instance
472            _this._trigger('selectDate', date);
473
474            // Set correct time values after timepicker's validation
475            // Prevent from setting hours or minutes which values are lesser then `min` value or
476            // greater then `max` value
477            if (this.timepicker) {
478                date.setHours(this.timepicker.hours);
479                date.setMinutes(this.timepicker.minutes)
480            }
481
482            if (_this.view == 'days') {
483                if (date.getMonth() != d.month && opts.moveToOtherMonthsOnSelect) {
484                    newDate = new Date(date.getFullYear(), date.getMonth(), 1);
485                }
486            }
487
488            if (_this.view == 'years') {
489                if (date.getFullYear() != d.year && opts.moveToOtherYearsOnSelect) {
490                    newDate = new Date(date.getFullYear(), 0, 1);
491                }
492            }
493
494            if (newDate) {
495                _this.silent = true;
496                _this.date = newDate;
497                _this.silent = false;
498                _this.nav._render()
499            }
500
501            if (opts.multipleDates && !opts.range) { // Set priority to range functionality
502                if (len === opts.multipleDates) return;
503                if (!_this._isSelected(date)) {
504                    _this.selectedDates.push(date);
505                }
506            } else if (opts.range) {
507                if (len == 2) {
508                    _this.selectedDates = [date];
509                    _this.minRange = date;
510                    _this.maxRange = '';
511                } else if (len == 1) {
512                    _this.selectedDates.push(date);
513                    if (!_this.maxRange){
514                        _this.maxRange = date;
515                    } else {
516                        _this.minRange = date;
517                    }
518                    // Swap dates if they were selected via dp.selectDate() and second date was smaller then first
519                    if (datepicker.bigger(_this.maxRange, _this.minRange)) {
520                        _this.maxRange = _this.minRange;
521                        _this.minRange = date;
522                    }
523                    _this.selectedDates = [_this.minRange, _this.maxRange]
524
525                } else {
526                    _this.selectedDates = [date];
527                    _this.minRange = date;
528                }
529            } else {
530                _this.selectedDates = [date];
531            }
532
533            _this._setInputValue();
534
535            if (opts.onSelect) {
536                _this._triggerOnChange();
537            }
538
539            if (opts.autoClose && !this.timepickerIsActive) {
540                if (!opts.multipleDates && !opts.range) {
541                    _this.hide();
542                } else if (opts.range && _this.selectedDates.length == 2) {
543                    _this.hide();
544                }
545            }
546
547            _this.views[this.currentView]._render()
548        },
549
550        removeDate: function (date) {
551            var selected = this.selectedDates,
552                _this = this;
553
554            if (!(date instanceof Date)) return;
555
556            return selected.some(function (curDate, i) {
557                if (datepicker.isSame(curDate, date)) {
558                    selected.splice(i, 1);
559
560                    if (!_this.selectedDates.length) {
561                        _this.minRange = '';
562                        _this.maxRange = '';
563                        _this.lastSelectedDate = '';
564                    } else {
565                        _this.lastSelectedDate = _this.selectedDates[_this.selectedDates.length - 1];
566                    }
567
568                    _this.views[_this.currentView]._render();
569                    _this._setInputValue();
570
571                    if (_this.opts.onSelect) {
572                        _this._triggerOnChange();
573                    }
574
575                    return true
576                }
577            })
578        },
579
580        today: function () {
581            this.silent = true;
582            this.view = this.opts.minView;
583            this.silent = false;
584            this.date = new Date();
585
586            if (this.opts.todayButton instanceof Date) {
587                this.selectDate(this.opts.todayButton)
588            }
589        },
590
591        clear: function () {
592            this.selectedDates = [];
593            this.minRange = '';
594            this.maxRange = '';
595            this.views[this.currentView]._render();
596            this._setInputValue();
597            if (this.opts.onSelect) {
598                this._triggerOnChange()
599            }
600        },
601
602        /**
603         * Updates datepicker options
604         * @param {String|Object} param - parameter's name to update. If object then it will extend current options
605         * @param {String|Number|Object} [value] - new param value
606         */
607        update: function (param, value) {
608            var len = arguments.length,
609                lastSelectedDate = this.lastSelectedDate;
610
611            if (len == 2) {
612                this.opts[param] = value;
613            } else if (len == 1 && typeof param == 'object') {
614                this.opts = $.extend(true, this.opts, param)
615            }
616
617            this._createShortCuts();
618            this._syncWithMinMaxDates();
619            this._defineLocale(this.opts.language);
620            this.nav._addButtonsIfNeed();
621            if (!this.opts.onlyTimepicker) this.nav._render();
622            this.views[this.currentView]._render();
623
624            if (this.elIsInput && !this.opts.inline) {
625                this._setPositionClasses(this.opts.position);
626                if (this.visible) {
627                    this.setPosition(this.opts.position)
628                }
629            }
630
631            if (this.opts.classes) {
632                this.$datepicker.addClass(this.opts.classes)
633            }
634
635            if (this.opts.onlyTimepicker) {
636                this.$datepicker.addClass('-only-timepicker-');
637            }
638
639            if (this.opts.timepicker) {
640                if (lastSelectedDate) this.timepicker._handleDate(lastSelectedDate);
641                this.timepicker._updateRanges();
642                this.timepicker._updateCurrentTime();
643                // Change hours and minutes if it's values have been changed through min/max hours/minutes
644                if (lastSelectedDate) {
645                    lastSelectedDate.setHours(this.timepicker.hours);
646                    lastSelectedDate.setMinutes(this.timepicker.minutes);
647                }
648            }
649
650            this._setInputValue();
651
652            return this;
653        },
654
655        _syncWithMinMaxDates: function () {
656            var curTime = this.date.getTime();
657            this.silent = true;
658            if (this.minTime > curTime) {
659                this.date = this.minDate;
660            }
661
662            if (this.maxTime < curTime) {
663                this.date = this.maxDate;
664            }
665            this.silent = false;
666        },
667
668        _isSelected: function (checkDate, cellType) {
669            var res = false;
670            this.selectedDates.some(function (date) {
671                if (datepicker.isSame(date, checkDate, cellType)) {
672                    res = date;
673                    return true;
674                }
675            });
676            return res;
677        },
678
679        _setInputValue: function () {
680            var _this = this,
681                opts = _this.opts,
682                format = _this.loc.dateFormat,
683                altFormat = opts.altFieldDateFormat,
684                value = _this.selectedDates.map(function (date) {
685                    return _this.formatDate(format, date)
686                }),
687                altValues;
688
689            if (opts.altField && _this.$altField.length) {
690                altValues = this.selectedDates.map(function (date) {
691                    return _this.formatDate(altFormat, date)
692                });
693                altValues = altValues.join(this.opts.multipleDatesSeparator);
694                this.$altField.val(altValues);
695            }
696
697            value = value.join(this.opts.multipleDatesSeparator);
698
699            this.$el.val(value)
700        },
701
702        /**
703         * Check if date is between minDate and maxDate
704         * @param date {object} - date object
705         * @param type {string} - cell type
706         * @returns {boolean}
707         * @private
708         */
709        _isInRange: function (date, type) {
710            var time = date.getTime(),
711                d = datepicker.getParsedDate(date),
712                min = datepicker.getParsedDate(this.minDate),
713                max = datepicker.getParsedDate(this.maxDate),
714                dMinTime = new Date(d.year, d.month, min.date).getTime(),
715                dMaxTime = new Date(d.year, d.month, max.date).getTime(),
716                types = {
717                    day: time >= this.minTime && time <= this.maxTime,
718                    month: dMinTime >= this.minTime && dMaxTime <= this.maxTime,
719                    year: d.year >= min.year && d.year <= max.year
720                };
721            return type ? types[type] : types.day
722        },
723
724        _getDimensions: function ($el) {
725            var offset = $el.offset();
726
727            return {
728                width: $el.outerWidth(),
729                height: $el.outerHeight(),
730                left: offset.left,
731                top: offset.top
732            }
733        },
734
735        _getDateFromCell: function (cell) {
736            var curDate = this.parsedDate,
737                year = cell.data('year') || curDate.year,
738                month = cell.data('month') == undefined ? curDate.month : cell.data('month'),
739                date = cell.data('date') || 1;
740
741            return new Date(year, month, date);
742        },
743
744        _setPositionClasses: function (pos) {
745            pos = pos.split(' ');
746            var main = pos[0],
747                sec = pos[1],
748                classes = 'datepicker -' + main + '-' + sec + '- -from-' + main + '-';
749
750            if (this.visible) classes += ' active';
751
752            this.$datepicker
753                .removeAttr('class')
754                .addClass(classes);
755        },
756
757        setPosition: function (position) {
758            position = position || this.opts.position;
759
760            var dims = this._getDimensions(this.$el),
761                selfDims = this._getDimensions(this.$datepicker),
762                pos = position.split(' '),
763                top, left,
764                offset = this.opts.offset,
765                main = pos[0],
766                secondary = pos[1];
767
768            switch (main) {
769                case 'top':
770                    top = dims.top - selfDims.height - offset;
771                    break;
772                case 'right':
773                    left = dims.left + dims.width + offset;
774                    break;
775                case 'bottom':
776                    top = dims.top + dims.height + offset;
777                    break;
778                case 'left':
779                    left = dims.left - selfDims.width - offset;
780                    break;
781            }
782
783            switch(secondary) {
784                case 'top':
785                    top = dims.top;
786                    break;
787                case 'right':
788                    left = dims.left + dims.width - selfDims.width;
789                    break;
790                case 'bottom':
791                    top = dims.top + dims.height - selfDims.height;
792                    break;
793                case 'left':
794                    left = dims.left;
795                    break;
796                case 'center':
797                    if (/left|right/.test(main)) {
798                        top = dims.top + dims.height/2 - selfDims.height/2;
799                    } else {
800                        left = dims.left + dims.width/2 - selfDims.width/2;
801                    }
802            }
803
804            this.$datepicker
805                .css({
806                    left: left,
807                    top: top
808                })
809        },
810
811        show: function () {
812            var onShow = this.opts.onShow;
813
814            this.setPosition(this.opts.position);
815            this.$datepicker.addClass('active');
816            this.visible = true;
817
818            if (onShow) {
819                this._bindVisionEvents(onShow)
820            }
821        },
822
823        hide: function () {
824            var onHide = this.opts.onHide;
825
826            this.$datepicker
827                .removeClass('active')
828                .css({
829                    left: '-100000px'
830                });
831
832            this.focused = '';
833            this.keys = [];
834
835            this.inFocus = false;
836            this.visible = false;
837            this.$el.blur();
838
839            if (onHide) {
840                this._bindVisionEvents(onHide)
841            }
842        },
843
844        down: function (date) {
845            this._changeView(date, 'down');
846        },
847
848        up: function (date) {
849            this._changeView(date, 'up');
850        },
851
852        _bindVisionEvents: function (event) {
853            this.$datepicker.off('transitionend.dp');
854            event(this, false);
855            this.$datepicker.one('transitionend.dp', event.bind(this, this, true))
856        },
857
858        _changeView: function (date, dir) {
859            date = date || this.focused || this.date;
860
861            var nextView = dir == 'up' ? this.viewIndex + 1 : this.viewIndex - 1;
862            if (nextView > 2) nextView = 2;
863            if (nextView < 0) nextView = 0;
864
865            this.silent = true;
866            this.date = new Date(date.getFullYear(), date.getMonth(), 1);
867            this.silent = false;
868            this.view = this.viewIndexes[nextView];
869
870        },
871
872        _handleHotKey: function (key) {
873            var date = datepicker.getParsedDate(this._getFocusedDate()),
874                focusedParsed,
875                o = this.opts,
876                newDate,
877                totalDaysInNextMonth,
878                monthChanged = false,
879                yearChanged = false,
880                decadeChanged = false,
881                y = date.year,
882                m = date.month,
883                d = date.date;
884
885            switch (key) {
886                case 'ctrlRight':
887                case 'ctrlUp':
888                    m += 1;
889                    monthChanged = true;
890                    break;
891                case 'ctrlLeft':
892                case 'ctrlDown':
893                    m -= 1;
894                    monthChanged = true;
895                    break;
896                case 'shiftRight':
897                case 'shiftUp':
898                    yearChanged = true;
899                    y += 1;
900                    break;
901                case 'shiftLeft':
902                case 'shiftDown':
903                    yearChanged = true;
904                    y -= 1;
905                    break;
906                case 'altRight':
907                case 'altUp':
908                    decadeChanged = true;
909                    y += 10;
910                    break;
911                case 'altLeft':
912                case 'altDown':
913                    decadeChanged = true;
914                    y -= 10;
915                    break;
916                case 'ctrlShiftUp':
917                    this.up();
918                    break;
919            }
920
921            totalDaysInNextMonth = datepicker.getDaysCount(new Date(y,m));
922            newDate = new Date(y,m,d);
923
924            // If next month has less days than current, set date to total days in that month
925            if (totalDaysInNextMonth < d) d = totalDaysInNextMonth;
926
927            // Check if newDate is in valid range
928            if (newDate.getTime() < this.minTime) {
929                newDate = this.minDate;
930            } else if (newDate.getTime() > this.maxTime) {
931                newDate = this.maxDate;
932            }
933
934            this.focused = newDate;
935
936            focusedParsed = datepicker.getParsedDate(newDate);
937            if (monthChanged && o.onChangeMonth) {
938                o.onChangeMonth(focusedParsed.month, focusedParsed.year)
939            }
940            if (yearChanged && o.onChangeYear) {
941                o.onChangeYear(focusedParsed.year)
942            }
943            if (decadeChanged && o.onChangeDecade) {
944                o.onChangeDecade(this.curDecade)
945            }
946        },
947
948        _registerKey: function (key) {
949            var exists = this.keys.some(function (curKey) {
950                return curKey == key;
951            });
952
953            if (!exists) {
954                this.keys.push(key)
955            }
956        },
957
958        _unRegisterKey: function (key) {
959            var index = this.keys.indexOf(key);
960
961            this.keys.splice(index, 1);
962        },
963
964        _isHotKeyPressed: function () {
965            var currentHotKey,
966                found = false,
967                _this = this,
968                pressedKeys = this.keys.sort();
969
970            for (var hotKey in hotKeys) {
971                currentHotKey = hotKeys[hotKey];
972                if (pressedKeys.length != currentHotKey.length) continue;
973
974                if (currentHotKey.every(function (key, i) { return key == pressedKeys[i]})) {
975                    _this._trigger('hotKey', hotKey);
976                    found = true;
977                }
978            }
979
980            return found;
981        },
982
983        _trigger: function (event, args) {
984            this.$el.trigger(event, args)
985        },
986
987        _focusNextCell: function (keyCode, type) {
988            type = type || this.cellType;
989
990            var date = datepicker.getParsedDate(this._getFocusedDate()),
991                y = date.year,
992                m = date.month,
993                d = date.date;
994
995            if (this._isHotKeyPressed()){
996                return;
997            }
998
999            switch(keyCode) {
1000                case 37: // left
1001                    type == 'day' ? (d -= 1) : '';
1002                    type == 'month' ? (m -= 1) : '';
1003                    type == 'year' ? (y -= 1) : '';
1004                    break;
1005                case 38: // up
1006                    type == 'day' ? (d -= 7) : '';
1007                    type == 'month' ? (m -= 3) : '';
1008                    type == 'year' ? (y -= 4) : '';
1009                    break;
1010                case 39: // right
1011                    type == 'day' ? (d += 1) : '';
1012                    type == 'month' ? (m += 1) : '';
1013                    type == 'year' ? (y += 1) : '';
1014                    break;
1015                case 40: // down
1016                    type == 'day' ? (d += 7) : '';
1017                    type == 'month' ? (m += 3) : '';
1018                    type == 'year' ? (y += 4) : '';
1019                    break;
1020            }
1021
1022            var nd = new Date(y,m,d);
1023            if (nd.getTime() < this.minTime) {
1024                nd = this.minDate;
1025            } else if (nd.getTime() > this.maxTime) {
1026                nd = this.maxDate;
1027            }
1028
1029            this.focused = nd;
1030
1031        },
1032
1033        _getFocusedDate: function () {
1034            var focused  = this.focused || this.selectedDates[this.selectedDates.length - 1],
1035                d = this.parsedDate;
1036
1037            if (!focused) {
1038                switch (this.view) {
1039                    case 'days':
1040                        focused = new Date(d.year, d.month, new Date().getDate());
1041                        break;
1042                    case 'months':
1043                        focused = new Date(d.year, d.month, 1);
1044                        break;
1045                    case 'years':
1046                        focused = new Date(d.year, 0, 1);
1047                        break;
1048                }
1049            }
1050
1051            return focused;
1052        },
1053
1054        _getCell: function (date, type) {
1055            type = type || this.cellType;
1056
1057            var d = datepicker.getParsedDate(date),
1058                selector = '.datepicker--cell[data-year="' + d.year + '"]',
1059                $cell;
1060
1061            switch (type) {
1062                case 'month':
1063                    selector = '[data-month="' + d.month + '"]';
1064                    break;
1065                case 'day':
1066                    selector += '[data-month="' + d.month + '"][data-date="' + d.date + '"]';
1067                    break;
1068            }
1069            $cell = this.views[this.currentView].$el.find(selector);
1070
1071            return $cell.length ? $cell : $('');
1072        },
1073
1074        destroy: function () {
1075            var _this = this;
1076            _this.$el
1077                .off('.adp')
1078                .data('datepicker', '');
1079
1080            _this.selectedDates = [];
1081            _this.focused = '';
1082            _this.views = {};
1083            _this.keys = [];
1084            _this.minRange = '';
1085            _this.maxRange = '';
1086
1087            if (_this.opts.inline || !_this.elIsInput) {
1088                _this.$datepicker.closest('.datepicker-inline').remove();
1089            } else {
1090                _this.$datepicker.remove();
1091            }
1092        },
1093
1094        _handleAlreadySelectedDates: function (alreadySelected, selectedDate) {
1095            if (this.opts.range) {
1096                if (!this.opts.toggleSelected) {
1097                    // Add possibility to select same date when range is true
1098                    if (this.selectedDates.length != 2) {
1099                        this._trigger('clickCell', selectedDate);
1100                    }
1101                } else {
1102                    this.removeDate(selectedDate);
1103                }
1104            } else if (this.opts.toggleSelected){
1105                this.removeDate(selectedDate);
1106            }
1107
1108            // Change last selected date to be able to change time when clicking on this cell
1109            if (!this.opts.toggleSelected) {
1110                this.lastSelectedDate = alreadySelected;
1111                if (this.opts.timepicker) {
1112                    this.timepicker._setTime(alreadySelected);
1113                    this.timepicker.update();
1114                }
1115            }
1116        },
1117
1118        _onShowEvent: function (e) {
1119            if (!this.visible) {
1120                this.show();
1121            }
1122        },
1123
1124        _onBlur: function () {
1125            if (!this.inFocus && this.visible) {
1126                this.hide();
1127            }
1128        },
1129
1130        _onMouseDownDatepicker: function (e) {
1131            this.inFocus = true;
1132        },
1133
1134        _onMouseUpDatepicker: function (e) {
1135            this.inFocus = false;
1136            e.originalEvent.inFocus = true;
1137            if (!e.originalEvent.timepickerFocus) this.$el.focus();
1138        },
1139
1140        _onKeyUpGeneral: function (e) {
1141            var val = this.$el.val();
1142
1143            if (!val) {
1144                this.clear();
1145            }
1146        },
1147
1148        _onResize: function () {
1149            if (this.visible) {
1150                this.setPosition();
1151            }
1152        },
1153
1154        _onMouseUpBody: function (e) {
1155            if (e.originalEvent.inFocus) return;
1156
1157            if (this.visible && !this.inFocus) {
1158                this.hide();
1159            }
1160        },
1161
1162        _onMouseUpEl: function (e) {
1163            e.originalEvent.inFocus = true;
1164            setTimeout(this._onKeyUpGeneral.bind(this),4);
1165        },
1166
1167        _onKeyDown: function (e) {
1168            var code = e.which;
1169            this._registerKey(code);
1170
1171            // Arrows
1172            if (code >= 37 && code <= 40) {
1173                e.preventDefault();
1174                this._focusNextCell(code);
1175            }
1176
1177            // Enter
1178            if (code == 13) {
1179                if (this.focused) {
1180                    if (this._getCell(this.focused).hasClass('-disabled-')) return;
1181                    if (this.view != this.opts.minView) {
1182                        this.down()
1183                    } else {
1184                        var alreadySelected = this._isSelected(this.focused, this.cellType);
1185
1186                        if (!alreadySelected) {
1187                            if (this.timepicker) {
1188                                this.focused.setHours(this.timepicker.hours);
1189                                this.focused.setMinutes(this.timepicker.minutes);
1190                            }
1191                            this.selectDate(this.focused);
1192                            return;
1193                        }
1194                        this._handleAlreadySelectedDates(alreadySelected, this.focused)
1195                    }
1196                }
1197            }
1198
1199            // Esc
1200            if (code == 27) {
1201                this.hide();
1202            }
1203        },
1204
1205        _onKeyUp: function (e) {
1206            var code = e.which;
1207            this._unRegisterKey(code);
1208        },
1209
1210        _onHotKey: function (e, hotKey) {
1211            this._handleHotKey(hotKey);
1212        },
1213
1214        _onMouseEnterCell: function (e) {
1215            var $cell = $(e.target).closest('.datepicker--cell'),
1216                date = this._getDateFromCell($cell);
1217
1218            // Prevent from unnecessary rendering and setting new currentDate
1219            this.silent = true;
1220
1221            if (this.focused) {
1222                this.focused = ''
1223            }
1224
1225            $cell.addClass('-focus-');
1226
1227            this.focused = date;
1228            this.silent = false;
1229
1230            if (this.opts.range && this.selectedDates.length == 1) {
1231                this.minRange = this.selectedDates[0];
1232                this.maxRange = '';
1233                if (datepicker.less(this.minRange, this.focused)) {
1234                    this.maxRange = this.minRange;
1235                    this.minRange = '';
1236                }
1237                this.views[this.currentView]._update();
1238            }
1239        },
1240
1241        _onMouseLeaveCell: function (e) {
1242            var $cell = $(e.target).closest('.datepicker--cell');
1243
1244            $cell.removeClass('-focus-');
1245
1246            this.silent = true;
1247            this.focused = '';
1248            this.silent = false;
1249        },
1250
1251        _onTimeChange: function (e, h, m) {
1252            var date = new Date(),
1253                selectedDates = this.selectedDates,
1254                selected = false;
1255
1256            if (selectedDates.length) {
1257                selected = true;
1258                date = this.lastSelectedDate;
1259            }
1260
1261            date.setHours(h);
1262            date.setMinutes(m);
1263
1264            if (!selected && !this._getCell(date).hasClass('-disabled-')) {
1265                this.selectDate(date);
1266            } else {
1267                this._setInputValue();
1268                if (this.opts.onSelect) {
1269                    this._triggerOnChange();
1270                }
1271            }
1272        },
1273
1274        _onClickCell: function (e, date) {
1275            if (this.timepicker) {
1276                date.setHours(this.timepicker.hours);
1277                date.setMinutes(this.timepicker.minutes);
1278            }
1279            this.selectDate(date);
1280        },
1281
1282        set focused(val) {
1283            if (!val && this.focused) {
1284                var $cell = this._getCell(this.focused);
1285
1286                if ($cell.length) {
1287                    $cell.removeClass('-focus-')
1288                }
1289            }
1290            this._focused = val;
1291            if (this.opts.range && this.selectedDates.length == 1) {
1292                this.minRange = this.selectedDates[0];
1293                this.maxRange = '';
1294                if (datepicker.less(this.minRange, this._focused)) {
1295                    this.maxRange = this.minRange;
1296                    this.minRange = '';
1297                }
1298            }
1299            if (this.silent) return;
1300            this.date = val;
1301        },
1302
1303        get focused() {
1304            return this._focused;
1305        },
1306
1307        get parsedDate() {
1308            return datepicker.getParsedDate(this.date);
1309        },
1310
1311        set date (val) {
1312            if (!(val instanceof Date)) return;
1313
1314            this.currentDate = val;
1315
1316            if (this.inited && !this.silent) {
1317                this.views[this.view]._render();
1318                this.nav._render();
1319                if (this.visible && this.elIsInput) {
1320                    this.setPosition();
1321                }
1322            }
1323            return val;
1324        },
1325
1326        get date () {
1327            return this.currentDate
1328        },
1329
1330        set view (val) {
1331            this.viewIndex = this.viewIndexes.indexOf(val);
1332
1333            if (this.viewIndex < 0) {
1334                return;
1335            }
1336
1337            this.prevView = this.currentView;
1338            this.currentView = val;
1339
1340            if (this.inited) {
1341                if (!this.views[val]) {
1342                    this.views[val] = new  $.fn.datepicker.Body(this, val, this.opts)
1343                } else {
1344                    this.views[val]._render();
1345                }
1346
1347                this.views[this.prevView].hide();
1348                this.views[val].show();
1349                this.nav._render();
1350
1351                if (this.opts.onChangeView) {
1352                    this.opts.onChangeView(val)
1353                }
1354                if (this.elIsInput && this.visible) this.setPosition();
1355            }
1356
1357            return val
1358        },
1359
1360        get view() {
1361            return this.currentView;
1362        },
1363
1364        get cellType() {
1365            return this.view.substring(0, this.view.length - 1)
1366        },
1367
1368        get minTime() {
1369            var min = datepicker.getParsedDate(this.minDate);
1370            return new Date(min.year, min.month, min.date).getTime()
1371        },
1372
1373        get maxTime() {
1374            var max = datepicker.getParsedDate(this.maxDate);
1375            return new Date(max.year, max.month, max.date).getTime()
1376        },
1377
1378        get curDecade() {
1379            return datepicker.getDecade(this.date)
1380        }
1381    };
1382
1383    //  Utils
1384    // -------------------------------------------------
1385
1386    datepicker.getDaysCount = function (date) {
1387        return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
1388    };
1389
1390    datepicker.getParsedDate = function (date) {
1391        return {
1392            year: date.getFullYear(),
1393            month: date.getMonth(),
1394            fullMonth: (date.getMonth() + 1) < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1, // One based
1395            date: date.getDate(),
1396            fullDate: date.getDate() < 10 ? '0' + date.getDate() : date.getDate(),
1397            day: date.getDay(),
1398            hours: date.getHours(),
1399            fullHours:  date.getHours() < 10 ? '0' + date.getHours() :  date.getHours() ,
1400            minutes: date.getMinutes(),
1401            fullMinutes:  date.getMinutes() < 10 ? '0' + date.getMinutes() :  date.getMinutes()
1402        }
1403    };
1404
1405    datepicker.getDecade = function (date) {
1406        var firstYear = Math.floor(date.getFullYear() / 10) * 10;
1407
1408        return [firstYear, firstYear + 9];
1409    };
1410
1411    datepicker.template = function (str, data) {
1412        return str.replace(/#\{([\w]+)\}/g, function (source, match) {
1413            if (data[match] || data[match] === 0) {
1414                return data[match]
1415            }
1416        });
1417    };
1418
1419    datepicker.isSame = function (date1, date2, type) {
1420        if (!date1 || !date2) return false;
1421        var d1 = datepicker.getParsedDate(date1),
1422            d2 = datepicker.getParsedDate(date2),
1423            _type = type ? type : 'day',
1424
1425            conditions = {
1426                day: d1.date == d2.date && d1.month == d2.month && d1.year == d2.year,
1427                month: d1.month == d2.month && d1.year == d2.year,
1428                year: d1.year == d2.year
1429            };
1430
1431        return conditions[_type];
1432    };
1433
1434    datepicker.less = function (dateCompareTo, date, type) {
1435        if (!dateCompareTo || !date) return false;
1436        return date.getTime() < dateCompareTo.getTime();
1437    };
1438
1439    datepicker.bigger = function (dateCompareTo, date, type) {
1440        if (!dateCompareTo || !date) return false;
1441        return date.getTime() > dateCompareTo.getTime();
1442    };
1443
1444    datepicker.getLeadingZeroNum = function (num) {
1445        return parseInt(num) < 10 ? '0' + num : num;
1446    };
1447
1448    /**
1449     * Returns copy of date with hours and minutes equals to 0
1450     * @param date {Date}
1451     */
1452    datepicker.resetTime = function (date) {
1453        if (typeof date != 'object') return;
1454        date = datepicker.getParsedDate(date);
1455        return new Date(date.year, date.month, date.date)
1456    };
1457
1458    $.fn.datepicker = function ( options ) {
1459        return this.each(function () {
1460            if (!$.data(this, pluginName)) {
1461                $.data(this,  pluginName,
1462                    new Datepicker( this, options ));
1463            } else {
1464                var _this = $.data(this, pluginName);
1465
1466                _this.opts = $.extend(true, _this.opts, options);
1467                _this.update();
1468            }
1469        });
1470    };
1471
1472    $.fn.datepicker.Constructor = Datepicker;
1473
1474    var language = {};
1475    
1476    if (typeof I18n !== 'undefined') {
1477        language = I18n.getTranslates('common.datepicker');
1478    }
1479
1480    if (!$.isEmptyObject(language)) {
1481        var __defParams = {
1482            dateFormat: 'dd.mm.yyyy',
1483            timeFormat: 'hh:ii',
1484            firstDay: 1
1485        };
1486    
1487        $.fn.datepicker.language = {};
1488        $.fn.datepicker.language[I18n.getLocale()] = $.extend(true, __defParams, language);
1489    } else {
1490        $.fn.datepicker.language = {
1491            ru: {
1492                days: ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'],
1493                daysShort: ['Вос','Пон','Вто','Сре','Чет','Пят','Суб'],
1494                daysMin: ['Вс','Пн','Вт','Ср','Чт','Пт','Сб'],
1495                months: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
1496                monthsShort: ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'],
1497                today: 'Сегодня',
1498                clear: 'Очистить',
1499                dateFormat: 'dd.mm.yyyy',
1500                timeFormat: 'hh:ii',
1501                firstDay: 1
1502            }
1503        };
1504    }
1505
1506    $(function () {
1507        $(autoInitSelector).datepicker();
1508    })
1509
1510})();
1511
1512;(function () {
1513    var templates = {
1514        days:'' +
1515        '<div class="datepicker--days datepicker--body">' +
1516        '<div class="datepicker--days-names"></div>' +
1517        '<div class="datepicker--cells datepicker--cells-days"></div>' +
1518        '</div>',
1519        months: '' +
1520        '<div class="datepicker--months datepicker--body">' +
1521        '<div class="datepicker--cells datepicker--cells-months"></div>' +
1522        '</div>',
1523        years: '' +
1524        '<div class="datepicker--years datepicker--body">' +
1525        '<div class="datepicker--cells datepicker--cells-years"></div>' +
1526        '</div>'
1527        },
1528        datepicker = $.fn.datepicker,
1529        dp = datepicker.Constructor;
1530
1531    datepicker.Body = function (d, type, opts) {
1532        this.d = d;
1533        this.type = type;
1534        this.opts = opts;
1535        this.$el = $('');
1536
1537        if (this.opts.onlyTimepicker) return;
1538        this.init();
1539    };
1540
1541    datepicker.Body.prototype = {
1542        init: function () {
1543            this._buildBaseHtml();
1544            this._render();
1545
1546            this._bindEvents();
1547        },
1548
1549        _bindEvents: function () {
1550            this.$el.on('click', '.datepicker--cell', $.proxy(this._onClickCell, this));
1551        },
1552
1553        _buildBaseHtml: function () {
1554            this.$el = $(templates[this.type]).appendTo(this.d.$content);
1555            this.$names = $('.datepicker--days-names', this.$el);
1556            this.$cells = $('.datepicker--cells', this.$el);
1557        },
1558
1559        _getDayNamesHtml: function (firstDay, curDay, html, i) {
1560            curDay = curDay != undefined ? curDay : firstDay;
1561            html = html ? html : '';
1562            i = i != undefined ? i : 0;
1563
1564            if (i > 7) return html;
1565            if (curDay == 7) return this._getDayNamesHtml(firstDay, 0, html, ++i);
1566
1567            html += '<div class="datepicker--day-name' + (this.d.isWeekend(curDay) ? " -weekend-" : "") + '">' + this.d.loc.daysMin[curDay] + '</div>';
1568
1569            return this._getDayNamesHtml(firstDay, ++curDay, html, ++i);
1570        },
1571
1572        _getCellContents: function (date, type) {
1573            var classes = "datepicker--cell datepicker--cell-" + type,
1574                currentDate = new Date(),
1575                parent = this.d,
1576                minRange = dp.resetTime(parent.minRange),
1577                maxRange = dp.resetTime(parent.maxRange),
1578                opts = parent.opts,
1579                d = dp.getParsedDate(date),
1580                render = {},
1581                html = d.date;
1582
1583            switch (type) {
1584                case 'day':
1585                    if (parent.isWeekend(d.day)) classes += " -weekend-";
1586                    if (d.month != this.d.parsedDate.month) {
1587                        classes += " -other-month-";
1588                        if (!opts.selectOtherMonths) {
1589                            classes += " -disabled-";
1590                        }
1591                        if (!opts.showOtherMonths) html = '';
1592                    }
1593                    break;
1594                case 'month':
1595                    html = parent.loc[parent.opts.monthsField][d.month];
1596                    break;
1597                case 'year':
1598                    var decade = parent.curDecade;
1599                    html = d.year;
1600                    if (d.year < decade[0] || d.year > decade[1]) {
1601                        classes += ' -other-decade-';
1602                        if (!opts.selectOtherYears) {
1603                            classes += " -disabled-";
1604                        }
1605                        if (!opts.showOtherYears) html = '';
1606                    }
1607                    break;
1608            }
1609
1610            if (opts.onRenderCell) {
1611                render = opts.onRenderCell(date, type) || {};
1612                html = render.html ? render.html : html;
1613                classes += render.classes ? ' ' + render.classes : '';
1614            }
1615
1616            if (opts.range) {
1617                if (dp.isSame(minRange, date, type)) classes += ' -range-from-';
1618                if (dp.isSame(maxRange, date, type)) classes += ' -range-to-';
1619
1620                if (parent.selectedDates.length == 1 && parent.focused) {
1621                    if (
1622                        (dp.bigger(minRange, date) && dp.less(parent.focused, date)) ||
1623                        (dp.less(maxRange, date) && dp.bigger(parent.focused, date)))
1624                    {
1625                        classes += ' -in-range-'
1626                    }
1627
1628                    if (dp.less(maxRange, date) && dp.isSame(parent.focused, date)) {
1629                        classes += ' -range-from-'
1630                    }
1631                    if (dp.bigger(minRange, date) && dp.isSame(parent.focused, date)) {
1632                        classes += ' -range-to-'
1633                    }
1634
1635                } else if (parent.selectedDates.length == 2) {
1636                    if (dp.bigger(minRange, date) && dp.less(maxRange, date)) {
1637                        classes += ' -in-range-'
1638                    }
1639                }
1640            }
1641
1642
1643            if (dp.isSame(currentDate, date, type)) classes += ' -current-';
1644            if (parent.focused && dp.isSame(date, parent.focused, type)) classes += ' -focus-';
1645            if (parent._isSelected(date, type)) classes += ' -selected-';
1646            if (!parent._isInRange(date, type) || render.disabled) classes += ' -disabled-';
1647
1648            return {
1649                html: html,
1650                classes: classes
1651            }
1652        },
1653
1654        /**
1655         * Calculates days number to render. Generates days html and returns it.
1656         * @param {object} date - Date object
1657         * @returns {string}
1658         * @private
1659         */
1660        _getDaysHtml: function (date) {
1661            var totalMonthDays = dp.getDaysCount(date),
1662                firstMonthDay = new Date(date.getFullYear(), date.getMonth(), 1).getDay(),
1663                lastMonthDay = new Date(date.getFullYear(), date.getMonth(), totalMonthDays).getDay(),
1664                daysFromPevMonth = firstMonthDay - this.d.loc.firstDay,
1665                daysFromNextMonth = 6 - lastMonthDay + this.d.loc.firstDay;
1666
1667            daysFromPevMonth = daysFromPevMonth < 0 ? daysFromPevMonth + 7 : daysFromPevMonth;
1668            daysFromNextMonth = daysFromNextMonth > 6 ? daysFromNextMonth - 7 : daysFromNextMonth;
1669
1670            var startDayIndex = -daysFromPevMonth + 1,
1671                m, y,
1672                html = '';
1673
1674            for (var i = startDayIndex, max = totalMonthDays + daysFromNextMonth; i <= max; i++) {
1675                y = date.getFullYear();
1676                m = date.getMonth();
1677
1678                html += this._getDayHtml(new Date(y, m, i))
1679            }
1680
1681            return html;
1682        },
1683
1684        _getDayHtml: function (date) {
1685           var content = this._getCellContents(date, 'day');
1686
1687            return '<div class="' + content.classes + '" ' +
1688                'data-date="' + date.getDate() + '" ' +
1689                'data-month="' + date.getMonth() + '" ' +
1690                'data-year="' + date.getFullYear() + '">' + content.html + '</div>';
1691        },
1692
1693        /**
1694         * Generates months html
1695         * @param {object} date - date instance
1696         * @returns {string}
1697         * @private
1698         */
1699        _getMonthsHtml: function (date) {
1700            var html = '',
1701                d = dp.getParsedDate(date),
1702                i = 0;
1703
1704            while(i < 12) {
1705                html += this._getMonthHtml(new Date(d.year, i));
1706                i++
1707            }
1708
1709            return html;
1710        },
1711
1712        _getMonthHtml: function (date) {
1713            var content = this._getCellContents(date, 'month');
1714
1715            return '<div class="' + content.classes + '" data-month="' + date.getMonth() + '">' + content.html + '</div>'
1716        },
1717
1718        _getYearsHtml: function (date) {
1719            var d = dp.getParsedDate(date),
1720                decade = dp.getDecade(date),
1721                firstYear = decade[0] - 1,
1722                html = '',
1723                i = firstYear;
1724
1725            for (i; i <= decade[1] + 1; i++) {
1726                html += this._getYearHtml(new Date(i , 0));
1727            }
1728
1729            return html;
1730        },
1731
1732        _getYearHtml: function (date) {
1733            var content = this._getCellContents(date, 'year');
1734
1735            return '<div class="' + content.classes + '" data-year="' + date.getFullYear() + '">' + content.html + '</div>'
1736        },
1737
1738        _renderTypes: {
1739            days: function () {
1740                var dayNames = this._getDayNamesHtml(this.d.loc.firstDay),
1741                    days = this._getDaysHtml(this.d.currentDate);
1742
1743                this.$cells.html(days);
1744                this.$names.html(dayNames)
1745            },
1746            months: function () {
1747                var html = this._getMonthsHtml(this.d.currentDate);
1748
1749                this.$cells.html(html)
1750            },
1751            years: function () {
1752                var html = this._getYearsHtml(this.d.currentDate);
1753
1754                this.$cells.html(html)
1755            }
1756        },
1757
1758        _render: function () {
1759            if (this.opts.onlyTimepicker) return;
1760            this._renderTypes[this.type].bind(this)();
1761        },
1762
1763        _update: function () {
1764            var $cells = $('.datepicker--cell', this.$cells),
1765                _this = this,
1766                classes,
1767                $cell,
1768                date;
1769            $cells.each(function (cell, i) {
1770                $cell = $(this);
1771                date = _this.d._getDateFromCell($(this));
1772                classes = _this._getCellContents(date, _this.d.cellType);
1773                $cell.attr('class',classes.classes)
1774            });
1775        },
1776
1777        show: function () {
1778            if (this.opts.onlyTimepicker) return;
1779            this.$el.addClass('active');
1780            this.acitve = true;
1781        },
1782
1783        hide: function () {
1784            this.$el.removeClass('active');
1785            this.active = false;
1786        },
1787
1788        //  Events
1789        // -------------------------------------------------
1790
1791        _handleClick: function (el) {
1792            var date = el.data('date') || 1,
1793                month = el.data('month') || 0,
1794                year = el.data('year') || this.d.parsedDate.year,
1795                dp = this.d;
1796            // Change view if min view does not reach yet
1797            if (dp.view != this.opts.minView) {
1798                dp.down(new Date(year, month, date));
1799                return;
1800            }
1801            // Select date if min view is reached
1802            var selectedDate = new Date(year, month, date),
1803                alreadySelected = this.d._isSelected(selectedDate, this.d.cellType);
1804
1805            if (!alreadySelected) {
1806                dp._trigger('clickCell', selectedDate);
1807                return;
1808            }
1809
1810            dp._handleAlreadySelectedDates.bind(dp, alreadySelected, selectedDate)();
1811
1812        },
1813
1814        _onClickCell: function (e) {
1815            var $el = $(e.target).closest('.datepicker--cell');
1816
1817            if ($el.hasClass('-disabled-')) return;
1818
1819            this._handleClick.bind(this)($el);
1820        }
1821    };
1822})();
1823
1824;(function () {
1825    var template = '' +
1826        '<div class="datepicker--nav-action" data-action="prev">#{prevHtml}</div>' +
1827        '<div class="datepicker--nav-title">#{title}</div>' +
1828        '<div class="datepicker--nav-action" data-action="next">#{nextHtml}</div>',
1829        buttonsContainerTemplate = '<div class="datepicker--buttons"></div>',
1830        button = '<span class="datepicker--button" data-action="#{action}">#{label}</span>',
1831        datepicker = $.fn.datepicker,
1832        dp = datepicker.Constructor;
1833
1834    datepicker.Navigation = function (d, opts) {
1835        this.d = d;
1836        this.opts = opts;
1837
1838        this.$buttonsContainer = '';
1839
1840        this.init();
1841    };
1842
1843    datepicker.Navigation.prototype = {
1844        init: function () {
1845            this._buildBaseHtml();
1846            this._bindEvents();
1847        },
1848
1849        _bindEvents: function () {
1850            this.d.$nav.on('click', '.datepicker--nav-action', $.proxy(this._onClickNavButton, this));
1851            this.d.$nav.on('click', '.datepicker--nav-title', $.proxy(this._onClickNavTitle, this));
1852            this.d.$datepicker.on('click', '.datepicker--button', $.proxy(this._onClickNavButton, this));
1853        },
1854
1855        _buildBaseHtml: function () {
1856            if (!this.opts.onlyTimepicker) {
1857                this._render();
1858            }
1859            this._addButtonsIfNeed();
1860        },
1861
1862        _addButtonsIfNeed: function () {
1863            if (this.opts.todayButton) {
1864                this._addButton('today')
1865            }
1866            if (this.opts.clearButton) {
1867                this._addButton('clear')
1868            }
1869        },
1870
1871        _render: function () {
1872            var title = this._getTitle(this.d.currentDate),
1873                html = dp.template(template, $.extend({title: title}, this.opts));
1874            this.d.$nav.html(html);
1875            if (this.d.view == 'years') {
1876                $('.datepicker--nav-title', this.d.$nav).addClass('-disabled-');
1877            }
1878            this.setNavStatus();
1879        },
1880
1881        _getTitle: function (date) {
1882            return this.d.formatDate(this.opts.navTitles[this.d.view], date)
1883        },
1884
1885        _addButton: function (type) {
1886            if (!this.$buttonsContainer.length) {
1887                this._addButtonsContainer();
1888            }
1889
1890            var data = {
1891                    action: type,
1892                    label: this.d.loc[type]
1893                },
1894                html = dp.template(button, data);
1895
1896            if ($('[data-action=' + type + ']', this.$buttonsContainer).length) return;
1897            this.$buttonsContainer.append(html);
1898        },
1899
1900        _addButtonsContainer: function () {
1901            this.d.$datepicker.append(buttonsContainerTemplate);
1902            this.$buttonsContainer = $('.datepicker--buttons', this.d.$datepicker);
1903        },
1904
1905        setNavStatus: function () {
1906            if (!(this.opts.minDate || this.opts.maxDate) || !this.opts.disableNavWhenOutOfRange) return;
1907
1908            var date = this.d.parsedDate,
1909                m = date.month,
1910                y = date.year,
1911                d = date.date;
1912
1913            switch (this.d.view) {
1914                case 'days':
1915                    if (!this.d._isInRange(new Date(y, m-1, 1), 'month')) {
1916                        this._disableNav('prev')
1917                    }
1918                    if (!this.d._isInRange(new Date(y, m+1, 1), 'month')) {
1919                        this._disableNav('next')
1920                    }
1921                    break;
1922                case 'months':
1923                    if (!this.d._isInRange(new Date(y-1, m, d), 'year')) {
1924                        this._disableNav('prev')
1925                    }
1926                    if (!this.d._isInRange(new Date(y+1, m, d), 'year')) {
1927                        this._disableNav('next')
1928                    }
1929                    break;
1930                case 'years':
1931                    var decade = dp.getDecade(this.d.date);
1932                    if (!this.d._isInRange(new Date(decade[0] - 1, 0, 1), 'year')) {
1933                        this._disableNav('prev')
1934                    }
1935                    if (!this.d._isInRange(new Date(decade[1] + 1, 0, 1), 'year')) {
1936                        this._disableNav('next')
1937                    }
1938                    break;
1939            }
1940        },
1941
1942        _disableNav: function (nav) {
1943            $('[data-action="' + nav + '"]', this.d.$nav).addClass('-disabled-')
1944        },
1945
1946        _activateNav: function (nav) {
1947            $('[data-action="' + nav + '"]', this.d.$nav).removeClass('-disabled-')
1948        },
1949
1950        _onClickNavButton: function (e) {
1951            var $el = $(e.target).closest('[data-action]'),
1952                action = $el.data('action');
1953
1954            this.d[action]();
1955        },
1956
1957        _onClickNavTitle: function (e) {
1958            if ($(e.target).hasClass('-disabled-')) return;
1959
1960            if (this.d.view == 'days') {
1961                return this.d.view = 'months'
1962            }
1963
1964            this.d.view = 'years';
1965        }
1966    }
1967
1968})();
1969
1970;(function () {
1971    var template = '<div class="datepicker--time">' +
1972        '<div class="datepicker--time-current">' +
1973        '   <span class="datepicker--time-current-hours">#{hourVisible}</span>' +
1974        '   <span class="datepicker--time-current-colon">:</span>' +
1975        '   <span class="datepicker--time-current-minutes">#{minValue}</span>' +
1976        '</div>' +
1977        '<div class="datepicker--time-sliders">' +
1978        '   <div class="datepicker--time-row">' +
1979        '      <input type="range" name="hours" value="#{hourValue}" min="#{hourMin}" max="#{hourMax}" step="#{hourStep}"/>' +
1980        '   </div>' +
1981        '   <div class="datepicker--time-row">' +
1982        '      <input type="range" name="minutes" value="#{minValue}" min="#{minMin}" max="#{minMax}" step="#{minStep}"/>' +
1983        '   </div>' +
1984        '</div>' +
1985        '</div>',
1986        datepicker = $.fn.datepicker,
1987        dp = datepicker.Constructor;
1988
1989    datepicker.Timepicker = function (inst, opts) {
1990        this.d = inst;
1991        this.opts = opts;
1992
1993        this.init();
1994    };
1995
1996    datepicker.Timepicker.prototype = {
1997        init: function () {
1998            var input = 'input';
1999            this._setTime(this.d.date);
2000            this._buildHTML();
2001
2002            if (navigator.userAgent.match(/trident/gi)) {
2003                input = 'change';
2004            }
2005
2006            this.d.$el.on('selectDate', this._onSelectDate.bind(this));
2007            this.$ranges.on(input, this._onChangeRange.bind(this));
2008            this.$ranges.on('mouseup', this._onMouseUpRange.bind(this));
2009            this.$ranges.on('mousemove focus ', this._onMouseEnterRange.bind(this));
2010            this.$ranges.on('mouseout blur', this._onMouseOutRange.bind(this));
2011        },
2012
2013        _setTime: function (date) {
2014            var _date = dp.getParsedDate(date);
2015
2016            this._handleDate(date);
2017            this.hours = _date.hours < this.minHours ? this.minHours : _date.hours;
2018            this.minutes = _date.minutes < this.minMinutes ? this.minMinutes : _date.minutes;
2019        },
2020
2021        /**
2022         * Sets minHours and minMinutes from date (usually it's a minDate)
2023         * Also changes minMinutes if current hours are bigger then @date hours
2024         * @param date {Date}
2025         * @private
2026         */
2027        _setMinTimeFromDate: function (date) {
2028            this.minHours = date.getHours();
2029            this.minMinutes = date.getMinutes();
2030
2031            // If, for example, min hours are 10, and current hours are 12,
2032            // update minMinutes to default value, to be able to choose whole range of values
2033            if (this.d.lastSelectedDate) {
2034                if (this.d.lastSelectedDate.getHours() > date.getHours()) {
2035                    this.minMinutes = this.opts.minMinutes;
2036                }
2037            }
2038        },
2039
2040        _setMaxTimeFromDate: function (date) {
2041            this.maxHours = date.getHours();
2042            this.maxMinutes = date.getMinutes();
2043
2044            if (this.d.lastSelectedDate) {
2045                if (this.d.lastSelectedDate.getHours() < date.getHours()) {
2046                    this.maxMinutes = this.opts.maxMinutes;
2047                }
2048            }
2049        },
2050
2051        _setDefaultMinMaxTime: function () {
2052            var maxHours = 23,
2053                maxMinutes = 59,
2054                opts = this.opts;
2055
2056            this.minHours = opts.minHours < 0 || opts.minHours > maxHours ? 0 : opts.minHours;
2057            this.minMinutes = opts.minMinutes < 0 || opts.minMinutes > maxMinutes ? 0 : opts.minMinutes;
2058            this.maxHours = opts.maxHours < 0 || opts.maxHours > maxHours ? maxHours : opts.maxHours;
2059            this.maxMinutes = opts.maxMinutes < 0 || opts.maxMinutes > maxMinutes ? maxMinutes : opts.maxMinutes;
2060        },
2061
2062        /**
2063         * Looks for min/max hours/minutes and if current values
2064         * are out of range sets valid values.
2065         * @private
2066         */
2067        _validateHoursMinutes: function (date) {
2068            if (this.hours < this.minHours) {
2069                this.hours = this.minHours;
2070            } else if (this.hours > this.maxHours) {
2071                this.hours = this.maxHours;
2072            }
2073
2074            if (this.minutes < this.minMinutes) {
2075                this.minutes = this.minMinutes;
2076            } else if (this.minutes > this.maxMinutes) {
2077                this.minutes = this.maxMinutes;
2078            }
2079        },
2080
2081        _buildHTML: function () {
2082            var lz = dp.getLeadingZeroNum,
2083                data = {
2084                    hourMin: this.minHours,
2085                    hourMax: lz(this.maxHours),
2086                    hourStep: this.opts.hoursStep,
2087                    hourValue: this.hours,
2088                    hourVisible: lz(this.displayHours),
2089                    minMin: this.minMinutes,
2090                    minMax: lz(this.maxMinutes),
2091                    minStep: this.opts.minutesStep,
2092                    minValue: lz(this.minutes)
2093                },
2094                _template = dp.template(template, data);
2095
2096            this.$timepicker = $(_template).appendTo(this.d.$datepicker);
2097            this.$ranges = $('[type="range"]', this.$timepicker);
2098            this.$hours = $('[name="hours"]', this.$timepicker);
2099            this.$minutes = $('[name="minutes"]', this.$timepicker);
2100            this.$hoursText = $('.datepicker--time-current-hours', this.$timepicker);
2101            this.$minutesText = $('.datepicker--time-current-minutes', this.$timepicker);
2102
2103            if (this.d.ampm) {
2104                this.$ampm = $('<span class="datepicker--time-current-ampm">')
2105                    .appendTo($('.datepicker--time-current', this.$timepicker))
2106                    .html(this.dayPeriod);
2107
2108                this.$timepicker.addClass('-am-pm-');
2109            }
2110        },
2111
2112        _updateCurrentTime: function () {
2113            var h =  dp.getLeadingZeroNum(this.displayHours),
2114                m = dp.getLeadingZeroNum(this.minutes);
2115
2116            this.$hoursText.html(h);
2117            this.$minutesText.html(m);
2118
2119            if (this.d.ampm) {
2120                this.$ampm.html(this.dayPeriod);
2121            }
2122        },
2123
2124        _updateRanges: function () {
2125            this.$hours.attr({
2126                min: this.minHours,
2127                max: this.maxHours
2128            }).val(this.hours);
2129
2130            this.$minutes.attr({
2131                min: this.minMinutes,
2132                max: this.maxMinutes
2133            }).val(this.minutes)
2134        },
2135
2136        /**
2137         * Sets minHours, minMinutes etc. from date. If date is not passed, than sets
2138         * values from options
2139         * @param [date] {object} - Date object, to get values from
2140         * @private
2141         */
2142        _handleDate: function (date) {
2143            this._setDefaultMinMaxTime();
2144            if (date) {
2145                if (dp.isSame(date, this.d.opts.minDate)) {
2146                    this._setMinTimeFromDate(this.d.opts.minDate);
2147                } else if (dp.isSame(date, this.d.opts.maxDate)) {
2148                    this._setMaxTimeFromDate(this.d.opts.maxDate);
2149                }
2150            }
2151
2152            this._validateHoursMinutes(date);
2153        },
2154
2155        update: function () {
2156            this._updateRanges();
2157            this._updateCurrentTime();
2158        },
2159
2160        /**
2161         * Calculates valid hour value to display in text input and datepicker's body.
2162         * @param date {Date|Number} - date or hours
2163         * @param [ampm] {Boolean} - 12 hours mode
2164         * @returns {{hours: *, dayPeriod: string}}
2165         * @private
2166         */
2167        _getValidHoursFromDate: function (date, ampm) {
2168            var d = date,
2169                hours = date;
2170
2171            if (date instanceof Date) {
2172                d = dp.getParsedDate(date);
2173                hours = d.hours;
2174            }
2175
2176            var _ampm = ampm || this.d.ampm,
2177                dayPeriod = 'am';
2178
2179            if (_ampm) {
2180                switch(true) {
2181                    case hours == 0:
2182                        hours = 12;
2183                        break;
2184                    case hours == 12:
2185                        dayPeriod = 'pm';
2186                        break;
2187                    case hours > 11:
2188                        hours = hours - 12;
2189                        dayPeriod = 'pm';
2190                        break;
2191                    default:
2192                        break;
2193                }
2194            }
2195
2196            return {
2197                hours: hours,
2198                dayPeriod: dayPeriod
2199            }
2200        },
2201
2202        set hours (val) {
2203            this._hours = val;
2204
2205            var displayHours = this._getValidHoursFromDate(val);
2206
2207            this.displayHours = displayHours.hours;
2208            this.dayPeriod = displayHours.dayPeriod;
2209        },
2210
2211        get hours() {
2212            return this._hours;
2213        },
2214
2215        //  Events
2216        // -------------------------------------------------
2217
2218        _onChangeRange: function (e) {
2219            var $target = $(e.target),
2220                name = $target.attr('name');
2221            
2222            this.d.timepickerIsActive = true;
2223
2224            this[name] = $target.val();
2225            this._updateCurrentTime();
2226            this.d._trigger('timeChange', [this.hours, this.minutes]);
2227
2228            this._handleDate(this.d.lastSelectedDate);
2229            this.update()
2230        },
2231
2232        _onSelectDate: function (e, data) {
2233            this._handleDate(data);
2234            this.update();
2235        },
2236
2237        _onMouseEnterRange: function (e) {
2238            var name = $(e.target).attr('name');
2239            $('.datepicker--time-current-' + name, this.$timepicker).addClass('-focus-');
2240        },
2241
2242        _onMouseOutRange: function (e) {
2243            var name = $(e.target).attr('name');
2244            if (this.d.inFocus) return; // Prevent removing focus when mouse out of range slider
2245            $('.datepicker--time-current-' + name, this.$timepicker).removeClass('-focus-');
2246        },
2247
2248        _onMouseUpRange: function (e) {
2249            this.d.timepickerIsActive = false;
2250        }
2251    };
2252})();
2253 })(window, jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.