datepicker.js 76 KB

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