calendar.js 36 KB

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