datepicker.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. /*
  2. * Fuel UX Datepicker
  3. * https://github.com/ExactTarget/fuelux
  4. *
  5. * Copyright (c) 2014 ExactTarget
  6. * Licensed under the BSD New license.
  7. */
  8. // -- BEGIN UMD WRAPPER PREFACE --
  9. // For more information on UMD visit:
  10. // https://github.com/umdjs/umd/blob/master/jqueryPlugin.js
  11. (function (factory) {
  12. if (typeof define === 'function' && define.amd) {
  13. // if AMD loader is available, register as an anonymous module.
  14. define(['jquery'], factory);
  15. } else {
  16. // OR use browser globals if AMD is not present
  17. factory(jQuery);
  18. }
  19. }(function ($) {
  20. // -- END UMD WRAPPER PREFACE --
  21. // -- BEGIN MODULE CODE HERE --
  22. var INVALID_DATE = 'Invalid Date';
  23. var MOMENT_NOT_AVAILABLE = 'moment.js is not available so you cannot use this function';
  24. var datepickerStack = [];
  25. var moment = false;
  26. var old = $.fn.datepicker;
  27. var requestedMoment = false;
  28. var runStack = function () {
  29. var i, l;
  30. requestedMoment = true;
  31. for (i = 0, l = datepickerStack.length; i < l; i++) {
  32. datepickerStack[i].init.call(datepickerStack[i].scope);
  33. }
  34. datepickerStack = [];
  35. };
  36. //only load moment if it's there. otherwise we'll look for it in window.moment
  37. if (typeof define === 'function' && define.amd) {//check if AMD is available
  38. require(['moment'], function (amdMoment) {
  39. moment = amdMoment;
  40. runStack();
  41. }, function (err) {
  42. var failedId = err.requireModules && err.requireModules[0];
  43. if (failedId === 'moment') {
  44. runStack();
  45. }
  46. });
  47. } else {
  48. runStack();
  49. }
  50. // DATEPICKER CONSTRUCTOR AND PROTOTYPE
  51. var Datepicker = function (element, options) {
  52. this.$element = $(element);
  53. this.options = $.extend(true, {}, $.fn.datepicker.defaults, options);
  54. this.$calendar = this.$element.find('.datepicker-calendar');
  55. this.$days = this.$calendar.find('.datepicker-calendar-days');
  56. this.$header = this.$calendar.find('.datepicker-calendar-header');
  57. this.$headerTitle = this.$header.find('.title');
  58. this.$input = this.$element.find('input');
  59. this.$wheels = this.$element.find('.datepicker-wheels');
  60. this.$wheelsMonth = this.$element.find('.datepicker-wheels-month');
  61. this.$wheelsYear = this.$element.find('.datepicker-wheels-year');
  62. this.artificialScrolling = false;
  63. this.formatDate = this.options.formatDate || this.formatDate;
  64. this.inputValue = null;
  65. this.moment = false;
  66. this.momentFormat = null;
  67. this.parseDate = this.options.parseDate || this.parseDate;
  68. this.preventBlurHide = false;
  69. this.restricted = this.options.restricted || [];
  70. this.restrictedParsed = [];
  71. this.restrictedText = this.options.restrictedText;
  72. this.sameYearOnly = this.options.sameYearOnly;
  73. this.selectedDate = null;
  74. this.yearRestriction = null;
  75. this.$calendar.find('.datepicker-today').on('click.fu.datepicker', $.proxy(this.todayClicked, this));
  76. this.$days.on('click.fu.datepicker', 'tr td button', $.proxy(this.dateClicked, this));
  77. this.$element.find('.dropdown-menu').on('mousedown.fu.datepicker', $.proxy(this.dropdownMousedown, this));
  78. this.$header.find('.next').on('click.fu.datepicker', $.proxy(this.next, this));
  79. this.$header.find('.prev').on('click.fu.datepicker', $.proxy(this.prev, this));
  80. this.$headerTitle.on('click.fu.datepicker', $.proxy(this.titleClicked, this));
  81. this.$input.on('blur.fu.datepicker', $.proxy(this.inputBlurred, this));
  82. this.$input.on('focus.fu.datepicker', $.proxy(this.inputFocused, this));
  83. this.$wheels.find('.datepicker-wheels-back').on('click.fu.datepicker', $.proxy(this.backClicked, this));
  84. this.$wheels.find('.datepicker-wheels-select').on('click.fu.datepicker', $.proxy(this.selectClicked, this));
  85. this.$wheelsMonth.on('click.fu.datepicker', 'ul button', $.proxy(this.monthClicked, this));
  86. this.$wheelsYear.on('click.fu.datepicker', 'ul button', $.proxy(this.yearClicked, this));
  87. this.$wheelsYear.find('ul').on('scroll.fu.datepicker', $.proxy(this.onYearScroll, this));
  88. var init = function () {
  89. if (this.checkForMomentJS()) {
  90. moment = moment || window.moment;// need to pull in the global moment if they didn't do it via require
  91. this.moment = true;
  92. this.momentFormat = this.options.momentConfig.format;
  93. this.setCulture(this.options.momentConfig.culture);
  94. // support moment with lang (< v2.8) or locale
  95. moment.locale = moment.locale || moment.lang;
  96. }
  97. this.setRestrictedDates(this.restricted);
  98. if (!this.setDate(this.options.date)) {
  99. this.$input.val('');
  100. this.inputValue = this.$input.val();
  101. }
  102. if (this.sameYearOnly) {
  103. this.yearRestriction = (this.selectedDate) ? this.selectedDate.getFullYear() : new Date().getFullYear();
  104. }
  105. };
  106. if (requestedMoment) {
  107. init.call(this);
  108. } else {
  109. datepickerStack.push({
  110. init: init,
  111. scope: this
  112. });
  113. }
  114. };
  115. Datepicker.prototype = {
  116. constructor: Datepicker,
  117. backClicked: function () {
  118. this.changeView('calendar');
  119. },
  120. changeView: function (view, date) {
  121. if (view === 'wheels') {
  122. this.$calendar.hide().attr('aria-hidden', 'true');
  123. this.$wheels.show().removeAttr('aria-hidden', '');
  124. if (date) {
  125. this.renderWheel(date);
  126. }
  127. } else {
  128. this.$wheels.hide().attr('aria-hidden', 'true');
  129. this.$calendar.show().removeAttr('aria-hidden', '');
  130. if (date) {
  131. this.renderMonth(date);
  132. }
  133. }
  134. },
  135. checkForMomentJS: function () {
  136. if (
  137. ($.isFunction(window.moment) || (typeof moment !== 'undefined' && $.isFunction(moment))) &&
  138. $.isPlainObject(this.options.momentConfig) &&
  139. this.options.momentConfig.culture && this.options.momentConfig.format
  140. ) {
  141. return true;
  142. } else {
  143. return false;
  144. }
  145. },
  146. dateClicked: function (e) {
  147. var $td = $(e.currentTarget).parents('td:first');
  148. var date;
  149. if ($td.hasClass('restricted')) {
  150. return;
  151. }
  152. this.$days.find('td.selected').removeClass('selected');
  153. $td.addClass('selected');
  154. date = new Date($td.attr('data-year'), $td.attr('data-month'), $td.attr('data-date'));
  155. this.selectedDate = date;
  156. this.$input.val(this.formatDate(date));
  157. this.inputValue = this.$input.val();
  158. this.$input.focus();
  159. this.$element.trigger('dateClicked.fu.datepicker', date);
  160. },
  161. destroy: function () {
  162. this.$element.remove();
  163. // any external bindings
  164. // [none]
  165. // empty elements to return to original markup
  166. this.$days.find('tbody').empty();
  167. this.$wheelsYear.find('ul').empty();
  168. return this.$element[0].outerHTML;
  169. },
  170. disable: function () {
  171. this.$element.addClass('disabled');
  172. this.$element.find('input, button').attr('disabled', 'disabled');
  173. this.$element.find('.input-group-btn').removeClass('open');
  174. },
  175. dropdownMousedown: function () {
  176. var self = this;
  177. this.preventBlurHide = true;
  178. setTimeout(function () {
  179. self.preventBlurHide = false;
  180. }, 0);
  181. },
  182. enable: function () {
  183. this.$element.removeClass('disabled');
  184. this.$element.find('input, button').removeAttr('disabled');
  185. },
  186. formatDate: function (date) {
  187. var padTwo = function (value) {
  188. var s = '0' + value;
  189. return s.substr(s.length - 2);
  190. };
  191. if (this.moment) {
  192. return moment(date).format(this.momentFormat);
  193. } else {
  194. return padTwo(date.getMonth() + 1) + '/' + padTwo(date.getDate()) + '/' + date.getFullYear();
  195. }
  196. },
  197. getCulture: function () {
  198. if (this.moment) {
  199. return moment.locale();
  200. } else {
  201. throw MOMENT_NOT_AVAILABLE;
  202. }
  203. },
  204. getDate: function () {
  205. return (!this.selectedDate) ? new Date(NaN) : this.selectedDate;
  206. },
  207. getFormat: function () {
  208. if (this.moment) {
  209. return this.momentFormat;
  210. } else {
  211. throw MOMENT_NOT_AVAILABLE;
  212. }
  213. },
  214. getFormattedDate: function () {
  215. return (!this.selectedDate) ? INVALID_DATE : this.formatDate(this.selectedDate);
  216. },
  217. getRestrictedDates: function () {
  218. return this.restricted;
  219. },
  220. inputBlurred: function (e) {
  221. var inputVal = this.$input.val();
  222. var date;
  223. if (inputVal !== this.inputValue) {
  224. date = this.setDate(inputVal);
  225. if (date === null) {
  226. this.$element.trigger('inputParsingFailed.fu.datepicker', inputVal);
  227. } else if (date === false) {
  228. this.$element.trigger('inputRestrictedDate.fu.datepicker', date);
  229. } else {
  230. this.$element.trigger('changed.fu.datepicker', date);
  231. }
  232. }
  233. if (!this.preventBlurHide) {
  234. this.$element.find('.input-group-btn').removeClass('open');
  235. }
  236. },
  237. inputFocused: function (e) {
  238. this.$element.find('.input-group-btn').addClass('open');
  239. },
  240. isInvalidDate: function (date) {
  241. var dateString = date.toString();
  242. if (dateString === INVALID_DATE || dateString === 'NaN') {
  243. return true;
  244. }
  245. return false;
  246. },
  247. isRestricted: function (date, month, year) {
  248. var restricted = this.restrictedParsed;
  249. var i, from, l, to;
  250. if (this.sameYearOnly && this.yearRestriction !== null && year !== this.yearRestriction) {
  251. return true;
  252. }
  253. for (i = 0, l = restricted.length; i < l; i++) {
  254. from = restricted[i].from;
  255. to = restricted[i].to;
  256. if (
  257. (year > from.year || (year === from.year && month > from.month) || (year === from.year && month === from.month && date >= from.date)) &&
  258. (year < to.year || (year === to.year && month < to.month) || (year === to.year && month === to.month && date <= to.date))
  259. ) {
  260. return true;
  261. }
  262. }
  263. return false;
  264. },
  265. monthClicked: function (e) {
  266. this.$wheelsMonth.find('.selected').removeClass('selected');
  267. $(e.currentTarget).parent().addClass('selected');
  268. },
  269. next: function () {
  270. var month = this.$headerTitle.attr('data-month');
  271. var year = this.$headerTitle.attr('data-year');
  272. month++;
  273. if (month > 11) {
  274. if (this.sameYearOnly) {
  275. return;
  276. }
  277. month = 0;
  278. year++;
  279. }
  280. this.renderMonth(new Date(year, month, 1));
  281. },
  282. onYearScroll: function (e) {
  283. if (this.artificialScrolling) {
  284. return;
  285. }
  286. var $yearUl = $(e.currentTarget);
  287. var height = ($yearUl.css('box-sizing') === 'border-box') ? $yearUl.outerHeight() : $yearUl.height();
  288. var scrollHeight = $yearUl.get(0).scrollHeight;
  289. var scrollTop = $yearUl.scrollTop();
  290. var bottomPercentage = (height / (scrollHeight - scrollTop)) * 100;
  291. var topPercentage = (scrollTop / scrollHeight) * 100;
  292. var i, start;
  293. if (topPercentage < 5) {
  294. start = parseInt($yearUl.find('li:first').attr('data-year'), 10);
  295. for (i = (start - 1); i > (start - 11); i--) {
  296. $yearUl.prepend('<li data-year="' + i + '"><button type="button">' + i + '</button></li>');
  297. }
  298. this.artificialScrolling = true;
  299. $yearUl.scrollTop(($yearUl.get(0).scrollHeight - scrollHeight) + scrollTop);
  300. this.artificialScrolling = false;
  301. } else if (bottomPercentage > 90) {
  302. start = parseInt($yearUl.find('li:last').attr('data-year'), 10);
  303. for (i = (start + 1); i < (start + 11); i++) {
  304. $yearUl.append('<li data-year="' + i + '"><button type="button">' + i + '</button></li>');
  305. }
  306. }
  307. },
  308. //some code ripped from http://stackoverflow.com/questions/2182246/javascript-dates-in-ie-nan-firefox-chrome-ok
  309. parseDate: function (date) {
  310. var self = this;
  311. var BAD_DATE = new Date(NaN);
  312. var dt, isoExp, momentParse, momentParseWithFormat, tryMomentParseAll, month, parts, use;
  313. if (date) {
  314. if (this.moment) {//if we have moment, use that to parse the dates
  315. momentParseWithFormat = function (d) {
  316. var md = moment(d, self.momentFormat);
  317. return (true === md.isValid()) ? md.toDate() : BAD_DATE;
  318. };
  319. momentParse = function (d) {
  320. var md = moment(new Date(d));
  321. return (true === md.isValid()) ? md.toDate() : BAD_DATE;
  322. };
  323. tryMomentParseAll = function (d, parseFunc1, parseFunc2) {
  324. var pd = parseFunc1(d);
  325. if (!self.isInvalidDate(pd)) {
  326. return pd;
  327. }
  328. pd = parseFunc2(pd);
  329. if (!self.isInvalidDate(pd)) {
  330. return pd;
  331. }
  332. return BAD_DATE;
  333. };
  334. if ('string' === typeof (date)) {
  335. // Attempts to parse date strings using this.momentFormat, falling back on newing a date
  336. return tryMomentParseAll(date, momentParseWithFormat, momentParse);
  337. } else {
  338. // Attempts to parse date by newing a date object directly, falling back on parsing using this.momentFormat
  339. return tryMomentParseAll(date, momentParse, momentParseWithFormat);
  340. }
  341. } else {//if moment isn't present, use previous date parsing strategy
  342. if (typeof (date) === 'string') {
  343. dt = new Date(Date.parse(date));
  344. if (!this.isInvalidDate(dt)) {
  345. return dt;
  346. } else {
  347. date = date.split('T')[0];
  348. isoExp = /^\s*(\d{4})-(\d\d)-(\d\d)\s*$/;
  349. parts = isoExp.exec(date);
  350. if (parts) {
  351. month = parseInt(parts[2], 10);
  352. dt = new Date(parts[1], month - 1, parts[3]);
  353. if (month === (dt.getMonth() + 1)) {
  354. return dt;
  355. }
  356. }
  357. }
  358. } else {
  359. dt = new Date(date);
  360. if (!this.isInvalidDate(dt)) {
  361. return dt;
  362. }
  363. }
  364. }
  365. }
  366. return new Date(NaN);
  367. },
  368. prev: function () {
  369. var month = this.$headerTitle.attr('data-month');
  370. var year = this.$headerTitle.attr('data-year');
  371. month--;
  372. if (month < 0) {
  373. if (this.sameYearOnly) {
  374. return;
  375. }
  376. month = 11;
  377. year--;
  378. }
  379. this.renderMonth(new Date(year, month, 1));
  380. },
  381. renderMonth: function (date) {
  382. date = date || new Date();
  383. var firstDay = new Date(date.getFullYear(), date.getMonth(), 1).getDay();
  384. var lastDate = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
  385. var lastMonthDate = new Date(date.getFullYear(), date.getMonth(), 0).getDate();
  386. var $month = this.$headerTitle.find('.month');
  387. var month = date.getMonth();
  388. var now = new Date();
  389. var nowDate = now.getDate();
  390. var nowMonth = now.getMonth();
  391. var nowYear = now.getFullYear();
  392. var selected = this.selectedDate;
  393. var $tbody = this.$days.find('tbody');
  394. var year = date.getFullYear();
  395. var curDate, curMonth, curYear, i, j, rows, stage, $td, $tr;
  396. if (selected) {
  397. selected = {
  398. date: selected.getDate(),
  399. month: selected.getMonth(),
  400. year: selected.getFullYear()
  401. };
  402. }
  403. $month.find('.current').removeClass('current');
  404. $month.find('span[data-month="' + month + '"]').addClass('current');
  405. this.$headerTitle.find('.year').text(year);
  406. this.$headerTitle.attr({
  407. 'data-month': month,
  408. 'data-year': year
  409. });
  410. $tbody.empty();
  411. if (firstDay !== 0) {
  412. curDate = lastMonthDate - firstDay + 1;
  413. stage = -1;
  414. } else {
  415. curDate = 1;
  416. stage = 0;
  417. }
  418. rows = (lastDate <= (35 - firstDay)) ? 5 : 6;
  419. for (i = 0; i < rows; i++) {
  420. $tr = $('<tr></tr>');
  421. for (j = 0; j < 7; j++) {
  422. $td = $('<td></td>');
  423. if (stage === -1) {
  424. $td.addClass('last-month');
  425. } else if (stage === 1) {
  426. $td.addClass('next-month');
  427. }
  428. curMonth = month + stage;
  429. curYear = year;
  430. if (curMonth < 0) {
  431. curMonth = 11;
  432. curYear--;
  433. } else if (curMonth > 11) {
  434. curMonth = 0;
  435. curYear++;
  436. }
  437. $td.attr({
  438. 'data-date': curDate,
  439. 'data-month': curMonth,
  440. 'data-year': curYear
  441. });
  442. if (curYear === nowYear && curMonth === nowMonth && curDate === nowDate) {
  443. $td.addClass('current-day');
  444. } else if (curYear < nowYear || (curYear === nowYear && curMonth < nowMonth) ||
  445. (curYear === nowYear && curMonth === nowMonth && curDate < nowDate)) {
  446. $td.addClass('past');
  447. if (!this.options.allowPastDates) {
  448. $td.addClass('restricted').attr('title', this.restrictedText);
  449. }
  450. }
  451. if (this.isRestricted(curDate, curMonth, curYear)) {
  452. $td.addClass('restricted').attr('title', this.restrictedText);
  453. }
  454. if (selected && curYear === selected.year && curMonth === selected.month && curDate === selected.date) {
  455. $td.addClass('selected');
  456. }
  457. if ($td.hasClass('restricted')) {
  458. $td.html('<span><b class="datepicker-date">' + curDate + '</b></span>');
  459. } else {
  460. $td.html('<span><button type="button" class="datepicker-date">' + curDate + '</button></span>');
  461. }
  462. curDate++;
  463. if (stage === -1 && curDate > lastMonthDate) {
  464. curDate = 1;
  465. stage = 0;
  466. } else if (stage === 0 && curDate > lastDate) {
  467. curDate = 1;
  468. stage = 1;
  469. }
  470. $tr.append($td);
  471. }
  472. $tbody.append($tr);
  473. }
  474. },
  475. renderWheel: function (date) {
  476. var month = date.getMonth();
  477. var $monthUl = this.$wheelsMonth.find('ul');
  478. var year = date.getFullYear();
  479. var $yearUl = this.$wheelsYear.find('ul');
  480. var i, $monthSelected, $yearSelected;
  481. if (this.sameYearOnly) {
  482. this.$wheelsMonth.addClass('full');
  483. this.$wheelsYear.addClass('hide');
  484. } else {
  485. this.$wheelsMonth.removeClass('full');
  486. this.$wheelsYear.removeClass('hide');
  487. }
  488. $monthUl.find('.selected').removeClass('selected');
  489. $monthSelected = $monthUl.find('li[data-month="' + month + '"]');
  490. $monthSelected.addClass('selected');
  491. $monthUl.scrollTop($monthUl.scrollTop() + ($monthSelected.position().top - $monthUl.outerHeight() / 2 - $monthSelected.outerHeight(true) / 2));
  492. $yearUl.empty();
  493. for (i = (year - 10); i < (year + 11); i++) {
  494. $yearUl.append('<li data-year="' + i + '"><button type="button">' + i + '</button></li>');
  495. }
  496. $yearSelected = $yearUl.find('li[data-year="' + year + '"]');
  497. $yearSelected.addClass('selected');
  498. this.artificialScrolling = true;
  499. $yearUl.scrollTop($yearUl.scrollTop() + ($yearSelected.position().top - $yearUl.outerHeight() / 2 - $yearSelected.outerHeight(true) / 2));
  500. this.artificialScrolling = false;
  501. $monthSelected.find('button').focus();
  502. },
  503. selectClicked: function () {
  504. var month = this.$wheelsMonth.find('.selected').attr('data-month');
  505. var year = this.$wheelsYear.find('.selected').attr('data-year');
  506. this.changeView('calendar', new Date(year, month, 1));
  507. },
  508. setCulture: function (cultureCode) {
  509. if (!cultureCode) {
  510. return false;
  511. }
  512. if (this.moment) {
  513. moment.locale(cultureCode);
  514. } else {
  515. throw MOMENT_NOT_AVAILABLE;
  516. }
  517. },
  518. setDate: function (date) {
  519. var parsed = this.parseDate(date);
  520. if (!this.isInvalidDate(parsed)) {
  521. if (!this.isRestricted(parsed.getDate(), parsed.getMonth(), parsed.getFullYear())) {
  522. this.selectedDate = parsed;
  523. this.renderMonth(parsed);
  524. this.$input.val(this.formatDate(parsed));
  525. } else {
  526. this.selectedDate = false;
  527. this.renderMonth();
  528. }
  529. } else {
  530. this.selectedDate = null;
  531. this.renderMonth();
  532. }
  533. this.inputValue = this.$input.val();
  534. return this.selectedDate;
  535. },
  536. setFormat: function (format) {
  537. if (!format) {
  538. return false;
  539. }
  540. if (this.moment) {
  541. this.momentFormat = format;
  542. } else {
  543. throw MOMENT_NOT_AVAILABLE;
  544. }
  545. },
  546. setRestrictedDates: function (restricted) {
  547. var parsed = [];
  548. var self = this;
  549. var i, l;
  550. var parseItem = function (val) {
  551. if (val === -Infinity) {
  552. return {
  553. date: -Infinity,
  554. month: -Infinity,
  555. year: -Infinity
  556. };
  557. } else if (val === Infinity) {
  558. return {
  559. date: Infinity,
  560. month: Infinity,
  561. year: Infinity
  562. };
  563. } else {
  564. val = self.parseDate(val);
  565. return {
  566. date: val.getDate(),
  567. month: val.getMonth(),
  568. year: val.getFullYear()
  569. };
  570. }
  571. };
  572. this.restricted = restricted;
  573. for (i = 0, l = restricted.length; i < l; i++) {
  574. parsed.push({
  575. from: parseItem(restricted[i].from),
  576. to: parseItem(restricted[i].to)
  577. });
  578. }
  579. this.restrictedParsed = parsed;
  580. },
  581. titleClicked: function (e) {
  582. this.changeView('wheels', new Date(this.$headerTitle.attr('data-year'), this.$headerTitle.attr('data-month'), 1));
  583. },
  584. todayClicked: function (e) {
  585. var date = new Date();
  586. if ((date.getMonth() + '') !== this.$headerTitle.attr('data-month') || (date.getFullYear() + '') !== this.$headerTitle.attr('data-year')) {
  587. this.renderMonth(date);
  588. }
  589. },
  590. yearClicked: function (e) {
  591. this.$wheelsYear.find('.selected').removeClass('selected');
  592. $(e.currentTarget).parent().addClass('selected');
  593. }
  594. };
  595. // DATEPICKER PLUGIN DEFINITION
  596. $.fn.datepicker = function (option) {
  597. var args = Array.prototype.slice.call(arguments, 1);
  598. var methodReturn;
  599. var $set = this.each(function () {
  600. var $this = $(this);
  601. var data = $this.data('fu.datepicker');
  602. var options = typeof option === 'object' && option;
  603. if (!data) {
  604. $this.data('fu.datepicker', (data = new Datepicker(this, options)));
  605. }
  606. if (typeof option === 'string') {
  607. methodReturn = data[option].apply(data, args);
  608. }
  609. });
  610. return (methodReturn === undefined) ? $set : methodReturn;
  611. };
  612. $.fn.datepicker.defaults = {
  613. allowPastDates: false,
  614. date: new Date(),
  615. formatDate: null,
  616. momentConfig: {
  617. culture: 'en',
  618. format: 'L'// more formats can be found here http://momentjs.com/docs/#/customization/long-date-formats/.
  619. },
  620. parseDate: null,
  621. restricted: [],//accepts an array of objects formatted as so: { from: {{date}}, to: {{date}} } (ex: [ { from: new Date('12/11/2014'), to: new Date('03/31/2015') } ])
  622. restrictedText: 'Restricted',
  623. sameYearOnly: false
  624. };
  625. $.fn.datepicker.Constructor = Datepicker;
  626. $.fn.datepicker.noConflict = function () {
  627. $.fn.datepicker = old;
  628. return this;
  629. };
  630. // DATA-API
  631. $(document).on('mousedown.fu.datepicker.data-api', '[data-initialize=datepicker]', function (e) {
  632. var $control = $(e.target).closest('.datepicker');
  633. if (!$control.data('datepicker')) {
  634. $control.datepicker($control.data());
  635. }
  636. });
  637. //used to prevent the dropdown from closing when clicking within it's bounds
  638. $(document).on('click.fu.datepicker.data-api', '.datepicker .dropdown-menu', function (e) {
  639. var $target = $(e.target);
  640. if (!$target.is('.datepicker-date') || $target.closest('.restricted').length) {
  641. e.stopPropagation();
  642. }
  643. });
  644. //used to prevent the dropdown from closing when clicking on the input
  645. $(document).on('click.fu.datepicker.data-api', '.datepicker input', function (e) {
  646. e.stopPropagation();
  647. });
  648. $(function () {
  649. $('[data-initialize=datepicker]').each(function () {
  650. var $this = $(this);
  651. if ($this.data('datepicker')) {
  652. return;
  653. }
  654. $this.datepicker($this.data());
  655. });
  656. });
  657. // -- BEGIN UMD WRAPPER AFTERWORD --
  658. }));
  659. // -- END UMD WRAPPER AFTERWORD --