Licitator 1.0
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2224 lines
75 KiB

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