pikaday.js 29 KB

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