calendar.js 38 KB

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