pikaday.js 34 KB

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