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 closeButton: false, 51 52 showEvent: 'focus', 53 autoClose: false, 54 55 // navigation 56 monthsField: 'monthsShort', 57 prevHtml: '<svg><path d="M 17,12 l -5,5 l 5,5"></path></svg>', 58 nextHtml: '<svg><path d="M 14,12 l 5,5 l -5,5"></path></svg>', 59 navTitles: { 60 days: 'MM, <i>yyyy</i>', 61 months: 'yyyy', 62 years: 'yyyy1 - yyyy2' 63 }, 64 65 // timepicker 66 timepicker: false, 67 onlyTimepicker: false, 68 dateTimeSeparator: ' ', 69 timeFormat: '', 70 minHours: 0, 71 maxHours: 24, 72 minMinutes: 0, 73 maxMinutes: 59, 74 hoursStep: 1, 75 minutesStep: 1, 76 77 // events 78 onSelect: '', 79 onShow: '', 80 onHide: '', 81 onChangeMonth: '', 82 onChangeYear: '', 83 onChangeDecade: '', 84 onChangeView: '', 85 onRenderCell: '' 86 }, 87 hotKeys = { 88 'ctrlRight': [17, 39], 89 'ctrlUp': [17, 38], 90 'ctrlLeft': [17, 37], 91 'ctrlDown': [17, 40], 92 'shiftRight': [16, 39], 93 'shiftUp': [16, 38], 94 'shiftLeft': [16, 37], 95 'shiftDown': [16, 40], 96 'altUp': [18, 38], 97 'altRight': [18, 39], 98 'altLeft': [18, 37], 99 'altDown': [18, 40], 100 'ctrlShiftUp': [16, 17, 38] 101 }, 102 datepicker; 103 104 var Datepicker = function (el, options) { 105 this.el = el; 106 this.$el = $(el); 107 108 this.opts = $.extend(true, {}, defaults, options, this.$el.data()); 109 110 if ($body == undefined) { 111 $body = $('body'); 112 } 113 114 if (!this.opts.startDate) { 115 this.opts.startDate = new Date(); 116 } 117 118 if (this.el.nodeName == 'INPUT') { 119 this.elIsInput = true; 120 } 121 122 if (this.opts.altField) { 123 this.$altField = typeof this.opts.altField == 'string' ? $(this.opts.altField) : this.opts.altField; 124 } 125 126 this.inited = false; 127 this.visible = false; 128 this.silent = false; // Need to prevent unnecessary rendering 129 130 this.currentDate = this.opts.startDate; 131 this.currentView = this.opts.view; 132 this._createShortCuts(); 133 this.selectedDates = []; 134 this.views = {}; 135 this.keys = []; 136 this.minRange = ''; 137 this.maxRange = ''; 138 this._prevOnSelectValue = ''; 139 140 this.init() 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.getFullYear() != d.year || 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 if (this.opts.todayButton instanceof Date) { 588 this.selectDate(this.opts.todayButton) 589 } 590 }, 591 592 clear: function () { 593 this.selectedDates = []; 594 this.minRange = ''; 595 this.maxRange = ''; 596 this.views[this.currentView]._render(); 597 this._setInputValue(); 598 if (this.opts.onSelect) { 599 this._triggerOnChange() 600 } 601 }, 602 603 close: function () { 604 this.$el.focus(); 605 this.$datepicker 606 .removeClass('active') 607 .css({left: '-100000px'}); 608 }, 609 /** 610 * Updates datepicker options 611 * @param {String|Object} param - parameter's name to update. If object then it will extend current options 612 * @param {String|Number|Object} [value] - new param value 613 */ 614 update: function (param, value) { 615 var len = arguments.length, 616 lastSelectedDate = this.lastSelectedDate; 617 618 if (len == 2) { 619 this.opts[param] = value; 620 } else if (len == 1 && typeof param == 'object') { 621 this.opts = $.extend(true, this.opts, param) 622 } 623 624 this._createShortCuts(); 625 this._syncWithMinMaxDates();
626 this._defineLocale(this.opts.language); 627 this.nav._addButtonsIfNeed(); 628 if (!this.opts.onlyTimepicker) this.nav._render(); 629 this.views[this.currentView]._render(); 630 631 if (this.elIsInput && !this.opts.inline) { 632 this._setPositionClasses(this.opts.position); 633 if (this.visible) { 634 this.setPosition(this.opts.position) 635 } 636 } 637 638 if (this.opts.classes) { 639 this.$datepicker.addClass(this.opts.classes) 640 } 641 642 if (this.opts.onlyTimepicker) { 643 this.$datepicker.addClass('-only-timepicker-'); 644 } 645 646 if (this.opts.timepicker) { 647 if (lastSelectedDate) this.timepicker._handleDate(lastSelectedDate); 648 this.timepicker._updateRanges(); 649 this.timepicker._updateCurrentTime(); 650 // Change hours and minutes if it's values have been changed through min/max hours/minutes 651 if (lastSelectedDate) { 652 lastSelectedDate.setHours(this.timepicker.hours); 653 lastSelectedDate.setMinutes(this.timepicker.minutes); 654 } 655 } 656 657 this._setInputValue(); 658 659 return this; 660 }, 661 662 _syncWithMinMaxDates: function () { 663 var curTime = this.date.getTime(); 664 this.silent = true; 665 if (this.minTime > curTime) { 666 this.date = this.minDate; 667 } 668 669 if (this.maxTime < curTime) { 670 this.date = this.maxDate; 671 } 672 this.silent = false; 673 }, 674 675 _isSelected: function (checkDate, cellType) { 676 var res = false; 677 this.selectedDates.some(function (date) { 678 if (datepicker.isSame(date, checkDate, cellType)) { 679 res = date; 680 return true; 681 } 682 }); 683 return res; 684 }, 685 686 _setInputValue: function () { 687 var _this = this, 688 opts = _this.opts, 689 format = _this.loc.dateFormat, 690 altFormat = opts.altFieldDateFormat, 691 value = _this.selectedDates.map(function (date) { 692 return _this.formatDate(format, date) 693 }), 694 altValues; 695 696 if (opts.altField && _this.$altField.length) { 697 altValues = this.selectedDates.map(function (date) { 698 return _this.formatDate(altFormat, date) 699 }); 700 altValues = altValues.join(this.opts.multipleDatesSeparator); 701 this.$altField.val(altValues); 702 } 703 704 value = value.join(this.opts.multipleDatesSeparator); 705 706 this.$el.val(value) 707 }, 708 709 /** 710 * Check if date is between minDate and maxDate 711 * @param date {object} - date object 712 * @param type {string} - cell type 713 * @returns {boolean} 714 * @private 715 */ 716 _isInRange: function (date, type) { 717 var time = date.getTime(), 718 d = datepicker.getParsedDate(date), 719 min = datepicker.getParsedDate(this.minDate), 720 max = datepicker.getParsedDate(this.maxDate), 721 dMinTime = new Date(d.year, d.month, min.date).getTime(), 722 dMaxTime = new Date(d.year, d.month, max.date).getTime(), 723 types = { 724 day: time >= this.minTime && time <= this.maxTime, 725 month: dMinTime >= this.minTime && dMaxTime <= this.maxTime, 726 year: d.year >= min.year && d.year <= max.year 727 }; 728 return type ? types[type] : types.day 729 }, 730 731 _getDimensions: function ($el) { 732 var offset = $el.offset(); 733 734 return { 735 width: $el.outerWidth(), 736 height: $el.outerHeight(), 737 left: offset.left, 738 top: offset.top 739 } 740 }, 741 742 _getDateFromCell: function (cell) { 743 var curDate = this.parsedDate, 744 year = cell.data('year') || curDate.year,
745 month = cell.data('month') == undefined ? curDate.month : cell.data('month'), 746 date = cell.data('date') || 1; 747 748 return new Date(year, month, date); 749 }, 750 751 _setPositionClasses: function (pos) { 752 pos = pos.split(' '); 753 var main = pos[0], 754 sec = pos[1], 755 classes = 'datepicker -' + main + '-' + sec + '- -from-' + main + '-'; 756 757 if (this.visible) classes += ' active'; 758 759 this.$datepicker 760 .removeAttr('class') 761 .addClass(classes); 762 }, 763 764 setPosition: function (position) { 765 position = position || this.opts.position; 766 767 var dims = this._getDimensions(this.$el), 768 selfDims = this._getDimensions(this.$datepicker), 769 pos = position.split(' '), 770 top, left, 771 offset = this.opts.offset, 772 main = pos[0], 773 secondary = pos[1]; 774 775 switch (main) { 776 case 'top': 777 top = dims.top - selfDims.height - offset; 778 break; 779 case 'right': 780 left = dims.left + dims.width + offset; 781 break; 782 case 'bottom': 783 top = dims.top + dims.height + offset; 784 break; 785 case 'left': 786 left = dims.left - selfDims.width - offset; 787 break; 788 } 789 790 switch(secondary) { 791 case 'top': 792 top = dims.top; 793 break; 794 case 'right': 795 left = dims.left + dims.width - selfDims.width; 796 break; 797 case 'bottom': 798 top = dims.top + dims.height - selfDims.height; 799 break; 800 case 'left': 801 left = dims.left; 802 break; 803 case 'center': 804 if (/left|right/.test(main)) { 805 top = dims.top + dims.height/2 - selfDims.height/2; 806 } else { 807 left = dims.left + dims.width/2 - selfDims.width/2; 808 } 809 } 810 811 this.$datepicker 812 .css({ 813 left: left, 814 top: top 815 }) 816 }, 817 818 show: function () { 819 var onShow = this.opts.onShow; 820 821 this.setPosition(this.opts.position); 822 this.$datepicker.addClass('active'); 823 this.visible = true; 824 825 if (onShow) { 826 this._bindVisionEvents(onShow) 827 } 828 }, 829 830 hide: function () { 831 var onHide = this.opts.onHide; 832 833 this.$datepicker 834 .removeClass('active') 835 .css({ 836 left: '-100000px' 837 }); 838 839 this.focused = ''; 840 this.keys = []; 841 842 this.inFocus = false; 843 this.visible = false; 844 this.$el.blur(); 845 846 if (onHide) { 847 this._bindVisionEvents(onHide) 848 } 849 }, 850 851 down: function (date) { 852 this._changeView(date, 'down'); 853 }, 854 855 up: function (date) { 856 this._changeView(date, 'up'); 857 }, 858 859 _bindVisionEvents: function (event) { 860 this.$datepicker.off('transitionend.dp'); 861 event(this, false); 862 this.$datepicker.one('transitionend.dp', event.bind(this, this, true)) 863 }, 864 865 _changeView: function (date, dir) { 866 date = date || this.focused || this.date; 867 868 var nextView = dir == 'up' ? this.viewIndex + 1 : this.viewIndex - 1; 869 if (nextView > 2) nextView = 2; 870 if (nextView < 0) nextView = 0; 871 872 this.silent = true; 873 this.date = new Date(date.getFullYear(), date.getMonth(), 1); 874 this.silent = false; 875 this.view = this.viewIndexes[nextView]; 876 877 }, 878
879 _handleHotKey: function (key) { 880 var date = datepicker.getParsedDate(this._getFocusedDate()), 881 focusedParsed, 882 o = this.opts, 883 newDate, 884 totalDaysInNextMonth, 885 monthChanged = false, 886 yearChanged = false, 887 decadeChanged = false, 888 y = date.year, 889 m = date.month, 890 d = date.date; 891 892 switch (key) { 893 case 'ctrlRight': 894 case 'ctrlUp': 895 m += 1; 896 monthChanged = true; 897 break; 898 case 'ctrlLeft': 899 case 'ctrlDown': 900 m -= 1; 901 monthChanged = true; 902 break; 903 case 'shiftRight': 904 case 'shiftUp': 905 yearChanged = true; 906 y += 1; 907 break; 908 case 'shiftLeft': 909 case 'shiftDown': 910 yearChanged = true; 911 y -= 1; 912 break; 913 case 'altRight': 914 case 'altUp': 915 decadeChanged = true; 916 y += 10; 917 break; 918 case 'altLeft': 919 case 'altDown': 920 decadeChanged = true; 921 y -= 10; 922 break; 923 case 'ctrlShiftUp': 924 this.up(); 925 break; 926 } 927 928 totalDaysInNextMonth = datepicker.getDaysCount(new Date(y,m)); 929 newDate = new Date(y,m,d); 930 931 // If next month has less days than current, set date to total days in that month 932 if (totalDaysInNextMonth < d) d = totalDaysInNextMonth; 933 934 // Check if newDate is in valid range 935 if (newDate.getTime() < this.minTime) { 936 newDate = this.minDate; 937 } else if (newDate.getTime() > this.maxTime) { 938 newDate = this.maxDate; 939 } 940 941 this.focused = newDate; 942 943 focusedParsed = datepicker.getParsedDate(newDate); 944 if (monthChanged && o.onChangeMonth) { 945 o.onChangeMonth(focusedParsed.month, focusedParsed.year) 946 } 947 if (yearChanged && o.onChangeYear) { 948 o.onChangeYear(focusedParsed.year) 949 } 950 if (decadeChanged && o.onChangeDecade) { 951 o.onChangeDecade(this.curDecade) 952 } 953 }, 954 955 _registerKey: function (key) { 956 var exists = this.keys.some(function (curKey) { 957 return curKey == key; 958 }); 959 960 if (!exists) { 961 this.keys.push(key) 962 } 963 }, 964 965 _unRegisterKey: function (key) { 966 var index = this.keys.indexOf(key); 967 968 this.keys.splice(index, 1); 969 }, 970 971 _isHotKeyPressed: function () { 972 var currentHotKey, 973 found = false, 974 _this = this, 975 pressedKeys = this.keys.sort(); 976 977 for (var hotKey in hotKeys) { 978 currentHotKey = hotKeys[hotKey]; 979 if (pressedKeys.length != currentHotKey.length) continue; 980 981 if (currentHotKey.every(function (key, i) { return key == pressedKeys[i]})) { 982 _this._trigger('hotKey', hotKey); 983 found = true; 984 } 985 } 986 987 return found; 988 }, 989 990 _trigger: function (event, args) { 991 this.$el.trigger(event, args) 992 }, 993 994 _focusNextCell: function (keyCode, type) { 995 type = type || this.cellType; 996 997 var date = datepicker.getParsedDate(this._getFocusedDate()), 998 y = date.year, 999 m = date.month, 1000 d = date.date; 1001 1002 if (this._isHotKeyPressed()){ 1003 return; 1004 } 1005 1006 switch(keyCode) { 1007 case 37: // left 1008 type == 'day' ? (d -= 1) : ''; 1009 type == 'month' ? (m -= 1) : ''; 1010 type == 'year' ? (y -= 1) : ''; 1011 break; 1012 case 38: // up 1013 type == 'day' ? (d -= 7) : ''; 1014 type == 'month' ? (m -= 3) : ''; 1015 type == 'year' ? (y -= 4) : ''; 1016 break; 1017 case 39: // right 1018 type == 'day' ? (d += 1) : ''; 1019 type == 'month' ? (m += 1) : ''; 1020 type == 'year' ? (y += 1) : ''; 1021 break; 1022 case 40: // down 1023 type == 'day' ? (d += 7) : ''; 1024 type == 'month' ? (m += 3) : ''; 1025 type == 'year' ? (y += 4) : ''; 1026 break; 1027 } 1028 1029 var nd = new Date(y,m,d); 1030 if (nd.getTime() < this.minTime) { 1031 nd = this.minDate; 1032 } else if (nd.getTime() > this.maxTime) { 1033 nd = this.maxDate; 1034 } 1035 1036 this.focused = nd; 1037 1038 }, 1039 1040 _getFocusedDate: function () { 1041 var focused = this.focused || this.selectedDates[this.selectedDates.length - 1], 1042 d = this.parsedDate; 1043 1044 if (!focused) { 1045 switch (this.view) { 1046 case 'days': 1047 focused = new Date(d.year, d.month, new Date().getDate()); 1048 break; 1049 case 'months': 1050 focused = new Date(d.year, d.month, 1); 1051 break; 1052 case 'years': 1053 focused = new Date(d.year, 0, 1); 1054 break; 1055 } 1056 } 1057 1058 return focused; 1059 }, 1060 1061 _getCell: function (date, type) { 1062 type = type || this.cellType; 1063 1064 var d = datepicker.getParsedDate(date), 1065 selector = '.datepicker--cell[data-year="' + d.year + '"]', 1066 $cell; 1067 1068 switch (type) { 1069 case 'month': 1070 selector = '[data-month="' + d.month + '"]'; 1071 break; 1072 case 'day': 1073 selector += '[data-month="' + d.month + '"][data-date="' + d.date + '"]'; 1074 break; 1075 } 1076 $cell = this.views[this.currentView].$el.find(selector); 1077 1078 return $cell.length ? $cell : $(''); 1079 }, 1080 1081 destroy: function () { 1082 var _this = this; 1083 _this.$el 1084 .off('.adp') 1085 .data('datepicker', ''); 1086 1087 _this.selectedDates = []; 1088 _this.focused = ''; 1089 _this.views = {}; 1090 _this.keys = []; 1091 _this.minRange = ''; 1092 _this.maxRange = ''; 1093 1094 if (_this.opts.inline || !_this.elIsInput) { 1095 _this.$datepicker.closest('.datepicker-inline').remove(); 1096 } else { 1097 _this.$datepicker.remove(); 1098 } 1099 }, 1100 1101 _handleAlreadySelectedDates: function (alreadySelected, selectedDate) { 1102 if (this.opts.range) { 1103 if (!this.opts.toggleSelected) { 1104 // Add possibility to select same date when range is true 1105 if (this.selectedDates.length != 2) { 1106 this._trigger('clickCell', selectedDate); 1107 } 1108 } else { 1109 this.removeDate(selectedDate); 1110 } 1111 } else if (this.opts.toggleSelected){ 1112 this.removeDate(selectedDate); 1113 } 1114 1115 // Change last selected date to be able to change time when clicking on this cell 1116 if (!this.opts.toggleSelected) { 1117 this.lastSelectedDate = alreadySelected; 1118 if (this.opts.timepicker) { 1119 this.timepicker._setTime(alreadySelected);
1120 this.timepicker.update(); 1121 } 1122 } 1123 }, 1124 1125 _onShowEvent: function (e) { 1126 if (!this.visible) { 1127 this.show(); 1128 } 1129 }, 1130 1131 _onBlur: function () { 1132 if (!this.inFocus && this.visible) { 1133 this.hide(); 1134 } 1135 }, 1136 1137 _onMouseDownDatepicker: function (e) { 1138 this.inFocus = true; 1139 }, 1140 1141 _onMouseUpDatepicker: function (e) { 1142 this.inFocus = false; 1143 e.originalEvent.inFocus = true; 1144 if (!e.originalEvent.timepickerFocus) this.$el.focus(); 1145 }, 1146 1147 _onKeyUpGeneral: function (e) { 1148 var val = this.$el.val(); 1149 1150 if (!val) { 1151 this.clear(); 1152 } 1153 }, 1154 1155 _onResize: function () { 1156 if (this.visible) { 1157 this.setPosition(); 1158 } 1159 }, 1160 1161 _onMouseUpBody: function (e) { 1162 if (e.originalEvent.inFocus) return; 1163 1164 if (this.visible && !this.inFocus) { 1165 this.hide(); 1166 } 1167 }, 1168 1169 _onMouseUpEl: function (e) { 1170 e.originalEvent.inFocus = true; 1171 setTimeout(this._onKeyUpGeneral.bind(this),4); 1172 }, 1173 1174 _onKeyDown: function (e) { 1175 var code = e.which; 1176 this._registerKey(code); 1177 1178 // Arrows 1179 if (code >= 37 && code <= 40) { 1180 e.preventDefault(); 1181 this._focusNextCell(code); 1182 } 1183 1184 // Enter 1185 if (code == 13) { 1186 if (this.focused) { 1187 if (this._getCell(this.focused).hasClass('-disabled-')) return; 1188 if (this.view != this.opts.minView) { 1189 this.down() 1190 } else { 1191 var alreadySelected = this._isSelected(this.focused, this.cellType); 1192 1193 if (!alreadySelected) { 1194 if (this.timepicker) { 1195 this.focused.setHours(this.timepicker.hours); 1196 this.focused.setMinutes(this.timepicker.minutes); 1197 } 1198 this.selectDate(this.focused); 1199 return; 1200 } 1201 this._handleAlreadySelectedDates(alreadySelected, this.focused) 1202 } 1203 } 1204 } 1205 1206 // Esc 1207 if (code == 27) { 1208 this.hide(); 1209 } 1210 }, 1211 1212 _onKeyUp: function (e) { 1213 var code = e.which; 1214 this._unRegisterKey(code); 1215 }, 1216 1217 _onHotKey: function (e, hotKey) { 1218 this._handleHotKey(hotKey); 1219 }, 1220 1221 _onMouseEnterCell: function (e) { 1222 var $cell = $(e.target).closest('.datepicker--cell'), 1223 date = this._getDateFromCell($cell); 1224 1225 // Prevent from unnecessary rendering and setting new currentDate 1226 this.silent = true; 1227 1228 if (this.focused) { 1229 this.focused = '' 1230 } 1231 1232 $cell.addClass('-focus-'); 1233 1234 this.focused = date; 1235 this.silent = false; 1236 1237 if (this.opts.range && this.selectedDates.length == 1) { 1238 this.minRange = this.selectedDates[0]; 1239 this.maxRange = ''; 1240 if (datepicker.less(this.minRange, this.focused)) { 1241 this.maxRange = this.minRange; 1242 this.minRange = ''; 1243 } 1244 this.views[this.currentView]._update(); 1245 } 1246 }, 1247 1248 _onMouseLeaveCell: function (e) { 1249 var $cell = $(e.target).closest('.datepicker--cell'); 1250 1251 $cell.removeClass('-focus-'); 1252 1253 this.silent = true; 1254 this.focused = ''; 1255 this.silent = false; 1256 }, 1257 1258 _onTimeChange: function (e, h, m) { 1259 var date = new Date(), 1260 selectedDates = this.selectedDates, 1261 selected = false; 1262 1263 if (selectedDates.length) { 1264 selected = true; 1265 date = this.lastSelectedDate; 1266 } 1267 1268 date.setHours(h); 1269 date.setMinutes(m); 1270 1271 if (!selected && !this._getCell(date).hasClass('-disabled-')) { 1272 this.selectDate(date); 1273 } else { 1274 this._setInputValue(); 1275 if (this.opts.onSelect) { 1276 this._triggerOnChange(); 1277 } 1278 } 1279 }, 1280 1281 _onClickCell: function (e, date) { 1282 if (this.timepicker) { 1283 date.setHours(this.timepicker.hours); 1284 date.setMinutes(this.timepicker.minutes); 1285 } 1286 this.selectDate(date); 1287 }, 1288 1289 set focused(val) { 1290 if (!val && this.focused) { 1291 var $cell = this._getCell(this.focused); 1292 1293 if ($cell.length) { 1294 $cell.removeClass('-focus-') 1295 } 1296 } 1297 this._focused = val; 1298 if (this.opts.range && this.selectedDates.length == 1) { 1299 this.minRange = this.selectedDates[0]; 1300 this.maxRange = ''; 1301 if (datepicker.less(this.minRange, this._focused)) { 1302 this.maxRange = this.minRange; 1303 this.minRange = ''; 1304 } 1305 } 1306 if (this.silent) return; 1307 this.date = val; 1308 }, 1309 1310 get focused() { 1311 return this._focused; 1312 }, 1313 1314 get parsedDate() { 1315 return datepicker.getParsedDate(this.date); 1316 }, 1317 1318 set date (val) { 1319 if (!(val instanceof Date)) return; 1320 1321 this.currentDate = val; 1322 1323 if (this.inited && !this.silent) { 1324 this.views[this.view]._render(); 1325 this.nav._render(); 1326 if (this.visible && this.elIsInput) { 1327 this.setPosition(); 1328 } 1329 } 1330 return val; 1331 }, 1332 1333 get date () { 1334 return this.currentDate 1335 }, 1336 1337 set view (val) { 1338 this.viewIndex = this.viewIndexes.indexOf(val); 1339 1340 if (this.viewIndex < 0) { 1341 return; 1342 } 1343 1344 this.prevView = this.currentView; 1345 this.currentView = val; 1346 1347 if (this.inited) { 1348 if (!this.views[val]) { 1349 this.views[val] = new $.fn.datepicker.Body(this, val, this.opts) 1350 } else { 1351 this.views[val]._render(); 1352 } 1353 1354 this.views[this.prevView].hide(); 1355 this.views[val].show(); 1356 this.nav._render(); 1357 1358 if (this.opts.onChangeView) { 1359 this.opts.onChangeView(val) 1360 } 1361 if (this.elIsInput && this.visible) this.setPosition(); 1362 } 1363 1364 return val 1365 }, 1366 1367 get view() { 1368 return this.currentView; 1369 }, 1370 1371 get cellType() { 1372 return this.view.substring(0, this.view.length - 1) 1373 }, 1374 1375 get minTime() { 1376 var min = datepicker.getParsedDate(this.minDate); 1377 return new Date(min.year, min.month, min.date).getTime() 1378 }, 1379 1380 get maxTime() { 1381 var max = datepicker.getParsedDate(this.maxDate); 1382 return new Date(max.year, max.month, max.date).getTime() 1383 }, 1384 1385 get curDecade() { 1386 return datepicker.getDecade(this.date) 1387 } 1388 }; 1389 1390 // Utils 1391 // ------------------------------------------------- 1392 1393 datepicker.getDaysCount = function (date) { 1394 return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); 1395 }; 1396 1397 datepicker.getParsedDate = function (date) { 1398 return { 1399 year: date.getFullYear(), 1400 month: date.getMonth(), 1401 fullMonth: (date.getMonth() + 1) < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1, // One based 1402 date: date.getDate(), 1403 fullDate: date.getDate() < 10 ? '0' + date.getDate() : date.getDate(), 1404 day: date.getDay(), 1405 hours: date.getHours(), 1406 fullHours: date.getHours() < 10 ? '0' + date.getHours() : date.getHours() , 1407 minutes: date.getMinutes(), 1408 fullMinutes: date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() 1409 } 1410 }; 1411 1412 datepicker.getDecade = function (date) { 1413 var firstYear = Math.floor(date.getFullYear() / 10) * 10; 1414 1415 return [firstYear, firstYear + 9]; 1416 }; 1417 1418 datepicker.template = function (str, data) { 1419 return str.replace(/#\{([\w]+)\}/g, function (source, match) { 1420 if (data[match] || data[match] === 0) { 1421 return data[match] 1422 } 1423 }); 1424 }; 1425 1426 datepicker.isSame = function (date1, date2, type) { 1427 if (!date1 || !date2) return false; 1428 var d1 = datepicker.getParsedDate(date1), 1429 d2 = datepicker.getParsedDate(date2), 1430 _type = type ? type : 'day', 1431 1432 conditions = { 1433 day: d1.date == d2.date && d1.month == d2.month && d1.year == d2.year, 1434 month: d1.month == d2.month && d1.year == d2.year, 1435 year: d1.year == d2.year 1436 }; 1437 1438 return conditions[_type]; 1439 }; 1440 1441 datepicker.less = function (dateCompareTo, date, type) { 1442 if (!dateCompareTo || !date) return false; 1443 return date.getTime() < dateCompareTo.getTime(); 1444 }; 1445 1446 datepicker.bigger = function (dateCompareTo, date, type) { 1447 if (!dateCompareTo || !date) return false; 1448 return date.getTime() > dateCompareTo.getTime(); 1449 }; 1450 1451 datepicker.getLeadingZeroNum = function (num) { 1452 return parseInt(num) < 10 ? '0' + num : num; 1453 }; 1454 1455 /** 1456 * Returns copy of date with hours and minutes equals to 0 1457 * @param date {Date} 1458 */ 1459 datepicker.resetTime = function (date) { 1460 if (typeof date != 'object') return; 1461 date = datepicker.getParsedDate(date); 1462 return new Date(date.year, date.month, date.date) 1463 }; 1464 1465 $.fn.datepicker = function ( options ) { 1466 return this.each(function () { 1467 if (!$.data(this, pluginName)) { 1468 $.data(this, pluginName, 1469 new Datepicker( this, options )); 1470 } else { 1471 var _this = $.data(this, pluginName); 1472 1473 _this.opts = $.extend(true, _this.opts, options); 1474 _this.update(); 1475 } 1476 }); 1477 }; 1478 1479 $.fn.datepicker.Constructor = Datepicker; 1480 1481 $.fn.datepicker.language = { 1482 ru: { 1483 days: ['ÐоÑкÑеÑенÑе', 'ÐонеделÑник', 'ÐÑоÑник', 'СÑеда', 'ЧеÑвеÑг', 'ÐÑÑниÑа', 'СÑббоÑа'], 1484 daysShort: ['ÐоÑ','Ðон','ÐÑо','СÑе','ЧеÑ','ÐÑÑ','СÑб'], 1485 daysMin: ['ÐÑ','Ðн','ÐÑ','СÑ','ЧÑ','ÐÑ','Сб'], 1486 months: ['ЯнваÑÑ', 'ФевÑалÑ', 'ÐаÑÑ', 'ÐпÑелÑ', 'Ðай', 'ÐÑнÑ', 'ÐÑлÑ', 'ÐвгÑÑÑ', 'СенÑÑбÑÑ', 'ÐкÑÑбÑÑ', 'ÐоÑбÑÑ', 'ÐекабÑÑ'], 1487 monthsShort: ['Янв', 'Фев', 'ÐаÑ', 'ÐпÑ', 'Ðай', 'ÐÑн', 'ÐÑл', 'Ðвг', 'Сен', 'ÐкÑ', 'ÐоÑ', 'Ðек'], 1488 today: 'СегоднÑ', 1489 clear: 'ÐÑиÑÑиÑÑ', 1490 dateFormat: 'dd.mm.yyyy', 1491 timeFormat: 'hh:ii', 1492 firstDay: 1 1493 } 1494 }; 1495 1496 $(function () { 1497 $(autoInitSelector).datepicker(); 1498 }) 1499 1500})(); 1501 1502;(function () { 1503 var templates = { 1504 days:'' + 1505 '<div class="datepicker--days datepicker--body">' + 1506 '<div class="datepicker--days-names"></div>' + 1507 '<div class="datepicker--cells datepicker--cells-days"></div>' + 1508 '</div>', 1509 months: '' + 1510 '<div class="datepicker--months datepicker--body">' + 1511 '<div class="datepicker--cells datepicker--cells-months"></div>' + 1512 '</div>', 1513 years: '' + 1514 '<div class="datepicker--years datepicker--body">' + 1515 '<div class="datepicker--cells datepicker--cells-years"></div>' + 1516 '</div>' 1517 }, 1518 datepicker = $.fn.datepicker, 1519 dp = datepicker.Constructor; 1520 1521 datepicker.Body = function (d, type, opts) { 1522 this.d = d; 1523 this.type = type; 1524 this.opts = opts; 1525 this.$el = $(''); 1526 1527 if (this.opts.onlyTimepicker) return; 1528 this.init(); 1529 }; 1530 1531 datepicker.Body.prototype = { 1532 init: function () { 1533 this._buildBaseHtml(); 1534 this._render(); 1535 1536 this._bindEvents(); 1537 }, 1538 1539 _bindEvents: function () { 1540 this.$el.on('click', '.datepicker--cell', $.proxy(this._onClickCell, this)); 1541 }, 1542 1543 _buildBaseHtml: function () { 1544 this.$el = $(templates[this.type]).appendTo(this.d.$content); 1545 this.$names = $('.datepicker--days-names', this.$el); 1546 this.$cells = $('.datepicker--cells', this.$el); 1547 }, 1548 1549 _getDayNamesHtml: function (firstDay, curDay, html, i) { 1550 curDay = curDay != undefined ? curDay : firstDay; 1551 html = html ? html : ''; 1552 i = i != undefined ? i : 0; 1553 1554 if (i > 7) return html; 1555 if (curDay == 7) return this._getDayNamesHtml(firstDay, 0, html, ++i); 1556 1557 html += '<div class="datepicker--day-name' + (this.d.isWeekend(curDay) ? " -weekend-" : "") + '">' + this.d.loc.daysMin[curDay] + '</div>'; 1558 1559 return this._getDayNamesHtml(firstDay, ++curDay, html, ++i); 1560 }, 1561 1562 _getCellContents: function (date, type) { 1563 var classes = "datepicker--cell datepicker--cell-" + type, 1564 currentDate = new Date(), 1565 parent = this.d, 1566 minRange = dp.resetTime(parent.minRange), 1567 maxRange = dp.resetTime(parent.maxRange), 1568 opts = parent.opts, 1569 d = dp.getParsedDate(date), 1570 render = {}, 1571 html = d.date; 1572 1573 switch (type) { 1574 case 'day': 1575 if (parent.isWeekend(d.day)) classes += " -weekend-"; 1576 if (d.month != this.d.parsedDate.month) { 1577 classes += " -other-month-"; 1578 if (!opts.selectOtherMonths) { 1579 classes += " -disabled-"; 1580 } 1581 if (!opts.showOtherMonths) html = ''; 1582 } 1583 break; 1584 case 'month': 1585 html = parent.loc[parent.opts.monthsField][d.month]; 1586 break; 1587 case 'year': 1588 var decade = parent.curDecade; 1589 html = d.year; 1590 if (d.year < decade[0] || d.year > decade[1]) { 1591 classes += ' -other-decade-'; 1592 if (!opts.selectOtherYears) { 1593 classes += " -disabled-"; 1594 } 1595 if (!opts.showOtherYears) html = ''; 1596 } 1597 break; 1598 } 1599 1600 if (opts.onRenderCell) { 1601 render = opts.onRenderCell(date, type) || {}; 1602 html = render.html ? render.html : html; 1603 classes += render.classes ? ' ' + render.classes : ''; 1604 } 1605 1606 if (opts.range) { 1607 if (dp.isSame(minRange, date, type)) classes += ' -range-from-'; 1608 if (dp.isSame(maxRange, date, type)) classes += ' -range-to-'; 1609 1610 if (parent.selectedDates.length == 1 && parent.focused) { 1611 if ( 1612 (dp.bigge
1612r(minRange, date) && dp.less(parent.focused, date)) || 1613 (dp.less(maxRange, date) && dp.bigger(parent.focused, date))) 1614 { 1615 classes += ' -in-range-' 1616 } 1617 1618 if (dp.less(maxRange, date) && dp.isSame(parent.focused, date)) { 1619 classes += ' -range-from-' 1620 } 1621 if (dp.bigger(minRange, date) && dp.isSame(parent.focused, date)) { 1622 classes += ' -range-to-' 1623 } 1624 1625 } else if (parent.selectedDates.length == 2) { 1626 if (dp.bigger(minRange, date) && dp.less(maxRange, date)) { 1627 classes += ' -in-range-' 1628 } 1629 } 1630 } 1631 1632 1633 if (dp.isSame(currentDate, date, type)) classes += ' -current-'; 1634 if (parent.focused && dp.isSame(date, parent.focused, type)) classes += ' -focus-'; 1635 if (parent._isSelected(date, type)) classes += ' -selected-'; 1636 if (!parent._isInRange(date, type) || render.disabled) classes += ' -disabled-'; 1637 1638 return { 1639 html: html, 1640 classes: classes 1641 } 1642 }, 1643 1644 /** 1645 * Calculates days number to render. Generates days html and returns it. 1646 * @param {object} date - Date object 1647 * @returns {string} 1648 * @private 1649 */ 1650 _getDaysHtml: function (date) { 1651 var totalMonthDays = dp.getDaysCount(date), 1652 firstMonthDay = new Date(date.getFullYear(), date.getMonth(), 1).getDay(), 1653 lastMonthDay = new Date(date.getFullYear(), date.getMonth(), totalMonthDays).getDay(), 1654 daysFromPevMonth = firstMonthDay - this.d.loc.firstDay, 1655 daysFromNextMonth = 6 - lastMonthDay + this.d.loc.firstDay; 1656 1657 daysFromPevMonth = daysFromPevMonth < 0 ? daysFromPevMonth + 7 : daysFromPevMonth; 1658 daysFromNextMonth = daysFromNextMonth > 6 ? daysFromNextMonth - 7 : daysFromNextMonth; 1659 1660 var startDayIndex = -daysFromPevMonth + 1, 1661 m, y, 1662 html = ''; 1663 1664 for (var i = startDayIndex, max = totalMonthDays + daysFromNextMonth; i <= max; i++) { 1665 y = date.getFullYear(); 1666 m = date.getMonth(); 1667 1668 html += this._getDayHtml(new Date(y, m, i)) 1669 } 1670 1671 return html; 1672 }, 1673 1674 _getDayHtml: function (date) { 1675 var content = this._getCellContents(date, 'day'); 1676 1677 return '<div class="' + content.classes + '" ' + 1678 'data-date="' + date.getDate() + '" ' + 1679 'data-month="' + date.getMonth() + '" ' + 1680 'data-year="' + date.getFullYear() + '">' + content.html + '</div>'; 1681 }, 1682 1683 /** 1684 * Generates months html 1685 * @param {object} date - date instance 1686 * @returns {string} 1687 * @private 1688 */ 1689 _getMonthsHtml: function (date) { 1690 var html = '', 1691 d = dp.getParsedDate(date), 1692 i = 0; 1693 1694 while(i < 12) { 1695 html += this._getMonthHtml(new Date(d.year, i)); 1696 i++ 1697 } 1698 1699 return html; 1700 }, 1701 1702 _getMonthHtml: function (date) { 1703 var content = this._getCellContents(date, 'month'); 1704 1705 return '<div class="' + content.classes + '" data-month="' + date.getMonth() + '">' + content.html + '</div>' 1706 }, 1707 1708 _getYearsHtml: function (date) { 1709 var d = dp.getParsedDate(date), 1710 decade = dp.getDecade(date), 1711 firstYear = decade[0] - 1, 1712 html = '', 1713 i = firstYear; 1714 1715 for (i; i <= decade[1] + 1; i++) { 1716 html += this._getYearHtml(new Date(i , 0)); 1717 } 1718 1719 return html; 1720 }, 1721 1722 _getYearHtml: function (date) { 1723 var content = this._getCellContents(date, 'year'); 1724 1725 return '<div class="' + content.classes + '" data-year="' + date.getFullYear() + '">' + content.html + '</div>' 1726 }, 1727 1728 _renderTypes: { 1729 days: function () { 1730 var dayNames = this._getDayNamesHtml(this.d.loc.firstDay), 1731 days = this._getDaysHtml(this.d.currentDate); 1732 1733 this.$cells.html(days);
1734 this.$names.html(dayNames) 1735 }, 1736 months: function () { 1737 var html = this._getMonthsHtml(this.d.currentDate); 1738 1739 this.$cells.html(html) 1740 }, 1741 years: function () { 1742 var html = this._getYearsHtml(this.d.currentDate); 1743 1744 this.$cells.html(html) 1745 } 1746 }, 1747 1748 _render: function () { 1749 if (this.opts.onlyTimepicker) return; 1750 this._renderTypes[this.type].bind(this)(); 1751 }, 1752 1753 _update: function () { 1754 var $cells = $('.datepicker--cell', this.$cells), 1755 _this = this, 1756 classes, 1757 $cell, 1758 date; 1759 $cells.each(function (cell, i) { 1760 $cell = $(this); 1761 date = _this.d._getDateFromCell($(this)); 1762 classes = _this._getCellContents(date, _this.d.cellType); 1763 $cell.attr('class',classes.classes) 1764 }); 1765 }, 1766 1767 show: function () { 1768 if (this.opts.onlyTimepicker) return; 1769 this.$el.addClass('active'); 1770 this.acitve = true; 1771 }, 1772 1773 hide: function () { 1774 this.$el.removeClass('active'); 1775 this.active = false; 1776 }, 1777 1778 // Events 1779 // ------------------------------------------------- 1780 1781 _handleClick: function (el) { 1782 var date = el.data('date') || 1, 1783 month = el.data('month') || 0, 1784 year = el.data('year') || this.d.parsedDate.year, 1785 dp = this.d; 1786 // Change view if min view does not reach yet 1787 if (dp.view != this.opts.minView) { 1788 dp.down(new Date(year, month, date)); 1789 return; 1790 } 1791 // Select date if min view is reached 1792 var selectedDate = new Date(year, month, date), 1793 alreadySelected = this.d._isSelected(selectedDate, this.d.cellType); 1794 1795 if (!alreadySelected) { 1796 dp._trigger('clickCell', selectedDate); 1797 return; 1798 } 1799 1800 dp._handleAlreadySelectedDates.bind(dp, alreadySelected, selectedDate)(); 1801 1802 }, 1803 1804 _onClickCell: function (e) { 1805 var $el = $(e.target).closest('.datepicker--cell'); 1806 1807 if ($el.hasClass('-disabled-')) return; 1808 1809 this._handleClick.bind(this)($el); 1810 } 1811 }; 1812})(); 1813 1814;(function () { 1815 var template = '' + 1816 '<div class="datepicker--nav-action" data-action="prev">#{prevHtml}</div>' + 1817 '<div class="datepicker--nav-title">#{title}</div>' + 1818 '<div class="datepicker--nav-action" data-action="next">#{nextHtml}</div>', 1819 buttonsContainerTemplate = '<div class="datepicker--buttons"></div>', 1820 button = '<span class="datepicker--button" data-action="#{action}">#{label}</span>', 1821 datepicker = $.fn.datepicker, 1822 dp = datepicker.Constructor; 1823 1824 datepicker.Navigation = function (d, opts) { 1825 this.d = d; 1826 this.opts = opts; 1827 1828 this.$buttonsContainer = ''; 1829 1830 this.init(); 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 this.d.$datepicker.on('click', '.datepicker--button', $.proxy(this._onClickNavButton, this)); 1843 }, 1844 1845 _buildBaseHtml: function () { 1846 if (!this.opts.onlyTimepicker) { 1847 this._render(); 1848 } 1849 this._addButtonsIfNeed(); 1850 }, 1851 1852 _addButtonsIfNeed: function () { 1853 if (this.opts.todayButton) { 1854 this._addButton('today') 1855 } 1856 if (this.opts.clearButton) { 1857 this._addButton('clear') 1858 } 1859 1860 if (this.opts.closeButton) { 1861 this._addButton('close') 1862 } 1863 }, 1864 1865 _render: function () { 1866 var title = this._getTitle(this.d.currentDate), 1867 html = dp.template(template, $.extend({title: title}, this.opts)); 1868 this.d.$nav.html(html); 1869 if (this.d.view == 'years') { 1870 $('.datepicker--nav-title', this.d.$nav).addClass('-disabled-'); 1871 } 1872 this.setNavStatus();
1873 }, 1874 1875 _getTitle: function (date) { 1876 return this.d.formatDate(this.opts.navTitles[this.d.view], date) 1877 }, 1878 1879 _addButton: function (type) { 1880 if (!this.$buttonsContainer.length) { 1881 this._addButtonsContainer(); 1882 } 1883 1884 var data = { 1885 action: type, 1886 label: this.d.loc[type] 1887 }, 1888 html = dp.template(button, data); 1889 1890 if ($('[data-action=' + type + ']', this.$buttonsContainer).length) return; 1891 this.$buttonsContainer.append(html); 1892 }, 1893 1894 _addButtonsContainer: function () { 1895 this.d.$datepicker.append(buttonsContainerTemplate); 1896 this.$buttonsContainer = $('.datepicker--buttons', this.d.$datepicker); 1897 }, 1898 1899 setNavStatus: function () { 1900 if (!(this.opts.minDate || this.opts.maxDate) || !this.opts.disableNavWhenOutOfRange) return; 1901 1902 var date = this.d.parsedDate, 1903 m = date.month, 1904 y = date.year, 1905 d = date.date; 1906 1907 switch (this.d.view) { 1908 case 'days': 1909 if (!this.d._isInRange(new Date(y, m-1, 1), 'month')) { 1910 this._disableNav('prev') 1911 } 1912 if (!this.d._isInRange(new Date(y, m+1, 1), 'month')) { 1913 this._disableNav('next') 1914 } 1915 break; 1916 case 'months': 1917 if (!this.d._isInRange(new Date(y-1, m, d), 'year')) { 1918 this._disableNav('prev') 1919 } 1920 if (!this.d._isInRange(new Date(y+1, m, d), 'year')) { 1921 this._disableNav('next') 1922 } 1923 break; 1924 case 'years': 1925 var decade = dp.getDecade(this.d.date); 1926 if (!this.d._isInRange(new Date(decade[0] - 1, 0, 1), 'year')) { 1927 this._disableNav('prev') 1928 } 1929 if (!this.d._isInRange(new Date(decade[1] + 1, 0, 1), 'year')) { 1930 this._disableNav('next') 1931 } 1932 break; 1933 } 1934 }, 1935 1936 _disableNav: function (nav) { 1937 $('[data-action="' + nav + '"]', this.d.$nav).addClass('-disabled-') 1938 }, 1939 1940 _activateNav: function (nav) { 1941 $('[data-action="' + nav + '"]', this.d.$nav).removeClass('-disabled-') 1942 }, 1943 1944 _onClickNavButton: function (e) { 1945 var $el = $(e.target).closest('[data-action]'), 1946 action = $el.data('action'); 1947 1948 this.d[action](); 1949 }, 1950 1951 _onClickNavTitle: function (e) { 1952 if ($(e.target).hasClass('-disabled-')) return; 1953 1954 if (this.d.view == 'days') { 1955 return this.d.view = 'months' 1956 } 1957 1958 this.d.view = 'years'; 1959 } 1960 } 1961 1962})(); 1963 1964;(function () { 1965 var template = '<div class="datepicker--time">' + 1966 '<div class="datepicker--time-current">' + 1967 ' <span class="datepicker--time-current-hours">#{hourVisible}</span>' + 1968 ' <span class="datepicker--time-current-colon">:</span>' + 1969 ' <span class="datepicker--time-current-minutes">#{minValue}</span>' + 1970 '</div>' + 1971 '<div class="datepicker--time-sliders">' + 1972 ' <div class="datepicker--time-row">' + 1973 ' <input type="range" name="hours" value="#{hourValue}" min="#{hourMin}" max="#{hourMax}" step="#{hourStep}"/>' + 1974 ' </div>' + 1975 ' <div class="datepicker--time-row">' + 1976 ' <input type="range" name="minutes" value="#{minValue}" min="#{minMin}" max="#{minMax}" step="#{minStep}"/>' + 1977 ' </div>' + 1978 '</div>' + 1979 '</div>', 1980 datepicker = $.fn.datepicker, 1981 dp = datepicker.Constructor; 1982 1983 datepicker.Timepicker = function (inst, opts) { 1984 this.d = inst; 1985 this.opts = opts; 1986 1987 this.init(); 1988 }; 1989 1990 datepicker.Timepicker.prototype = { 1991 init: function () { 1992 var input = 'input'; 1993 this._setTime(this.d.date); 1994 this._buildHTML(); 1995
1996 if (navigator.userAgent.match(/trident/gi)) { 1997 input = 'change'; 1998 } 1999 2000 this.d.$el.on('selectDate', this._onSelectDate.bind(this)); 2001 this.$ranges.on(input, this._onChangeRange.bind(this)); 2002 this.$ranges.on('mouseup', this._onMouseUpRange.bind(this)); 2003 this.$ranges.on('mousemove focus ', this._onMouseEnterRange.bind(this)); 2004 this.$ranges.on('mouseout blur', this._onMouseOutRange.bind(this)); 2005 }, 2006 2007 _setTime: function (date) { 2008 var _date = dp.getParsedDate(date); 2009 2010 this._handleDate(date); 2011 this.hours = _date.hours < this.minHours ? this.minHours : _date.hours; 2012 this.minutes = _date.minutes < this.minMinutes ? this.minMinutes : _date.minutes; 2013 }, 2014 2015 /** 2016 * Sets minHours and minMinutes from date (usually it's a minDate) 2017 * Also changes minMinutes if current hours are bigger then @date hours 2018 * @param date {Date} 2019 * @private 2020 */ 2021 _setMinTimeFromDate: function (date) { 2022 this.minHours = date.getHours(); 2023 this.minMinutes = date.getMinutes(); 2024 2025 // If, for example, min hours are 10, and current hours are 12, 2026 // update minMinutes to default value, to be able to choose whole range of values 2027 if (this.d.lastSelectedDate) { 2028 if (this.d.lastSelectedDate.getHours() > date.getHours()) { 2029 this.minMinutes = this.opts.minMinutes; 2030 } 2031 } 2032 }, 2033 2034 _setMaxTimeFromDate: function (date) { 2035 this.maxHours = date.getHours(); 2036 this.maxMinutes = date.getMinutes(); 2037 2038 if (this.d.lastSelectedDate) { 2039 if (this.d.lastSelectedDate.getHours() < date.getHours()) { 2040 this.maxMinutes = this.opts.maxMinutes; 2041 } 2042 } 2043 }, 2044 2045 _setDefaultMinMaxTime: function () { 2046 var maxHours = 23, 2047 maxMinutes = 59, 2048 opts = this.opts; 2049 2050 this.minHours = opts.minHours < 0 || opts.minHours > maxHours ? 0 : opts.minHours; 2051 this.minMinutes = opts.minMinutes < 0 || opts.minMinutes > maxMinutes ? 0 : opts.minMinutes; 2052 this.maxHours = opts.maxHours < 0 || opts.maxHours > maxHours ? maxHours : opts.maxHours; 2053 this.maxMinutes = opts.maxMinutes < 0 || opts.maxMinutes > maxMinutes ? maxMinutes : opts.maxMinutes; 2054 }, 2055 2056 /** 2057 * Looks for min/max hours/minutes and if current values 2058 * are out of range sets valid values. 2059 * @private 2060 */ 2061 _validateHoursMinutes: function (date) { 2062 if (this.hours < this.minHours) { 2063 this.hours = this.minHours; 2064 } else if (this.hours > this.maxHours) { 2065 this.hours = this.maxHours; 2066 } 2067 2068 if (this.minutes < this.minMinutes) { 2069 this.minutes = this.minMinutes; 2070 } else if (this.minutes > this.maxMinutes) { 2071 this.minutes = this.maxMinutes; 2072 } 2073 }, 2074 2075 _buildHTML: function () { 2076 var lz = dp.getLeadingZeroNum, 2077 data = { 2078 hourMin: this.minHours, 2079 hourMax: lz(this.maxHours), 2080 hourStep: this.opts.hoursStep, 2081 hourValue: this.hours, 2082 hourVisible: lz(this.displayHours), 2083 minMin: this.minMinutes, 2084 minMax: lz(this.maxMinutes), 2085 minStep: this.opts.minutesStep, 2086 minValue: lz(this.minutes) 2087 }, 2088 _template = dp.template(template, data); 2089 2090 this.$timepicker = $(_template).appendTo(this.d.$datepicker); 2091 this.$ranges = $('[type="range"]', this.$timepicker); 2092 this.$hours = $('[name="hours"]', this.$timepicker); 2093 this.$minutes = $('[name="minutes"]', this.$timepicker); 2094 this.$hoursText = $('.datepicker--time-current-hours', this.$timepicker); 2095 this.$minutesText = $('.datepicker--time-current-minutes', this.$timepicker); 2096 2097 if (this.d.ampm) { 2098 this.$ampm = $('<span class="datepicker--time-current-ampm">') 2099 .appendTo($('.datepicker--time-current', this.$timepicker)) 2100 .html(this.dayPeriod); 2101 2102 this.$timepicker.addClass('-am-pm-'); 2103 } 2104 }, 2105 2106 _updateCurrentTime: function () { 2107 var h = dp.getLeadingZeroNum(this.displayHours), 2108 m = dp.getLeadingZeroNum(this.minutes); 2109 2110 this.$hoursText.html(h); 2111 this.$minutesText.html(m); 2112 2113 if (this.d.ampm) { 2114 this.$ampm.html(this.dayPeriod); 2115 } 2116 }, 2117 2118 _updateRanges: function () { 2119 this.$hours.attr({ 2120 min: this.minHours, 2121 max: this.maxHours 2122 }).val(this.hours); 2123 2124 this.$minutes.attr({ 2125 min: this.minMinutes, 2126 max: this.maxMinutes 2127 }).val(this.minutes) 2128 }, 2129 2130 /** 2131 * Sets minHours, minMinutes etc. from date. If date is not passed, than sets 2132 * values from options 2133 * @param [date] {object} - Date object, to get values from 2134 * @private 2135 */ 2136 _handleDate: function (date) { 2137 this._setDefaultMinMaxTime(); 2138 if (date) { 2139 if (dp.isSame(date, this.d.opts.minDate)) { 2140 this._setMinTimeFromDate(this.d.opts.minDate); 2141 } else if (dp.isSame(date, this.d.opts.maxDate)) { 2142 this._setMaxTimeFromDate(this.d.opts.maxDate); 2143 } 2144 } 2145
2146 this._validateHoursMinutes(date); 2147 }, 2148 2149 update: function () { 2150 this._updateRanges(); 2151 this._updateCurrentTime(); 2152 }, 2153 2154 /** 2155 * Calculates valid hour value to display in text input and datepicker's body. 2156 * @param date {Date|Number} - date or hours 2157 * @param [ampm] {Boolean} - 12 hours mode 2158 * @returns {{hours: *, dayPeriod: string}} 2159 * @private 2160 */ 2161 _getValidHoursFromDate: function (date, ampm) { 2162 var d = date, 2163 hours = date; 2164 2165 if (date instanceof Date) { 2166 d = dp.getParsedDate(date); 2167 hours = d.hours; 2168 } 2169 2170 var _ampm = ampm || this.d.ampm, 2171 dayPeriod = 'am'; 2172 2173 if (_ampm) { 2174 switch(true) { 2175 case hours == 0: 2176 hours = 12; 2177 break; 2178 case hours == 12: 2179 dayPeriod = 'pm'; 2180 break; 2181 case hours > 11: 2182 hours = hours - 12; 2183 dayPeriod = 'pm'; 2184 break; 2185 default: 2186 break; 2187 } 2188 } 2189 2190 return { 2191 hours: hours, 2192 dayPeriod: dayPeriod 2193 } 2194 }, 2195 2196 set hours (val) { 2197 this._hours = val; 2198 2199 var displayHours = this._getValidHoursFromDate(val); 2200 2201 this.displayHours = displayHours.hours; 2202 this.dayPeriod = displayHours.dayPeriod; 2203 }, 2204 2205 get hours() { 2206 return this._hours; 2207 }, 2208 2209 // Events 2210 // ------------------------------------------------- 2211 2212 _onChangeRange: function (e) { 2213 var $target = $(e.target), 2214 name = $target.attr('name'); 2215 2216 this.d.timepickerIsActive = true; 2217 2218 this[name] = $target.val(); 2219 this._updateCurrentTime(); 2220 this.d._trigger('timeChange', [this.hours, this.minutes]); 2221 2222 this._handleDate(this.d.lastSelectedDate); 2223 this.update() 2224 }, 2225 2226 _onSelectDate: function (e, data) { 2227 this._handleDate(data); 2228 this.update(); 2229 }, 2230 2231 _onMouseEnterRange: function (e) { 2232 var name = $(e.target).attr('name'); 2233 $('.datepicker--time-current-' + name, this.$timepicker).addClass('-focus-'); 2234 }, 2235 2236 _onMouseOutRange: function (e) { 2237 var name = $(e.target).attr('name'); 2238 if (this.d.inFocus) return; // Prevent removing focus when mouse out of range slider 2239 $('.datepicker--time-current-' + name, this.$timepicker).removeClass('-focus-'); 2240 }, 2241 2242 _onMouseUpRange: function (e) { 2243 this.d.timepickerIsActive = false; 2244 } 2245 }; 2246})(); 2247 })(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.