PageSourceSearch

https://kraslib.ru/js/datepicker.js

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