calendar.js 33 KB

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