calendar.js 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141
  1. /*!
  2. * Pikaday
  3. *
  4. * Copyright © 2014 David Bushell | BSD & MIT license | https://github.com/dbushell/Pikaday
  5. */
  6. var pickers = {};
  7. var clLangs = {
  8. ua: {
  9. previousMonth : 'Попередній місяць',
  10. nextMonth : 'Наступний місяць',
  11. months : ['Січень','Лютий','Березень','Квітень','Травень','Червень','Липень','Серпень','Вересень','Жовтень','Листопад','Грудень'],
  12. weekdays : ['Неділя','Понеділок','Вівторок','Середа','Четвер','П’ятниця','Субота'],
  13. weekdaysShort : ['Нд','Пн','Вв','Ср','Чт','Пт','Сб']
  14. },
  15. ru: {
  16. previousMonth : 'Предыдущий месяц',
  17. nextMonth : 'Следующий месяц',
  18. months : ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'],
  19. weekdays : ['Воскресенье','Понедельник','Вторник','Среда','Четверг','Пятница','Суббота'],
  20. weekdaysShort : ['Вс','Пн','Вт','Ср','Чт','Пт','Сб']
  21. },
  22. en: {
  23. previousMonth : 'Previous Month',
  24. nextMonth : 'Next Month',
  25. months : ['January','February','March','April','May','June','July','August','September','October','November','December'],
  26. weekdays : ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
  27. weekdaysShort : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
  28. }
  29. };
  30. function getDateParamBySign(date,sign) {
  31. sign = sign.toUpperCase();
  32. var param;
  33. switch(sign) {
  34. case "YYYY":
  35. param = date.getFullYear().toString();break;
  36. case "YY":
  37. param = date.getFullYear().toString().substr(2,2);break;
  38. case "MM":
  39. param = (date.getMonth()+1);
  40. param = (param >= 10) ? param.toString() : ("0"+param.toString());
  41. break;
  42. case "DD":
  43. param = (date.getDate() >= 10) ? date.getDate().toString() : ("0"+date.getDate().toString());
  44. break;
  45. default:
  46. param = date.toDateString();
  47. }
  48. return param;
  49. }
  50. function formatter(date, format) {
  51. date = date || new Date();
  52. format = format || "YYYY-MM-DD";
  53. var signs = format.match(/(Y{2,4})|(M{2})|(D{2})/g);
  54. var params = [];
  55. var reStr = '';
  56. for(var i=0; i<signs.length; ++i) {
  57. params.push(getDateParamBySign(date,signs[i]));
  58. reStr += ((i+1) != signs.length) ? signs[i] + "(.)" : signs[i];
  59. }
  60. var re = new RegExp(reStr,'g');
  61. var delimiters = re.exec(format);
  62. delimiters.splice(0,1);
  63. var value = "";
  64. for(i=0; i<params.length; i++) {
  65. value += ((i+1) != params.length) ? (params[i] + delimiters[i]) : params[i];
  66. }
  67. return value;
  68. }
  69. (function (root, factory)
  70. {
  71. 'use strict';
  72. var moment;
  73. if (typeof exports === 'object') {
  74. // CommonJS module
  75. // Load moment.js as an optional dependency
  76. try { moment = require('moment'); } catch (e) {}
  77. module.exports = factory(moment);
  78. } else if (typeof define === 'function' && define.amd) {
  79. // AMD. Register as an anonymous module.
  80. define(function (req)
  81. {
  82. // Load moment.js as an optional dependency
  83. var id = 'moment';
  84. try { moment = req(id); } catch (e) {}
  85. return factory(moment);
  86. });
  87. } else {
  88. root.Pikaday = factory(root.moment);
  89. }
  90. }(this, function (moment)
  91. {
  92. 'use strict';
  93. /**
  94. * feature detection and helper functions
  95. */
  96. var hasMoment = typeof moment === 'function',
  97. hasEventListeners = !!window.addEventListener,
  98. document = window.document,
  99. sto = window.setTimeout,
  100. addEvent = function(el, e, callback, capture)
  101. {
  102. if (hasEventListeners) {
  103. el.addEventListener(e, callback, !!capture);
  104. } else {
  105. el.attachEvent('on' + e, callback);
  106. }
  107. },
  108. removeEvent = function(el, e, callback, capture)
  109. {
  110. if (hasEventListeners) {
  111. el.removeEventListener(e, callback, !!capture);
  112. } else {
  113. el.detachEvent('on' + e, callback);
  114. }
  115. },
  116. fireEvent = function(el, eventName, data)
  117. {
  118. var ev;
  119. if (document.createEvent) {
  120. ev = document.createEvent('HTMLEvents');
  121. ev.initEvent(eventName, true, false);
  122. ev = extend(ev, data);
  123. el.dispatchEvent(ev);
  124. } else if (document.createEventObject) {
  125. ev = document.createEventObject();
  126. ev = extend(ev, data);
  127. el.fireEvent('on' + eventName, ev);
  128. }
  129. },
  130. trim = function(str)
  131. {
  132. return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g,'');
  133. },
  134. hasClass = function(el, cn)
  135. {
  136. return (' ' + el.className + ' ').indexOf(' ' + cn + ' ') !== -1;
  137. },
  138. addClass = function(el, cn)
  139. {
  140. if (!hasClass(el, cn)) {
  141. el.className = (el.className === '') ? cn : el.className + ' ' + cn;
  142. }
  143. },
  144. removeClass = function(el, cn)
  145. {
  146. el.className = trim((' ' + el.className + ' ').replace(' ' + cn + ' ', ' '));
  147. },
  148. isArray = function(obj)
  149. {
  150. return (/Array/).test(Object.prototype.toString.call(obj));
  151. },
  152. isDate = function(obj)
  153. {
  154. return (/Date/).test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime());
  155. },
  156. isWeekend = function(date)
  157. {
  158. var day = date.getDay();
  159. return day === 0 || day === 6;
  160. },
  161. isLeapYear = function(year)
  162. {
  163. // solution by Matti Virkkunen: http://stackoverflow.com/a/4881951
  164. return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
  165. },
  166. getDaysInMonth = function(year, month)
  167. {
  168. return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
  169. },
  170. setToStartOfDay = function(date)
  171. {
  172. if (isDate(date)) date.setHours(0,0,0,0);
  173. },
  174. compareDates = function(a,b)
  175. {
  176. // weak date comparison (use setToStartOfDay(date) to ensure correct result)
  177. return a.getTime() === b.getTime();
  178. },
  179. extend = function(to, from, overwrite)
  180. {
  181. var prop, hasProp;
  182. for (prop in from) {
  183. hasProp = to[prop] !== undefined;
  184. if (hasProp && typeof from[prop] === 'object' && from[prop] !== null && from[prop].nodeName === undefined) {
  185. if (isDate(from[prop])) {
  186. if (overwrite) {
  187. to[prop] = new Date(from[prop].getTime());
  188. }
  189. }
  190. else if (isArray(from[prop])) {
  191. if (overwrite) {
  192. to[prop] = from[prop].slice(0);
  193. }
  194. } else {
  195. to[prop] = extend({}, from[prop], overwrite);
  196. }
  197. } else if (overwrite || !hasProp) {
  198. to[prop] = from[prop];
  199. }
  200. }
  201. return to;
  202. },
  203. adjustCalendar = function(calendar) {
  204. if (calendar.month < 0) {
  205. calendar.year -= Math.ceil(Math.abs(calendar.month)/12);
  206. calendar.month += 12;
  207. }
  208. if (calendar.month > 11) {
  209. calendar.year += Math.floor(Math.abs(calendar.month)/12);
  210. calendar.month -= 12;
  211. }
  212. return calendar;
  213. },
  214. /**
  215. * defaults and localisation
  216. */
  217. defaults = {
  218. // bind the picker to a form field
  219. field: null,
  220. // automatically show/hide the picker on `field` focus (default `true` if `field` is set)
  221. bound: undefined,
  222. // position of the datepicker, relative to the field (default to bottom & left)
  223. // ('bottom' & 'left' keywords are not used, 'top' & 'right' are modifier on the bottom/left position)
  224. position: 'bottom left',
  225. // automatically fit in the viewport even if it means repositioning from the position option
  226. reposition: true,
  227. // the default output format for `.toString()` and `field` value
  228. format: 'YYYY-MM-DD',
  229. // the initial date to view when first opened
  230. defaultDate: null,
  231. // make the `defaultDate` the initial selected value
  232. setDefaultDate: false,
  233. // first day of week (0: Sunday, 1: Monday etc)
  234. firstDay: 0,
  235. // the minimum/earliest date that can be selected
  236. minDate: null,
  237. // the maximum/latest date that can be selected
  238. maxDate: null,
  239. // number of years either side, or array of upper/lower range
  240. yearRange: 10,
  241. // show week numbers at head of row
  242. showWeekNumber: false,
  243. // used internally (don't config outside)
  244. minYear: 0,
  245. maxYear: 9999,
  246. minMonth: undefined,
  247. maxMonth: undefined,
  248. startRange: null,
  249. endRange: null,
  250. isRTL: false,
  251. // Additional text to append to the year in the calendar title
  252. yearSuffix: '',
  253. // Render the month after year in the calendar title
  254. showMonthAfterYear: false,
  255. // how many months are visible
  256. numberOfMonths: 1,
  257. // when numberOfMonths is used, this will help you to choose where the main calendar will be (default `left`, can be set to `right`)
  258. // only used for the first display or when a selected date is not visible
  259. mainCalendar: 'left',
  260. // Specify a DOM element to render the calendar in
  261. container: undefined,
  262. // internationalization
  263. i18n: clLangs.en,
  264. // Theme Classname
  265. theme: null,
  266. // callback function
  267. onSelect: null,
  268. onOpen: null,
  269. onClose: null,
  270. onDraw: null
  271. },
  272. /**
  273. * templating functions to abstract HTML rendering
  274. */
  275. renderDayName = function(opts, day, abbr)
  276. {
  277. day += opts.firstDay;
  278. while (day >= 7) {
  279. day -= 7;
  280. }
  281. return abbr ? opts.i18n.weekdaysShort[day] : opts.i18n.weekdays[day];
  282. },
  283. renderDay = function(opts)
  284. {
  285. if (opts.isEmpty) {
  286. return '<td class="is-empty"></td>';
  287. }
  288. var arr = [];
  289. if (opts.isDisabled) {
  290. arr.push('is-disabled');
  291. }
  292. if (opts.isToday) {
  293. arr.push('is-today');
  294. }
  295. if (opts.isSelected) {
  296. arr.push('is-selected');
  297. }
  298. if (opts.isInRange) {
  299. arr.push('is-inrange');
  300. }
  301. if (opts.isStartRange) {
  302. arr.push('is-startrange');
  303. }
  304. if (opts.isEndRange) {
  305. arr.push('is-endrange');
  306. }
  307. return '<td data-day="' + opts.day + '" class="' + arr.join(' ') + '">' +
  308. '<button class="pika-button pika-day" type="button" ' +
  309. 'data-pika-year="' + opts.year + '" data-pika-month="' + opts.month + '" data-pika-day="' + opts.day + '">' +
  310. opts.day +
  311. '</button>' +
  312. '</td>';
  313. },
  314. renderWeek = function (d, m, y) {
  315. // Lifted from http://javascript.about.com/library/blweekyear.htm, lightly modified.
  316. var onejan = new Date(y, 0, 1),
  317. weekNum = Math.ceil((((new Date(y, m, d) - onejan) / 86400000) + onejan.getDay()+1)/7);
  318. return '<td class="pika-week">' + weekNum + '</td>';
  319. },
  320. renderRow = function(days, isRTL)
  321. {
  322. return '<tr>' + (isRTL ? days.reverse() : days).join('') + '</tr>';
  323. },
  324. renderBody = function(rows)
  325. {
  326. return '<tbody>' + rows.join('') + '</tbody>';
  327. },
  328. renderHead = function(opts)
  329. {
  330. var i, arr = [];
  331. if (opts.showWeekNumber) {
  332. arr.push('<th></th>');
  333. }
  334. for (i = 0; i < 7; i++) {
  335. arr.push('<th scope="col"><abbr title="' + renderDayName(opts, i) + '">' + renderDayName(opts, i, true) + '</abbr></th>');
  336. }
  337. return '<thead>' + (opts.isRTL ? arr.reverse() : arr).join('') + '</thead>';
  338. },
  339. renderTitle = function(instance, c, year, month, refYear)
  340. {
  341. var i, j, arr,
  342. opts = instance._o,
  343. isMinYear = year === opts.minYear,
  344. isMaxYear = year === opts.maxYear,
  345. html = '<div class="pika-title">',
  346. monthHtml,
  347. yearHtml,
  348. prev = true,
  349. next = true;
  350. for (arr = [], i = 0; i < 12; i++) {
  351. arr.push('<option value="' + (year === refYear ? i - c : 12 + i - c) + '"' +
  352. (i === month ? ' selected': '') +
  353. ((isMinYear && i < opts.minMonth) || (isMaxYear && i > opts.maxMonth) ? 'disabled' : '') + '>' +
  354. opts.i18n.months[i] + '</option>');
  355. }
  356. monthHtml = '<div class="pika-label">' + opts.i18n.months[month] + '<select class="pika-select pika-select-month" tabindex="-1">' + arr.join('') + '</select></div>';
  357. if (isArray(opts.yearRange)) {
  358. i = opts.yearRange[0];
  359. j = opts.yearRange[1] + 1;
  360. } else {
  361. i = year - opts.yearRange;
  362. j = 1 + year + opts.yearRange;
  363. }
  364. for (arr = []; i < j && i <= opts.maxYear; i++) {
  365. if (i >= opts.minYear) {
  366. arr.push('<option value="' + i + '"' + (i === year ? ' selected': '') + '>' + (i) + '</option>');
  367. }
  368. }
  369. yearHtml = '<div class="pika-label">' + year + opts.yearSuffix + '<select class="pika-select pika-select-year" tabindex="-1">' + arr.join('') + '</select></div>';
  370. if (opts.showMonthAfterYear) {
  371. html += yearHtml + monthHtml;
  372. } else {
  373. html += monthHtml + yearHtml;
  374. }
  375. if (isMinYear && (month === 0 || opts.minMonth >= month)) {
  376. prev = false;
  377. }
  378. if (isMaxYear && (month === 11 || opts.maxMonth <= month)) {
  379. next = false;
  380. }
  381. if (c === 0) {
  382. html += '<button class="pika-prev' + (prev ? '' : ' is-disabled') + '" type="button">' + opts.i18n.previousMonth + '</button>';
  383. }
  384. if (c === (instance._o.numberOfMonths - 1) ) {
  385. html += '<button class="pika-next' + (next ? '' : ' is-disabled') + '" type="button">' + opts.i18n.nextMonth + '</button>';
  386. }
  387. return html += '</div>';
  388. },
  389. renderTable = function(opts, data)
  390. {
  391. return '<table cellpadding="0" cellspacing="0" class="pika-table">' + renderHead(opts) + renderBody(data) + '</table>';
  392. },
  393. /**
  394. * Pikaday constructor
  395. */
  396. Pikaday = function(options)
  397. {
  398. var self = this,
  399. opts = self.config(options);
  400. self._onMouseDown = function(e)
  401. {
  402. if (!self._v) {
  403. return;
  404. }
  405. e = e || window.event;
  406. var target = e.target || e.srcElement;
  407. if (!target) {
  408. return;
  409. }
  410. if (!hasClass(target.parentNode, 'is-disabled')) {
  411. if (hasClass(target, 'pika-button') && !hasClass(target, 'is-empty')) {
  412. self.setDate(new Date(target.getAttribute('data-pika-year'), target.getAttribute('data-pika-month'), target.getAttribute('data-pika-day')));
  413. if (opts.bound) {
  414. sto(function() {
  415. self.hide();
  416. if (opts.field) {
  417. opts.field.blur();
  418. }
  419. }, 100);
  420. }
  421. }
  422. else if (hasClass(target, 'pika-prev')) {
  423. self.prevMonth();
  424. }
  425. else if (hasClass(target, 'pika-next')) {
  426. self.nextMonth();
  427. }
  428. }
  429. if (!hasClass(target, 'pika-select')) {
  430. // if this is touch event prevent mouse events emulation
  431. if (e.preventDefault) {
  432. e.preventDefault();
  433. } else {
  434. e.returnValue = false;
  435. return false;
  436. }
  437. } else {
  438. self._c = true;
  439. }
  440. };
  441. self._onChange = function(e)
  442. {
  443. e = e || window.event;
  444. var target = e.target || e.srcElement;
  445. if (!target) {
  446. return;
  447. }
  448. if (hasClass(target, 'pika-select-month')) {
  449. self.gotoMonth(target.value);
  450. }
  451. else if (hasClass(target, 'pika-select-year')) {
  452. self.gotoYear(target.value);
  453. }
  454. };
  455. self._onInputChange = function(e)
  456. {
  457. var date;
  458. if (e.firedBy === self) {
  459. return;
  460. }
  461. if (hasMoment) {
  462. date = moment(opts.field.value, opts.format);
  463. date = (date && date.isValid()) ? date.toDate() : null;
  464. }
  465. else {
  466. date = new Date(Date.parse(opts.field.value));
  467. }
  468. if (isDate(date)) {
  469. self.setDate(date);
  470. }
  471. if (!self._v) {
  472. self.show();
  473. }
  474. };
  475. self._onInputFocus = function()
  476. {
  477. self.show();
  478. };
  479. self._onInputClick = function()
  480. {
  481. self.show();
  482. };
  483. self._onInputBlur = function()
  484. {
  485. // IE allows pika div to gain focus; catch blur the input field
  486. var pEl = document.activeElement;
  487. do {
  488. if (hasClass(pEl, 'pika-single')) {
  489. return;
  490. }
  491. }
  492. while ((pEl = pEl.parentNode));
  493. if (!self._c) {
  494. self._b = sto(function() {
  495. self.hide();
  496. }, 50);
  497. }
  498. self._c = false;
  499. };
  500. self._onClick = function(e)
  501. {
  502. e = e || window.event;
  503. var target = e.target || e.srcElement,
  504. pEl = target;
  505. if (!target) {
  506. return;
  507. }
  508. if (!hasEventListeners && hasClass(target, 'pika-select')) {
  509. if (!target.onchange) {
  510. target.setAttribute('onchange', 'return;');
  511. addEvent(target, 'change', self._onChange);
  512. }
  513. }
  514. do {
  515. if (hasClass(pEl, 'pika-single') || pEl === opts.trigger) {
  516. return;
  517. }
  518. }
  519. while ((pEl = pEl.parentNode));
  520. if (self._v && target !== opts.trigger && pEl !== opts.trigger) {
  521. self.hide();
  522. }
  523. };
  524. self.el = document.createElement('div');
  525. self.el.className = 'pika-single' + (opts.isRTL ? ' is-rtl' : '') + (opts.theme ? ' ' + opts.theme : '');
  526. addEvent(self.el, 'mousedown', self._onMouseDown, true);
  527. addEvent(self.el, 'touchend', self._onMouseDown, true);
  528. addEvent(self.el, 'change', self._onChange);
  529. if (opts.field) {
  530. if (opts.container) {
  531. opts.container.appendChild(self.el);
  532. } else if (opts.bound) {
  533. document.body.appendChild(self.el);
  534. } else {
  535. opts.field.parentNode.insertBefore(self.el, opts.field.nextSibling);
  536. }
  537. addEvent(opts.field, 'change', self._onInputChange);
  538. if (!opts.defaultDate) {
  539. if (hasMoment && opts.field.value) {
  540. opts.defaultDate = moment(opts.field.value, opts.format).toDate();
  541. } else {
  542. opts.defaultDate = new Date(Date.parse(opts.field.value));
  543. }
  544. opts.setDefaultDate = true;
  545. }
  546. }
  547. var defDate = opts.defaultDate;
  548. if (isDate(defDate)) {
  549. if (opts.setDefaultDate) {
  550. self.setDate(defDate, true);
  551. } else {
  552. self.gotoDate(defDate);
  553. }
  554. } else {
  555. self.gotoDate(new Date());
  556. }
  557. if (opts.bound) {
  558. this.hide();
  559. self.el.className += ' is-bound';
  560. addEvent(opts.trigger, 'click', self._onInputClick);
  561. addEvent(opts.trigger, 'focus', self._onInputFocus);
  562. addEvent(opts.trigger, 'blur', self._onInputBlur);
  563. } else {
  564. this.show();
  565. }
  566. };
  567. /**
  568. * public Pikaday API
  569. */
  570. Pikaday.prototype = {
  571. /**
  572. * configure functionality
  573. */
  574. config: function(options)
  575. {
  576. if (!this._o) {
  577. this._o = extend({}, defaults, true);
  578. }
  579. var opts = extend(this._o, options, true);
  580. opts.isRTL = !!opts.isRTL;
  581. opts.field = (opts.field && opts.field.nodeName) ? opts.field : null;
  582. opts.theme = (typeof opts.theme) === 'string' && opts.theme ? opts.theme : null;
  583. opts.bound = !!(opts.bound !== undefined ? opts.field && opts.bound : opts.field);
  584. opts.trigger = (opts.trigger && opts.trigger.nodeName) ? opts.trigger : opts.field;
  585. opts.disableWeekends = !!opts.disableWeekends;
  586. opts.disableDayFn = (typeof opts.disableDayFn) === 'function' ? opts.disableDayFn : null;
  587. var nom = parseInt(opts.numberOfMonths, 10) || 1;
  588. opts.numberOfMonths = nom > 4 ? 4 : nom;
  589. if (!isDate(opts.minDate)) {
  590. opts.minDate = false;
  591. }
  592. if (!isDate(opts.maxDate)) {
  593. opts.maxDate = false;
  594. }
  595. if ((opts.minDate && opts.maxDate) && opts.maxDate < opts.minDate) {
  596. opts.maxDate = opts.minDate = false;
  597. }
  598. if (opts.minDate) {
  599. this.setMinDate(opts.minDate);
  600. }
  601. if (opts.maxDate) {
  602. setToStartOfDay(opts.maxDate);
  603. opts.maxYear = opts.maxDate.getFullYear();
  604. opts.maxMonth = opts.maxDate.getMonth();
  605. }
  606. if (isArray(opts.yearRange)) {
  607. var fallback = new Date().getFullYear() - 10;
  608. opts.yearRange[0] = parseInt(opts.yearRange[0], 10) || fallback;
  609. opts.yearRange[1] = parseInt(opts.yearRange[1], 10) || fallback;
  610. } else {
  611. opts.yearRange = Math.abs(parseInt(opts.yearRange, 10)) || defaults.yearRange;
  612. if (opts.yearRange > 100) {
  613. opts.yearRange = 100;
  614. }
  615. }
  616. return opts;
  617. },
  618. /**
  619. * return a formatted string of the current selection (using Moment.js if available or default formatter)
  620. */
  621. toString: function(format) {
  622. return !isDate(this._d) ? '' : (hasMoment) ? moment(this._d).format(format || this._o.format) : formatter(this._d,this._o.format);
  623. },
  624. /**
  625. * return a Moment.js object of the current selection (if available)
  626. */
  627. getMoment: function()
  628. {
  629. return hasMoment ? moment(this._d) : null;
  630. },
  631. /**
  632. * set the current selection from a Moment.js object (if available)
  633. */
  634. setMoment: function(date, preventOnSelect)
  635. {
  636. if (hasMoment && moment.isMoment(date)) {
  637. this.setDate(date.toDate(), preventOnSelect);
  638. }
  639. },
  640. /**
  641. * return a Date object of the current selection
  642. */
  643. getDate: function()
  644. {
  645. return isDate(this._d) ? new Date(this._d.getTime()) : null;
  646. },
  647. /**
  648. * set the current selection
  649. */
  650. setDate: function(date, preventOnSelect)
  651. {
  652. if (!date) {
  653. this._d = null;
  654. if (this._o.field) {
  655. this._o.field.value = '';
  656. fireEvent(this._o.field, 'change', { firedBy: this });
  657. }
  658. return this.draw();
  659. }
  660. if (typeof date === 'string') {
  661. date = new Date(Date.parse(date));
  662. }
  663. if (!isDate(date)) {
  664. return;
  665. }
  666. var min = this._o.minDate,
  667. max = this._o.maxDate;
  668. if (isDate(min) && date < min) {
  669. date = min;
  670. } else if (isDate(max) && date > max) {
  671. date = max;
  672. }
  673. this._d = new Date(date.getTime());
  674. setToStartOfDay(this._d);
  675. this.gotoDate(this._d);
  676. if (this._o.field) {
  677. this._o.field.value = this.toString();
  678. fireEvent(this._o.field, 'change', { firedBy: this });
  679. }
  680. if (!preventOnSelect && typeof this._o.onSelect === 'function') {
  681. this._o.onSelect.call(this, this.getDate());
  682. }
  683. },
  684. /**
  685. * change view to a specific date
  686. */
  687. gotoDate: function(date)
  688. {
  689. var newCalendar = true;
  690. if (!isDate(date)) {
  691. return;
  692. }
  693. if (this.calendars) {
  694. var firstVisibleDate = new Date(this.calendars[0].year, this.calendars[0].month, 1),
  695. lastVisibleDate = new Date(this.calendars[this.calendars.length-1].year, this.calendars[this.calendars.length-1].month, 1),
  696. visibleDate = date.getTime();
  697. // get the end of the month
  698. lastVisibleDate.setMonth(lastVisibleDate.getMonth()+1);
  699. lastVisibleDate.setDate(lastVisibleDate.getDate()-1);
  700. newCalendar = (visibleDate < firstVisibleDate.getTime() || lastVisibleDate.getTime() < visibleDate);
  701. }
  702. if (newCalendar) {
  703. this.calendars = [{
  704. month: date.getMonth(),
  705. year: date.getFullYear()
  706. }];
  707. if (this._o.mainCalendar === 'right') {
  708. this.calendars[0].month += 1 - this._o.numberOfMonths;
  709. }
  710. }
  711. this.adjustCalendars();
  712. },
  713. adjustCalendars: function() {
  714. this.calendars[0] = adjustCalendar(this.calendars[0]);
  715. for (var c = 1; c < this._o.numberOfMonths; c++) {
  716. this.calendars[c] = adjustCalendar({
  717. month: this.calendars[0].month + c,
  718. year: this.calendars[0].year
  719. });
  720. }
  721. this.draw();
  722. },
  723. gotoToday: function()
  724. {
  725. this.gotoDate(new Date());
  726. },
  727. /**
  728. * change view to a specific month (zero-index, e.g. 0: January)
  729. */
  730. gotoMonth: function(month)
  731. {
  732. if (!isNaN(month)) {
  733. this.calendars[0].month = parseInt(month, 10);
  734. this.adjustCalendars();
  735. }
  736. },
  737. nextMonth: function()
  738. {
  739. this.calendars[0].month++;
  740. this.adjustCalendars();
  741. },
  742. prevMonth: function()
  743. {
  744. this.calendars[0].month--;
  745. this.adjustCalendars();
  746. },
  747. /**
  748. * change view to a specific full year (e.g. "2012")
  749. */
  750. gotoYear: function(year)
  751. {
  752. if (!isNaN(year)) {
  753. this.calendars[0].year = parseInt(year, 10);
  754. this.adjustCalendars();
  755. }
  756. },
  757. /**
  758. * change the minDate
  759. */
  760. setMinDate: function(value)
  761. {
  762. setToStartOfDay(value);
  763. this._o.minDate = value;
  764. this._o.minYear = value.getFullYear();
  765. this._o.minMonth = value.getMonth();
  766. },
  767. /**
  768. * change the maxDate
  769. */
  770. setMaxDate: function(value)
  771. {
  772. this._o.maxDate = value;
  773. },
  774. setStartRange: function(value)
  775. {
  776. this._o.startRange = value;
  777. },
  778. setEndRange: function(value)
  779. {
  780. this._o.endRange = value;
  781. },
  782. /**
  783. * refresh the HTML
  784. */
  785. draw: function(force)
  786. {
  787. if (!this._v && !force) {
  788. return;
  789. }
  790. var opts = this._o,
  791. minYear = opts.minYear,
  792. maxYear = opts.maxYear,
  793. minMonth = opts.minMonth,
  794. maxMonth = opts.maxMonth,
  795. html = '';
  796. if (this._y <= minYear) {
  797. this._y = minYear;
  798. if (!isNaN(minMonth) && this._m < minMonth) {
  799. this._m = minMonth;
  800. }
  801. }
  802. if (this._y >= maxYear) {
  803. this._y = maxYear;
  804. if (!isNaN(maxMonth) && this._m > maxMonth) {
  805. this._m = maxMonth;
  806. }
  807. }
  808. for (var c = 0; c < opts.numberOfMonths; c++) {
  809. html += '<div class="pika-lendar">' + renderTitle(this, c, this.calendars[c].year, this.calendars[c].month, this.calendars[0].year) + this.render(this.calendars[c].year, this.calendars[c].month) + '</div>';
  810. }
  811. this.el.innerHTML = html;
  812. if (opts.bound) {
  813. if(opts.field.type !== 'hidden') {
  814. sto(function() {
  815. opts.trigger.focus();
  816. }, 1);
  817. }
  818. }
  819. if (typeof this._o.onDraw === 'function') {
  820. var self = this;
  821. sto(function() {
  822. self._o.onDraw.call(self);
  823. }, 0);
  824. }
  825. },
  826. adjustPosition: function()
  827. {
  828. var field, pEl, width, height, viewportWidth, viewportHeight, scrollTop, left, top, clientRect;
  829. if (this._o.container) return;
  830. this.el.style.position = 'absolute';
  831. field = this._o.trigger;
  832. pEl = field;
  833. width = this.el.offsetWidth;
  834. height = this.el.offsetHeight;
  835. viewportWidth = window.innerWidth || document.documentElement.clientWidth;
  836. viewportHeight = window.innerHeight || document.documentElement.clientHeight;
  837. scrollTop = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
  838. if (typeof field.getBoundingClientRect === 'function') {
  839. clientRect = field.getBoundingClientRect();
  840. left = clientRect.left + window.pageXOffset;
  841. top = clientRect.bottom + window.pageYOffset;
  842. } else {
  843. left = pEl.offsetLeft;
  844. top = pEl.offsetTop + pEl.offsetHeight;
  845. while((pEl = pEl.offsetParent)) {
  846. left += pEl.offsetLeft;
  847. top += pEl.offsetTop;
  848. }
  849. }
  850. // default position is bottom & left
  851. if ((this._o.reposition && left + width > viewportWidth) ||
  852. (
  853. this._o.position.indexOf('right') > -1 &&
  854. left - width + field.offsetWidth > 0
  855. )
  856. ) {
  857. left = left - width + field.offsetWidth;
  858. }
  859. if ((this._o.reposition && top + height > viewportHeight + scrollTop) ||
  860. (
  861. this._o.position.indexOf('top') > -1 &&
  862. top - height - field.offsetHeight > 0
  863. )
  864. ) {
  865. top = top - height - field.offsetHeight;
  866. }
  867. this.el.style.left = left + 'px';
  868. this.el.style.top = top + 'px';
  869. },
  870. /**
  871. * render HTML for a particular month
  872. */
  873. render: function(year, month)
  874. {
  875. var opts = this._o,
  876. now = new Date(),
  877. days = getDaysInMonth(year, month),
  878. before = new Date(year, month, 1).getDay(),
  879. data = [],
  880. row = [];
  881. setToStartOfDay(now);
  882. if (opts.firstDay > 0) {
  883. before -= opts.firstDay;
  884. if (before < 0) {
  885. before += 7;
  886. }
  887. }
  888. var cells = days + before,
  889. after = cells;
  890. while(after > 7) {
  891. after -= 7;
  892. }
  893. cells += 7 - after;
  894. for (var i = 0, r = 0; i < cells; i++)
  895. {
  896. var day = new Date(year, month, 1 + (i - before)),
  897. isSelected = isDate(this._d) ? compareDates(day, this._d) : false,
  898. isToday = compareDates(day, now),
  899. isEmpty = i < before || i >= (days + before),
  900. isStartRange = opts.startRange && compareDates(opts.startRange, day),
  901. isEndRange = opts.endRange && compareDates(opts.endRange, day),
  902. isInRange = opts.startRange && opts.endRange && opts.startRange < day && day < opts.endRange,
  903. isDisabled = (opts.minDate && day < opts.minDate) ||
  904. (opts.maxDate && day > opts.maxDate) ||
  905. (opts.disableWeekends && isWeekend(day)) ||
  906. (opts.disableDayFn && opts.disableDayFn(day)),
  907. dayConfig = {
  908. day: 1 + (i - before),
  909. month: month,
  910. year: year,
  911. isSelected: isSelected,
  912. isToday: isToday,
  913. isDisabled: isDisabled,
  914. isEmpty: isEmpty,
  915. isStartRange: isStartRange,
  916. isEndRange: isEndRange,
  917. isInRange: isInRange
  918. };
  919. row.push(renderDay(dayConfig));
  920. if (++r === 7) {
  921. if (opts.showWeekNumber) {
  922. row.unshift(renderWeek(i - before, month, year));
  923. }
  924. data.push(renderRow(row, opts.isRTL));
  925. row = [];
  926. r = 0;
  927. }
  928. }
  929. return renderTable(opts, data);
  930. },
  931. isVisible: function()
  932. {
  933. return this._v;
  934. },
  935. show: function()
  936. {
  937. if (!this._v) {
  938. removeClass(this.el, 'is-hidden');
  939. this._v = true;
  940. this.draw();
  941. if (this._o.bound) {
  942. addEvent(document, 'click', this._onClick);
  943. this.adjustPosition();
  944. }
  945. if (typeof this._o.onOpen === 'function') {
  946. this._o.onOpen.call(this);
  947. }
  948. }
  949. },
  950. hide: function()
  951. {
  952. var v = this._v;
  953. if (v !== false) {
  954. if (this._o.bound) {
  955. removeEvent(document, 'click', this._onClick);
  956. }
  957. this.el.style.position = 'static'; // reset
  958. this.el.style.left = 'auto';
  959. this.el.style.top = 'auto';
  960. addClass(this.el, 'is-hidden');
  961. this._v = false;
  962. if (v !== undefined && typeof this._o.onClose === 'function') {
  963. this._o.onClose.call(this);
  964. }
  965. }
  966. },
  967. /**
  968. * GAME OVER
  969. */
  970. destroy: function()
  971. {
  972. this.hide();
  973. removeEvent(this.el, 'mousedown', this._onMouseDown, true);
  974. removeEvent(this.el, 'touchend', this._onMouseDown, true);
  975. removeEvent(this.el, 'change', this._onChange);
  976. if (this._o.field) {
  977. removeEvent(this._o.field, 'change', this._onInputChange);
  978. if (this._o.bound) {
  979. removeEvent(this._o.trigger, 'click', this._onInputClick);
  980. removeEvent(this._o.trigger, 'focus', this._onInputFocus);
  981. removeEvent(this._o.trigger, 'blur', this._onInputBlur);
  982. }
  983. }
  984. if (this.el.parentNode) {
  985. this.el.parentNode.removeChild(this.el);
  986. }
  987. }
  988. };
  989. return Pikaday;
  990. }));