bootstrap-datepicker.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026
  1. /* =========================================================
  2. * bootstrap-datepicker.js
  3. * http://www.eyecon.ro/bootstrap-datepicker
  4. * =========================================================
  5. * Copyright 2012 Stefan Petre
  6. * Improvements by Andrew Rowls
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. * ========================================================= */
  20. !function( $ ) {
  21. function UTCDate(){
  22. return new Date(Date.UTC.apply(Date, arguments));
  23. }
  24. function UTCToday(){
  25. var today = new Date();
  26. return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
  27. }
  28. // Picker object
  29. var Datepicker = function(element, options) {
  30. var that = this;
  31. this.element = $(element);
  32. this.language = options.language||this.element.data('date-language')||"en";
  33. this.language = this.language in dates ? this.language : this.language.split('-')[0]; //Check if "de-DE" style date is available, if not language should fallback to 2 letter code eg "de"
  34. this.language = this.language in dates ? this.language : "en";
  35. this.isRTL = dates[this.language].rtl||false;
  36. this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||dates[this.language].format||'mm/dd/yyyy');
  37. this.isInline = false;
  38. this.isInput = this.element.is('input');
  39. this.component = this.element.is('.date') ? this.element.find('.add-on, .btn') : false;
  40. this.hasInput = this.component && this.element.find('input').length;
  41. if(this.component && this.component.length === 0)
  42. this.component = false;
  43. this._attachEvents();
  44. this.forceParse = true;
  45. if ('forceParse' in options) {
  46. this.forceParse = options.forceParse;
  47. } else if ('dateForceParse' in this.element.data()) {
  48. this.forceParse = this.element.data('date-force-parse');
  49. }
  50. this.picker = $(DPGlobal.template)
  51. .on({
  52. click: $.proxy(this.click, this),
  53. mousedown: $.proxy(this.mousedown, this)
  54. });
  55. if(this.isInline) {
  56. this.picker.addClass('datepicker-inline').appendTo(this.element);
  57. } else {
  58. this.picker.addClass('datepicker-dropdown dropdown-menu');
  59. }
  60. if (this.isRTL){
  61. this.picker.addClass('datepicker-rtl');
  62. this.picker.find('.prev i, .next i')
  63. .toggleClass('icon-arrow-left icon-arrow-right');
  64. }
  65. $(document).on('mousedown', function (e) {
  66. // Clicked outside the datepicker, hide it
  67. if ($(e.target).closest('.datepicker.datepicker-inline, .datepicker.datepicker-dropdown').length === 0) {
  68. that.hide();
  69. }
  70. });
  71. this.autoclose = false;
  72. if ('autoclose' in options) {
  73. this.autoclose = options.autoclose;
  74. } else if ('dateAutoclose' in this.element.data()) {
  75. this.autoclose = this.element.data('date-autoclose');
  76. }
  77. this.keyboardNavigation = true;
  78. if ('keyboardNavigation' in options) {
  79. this.keyboardNavigation = options.keyboardNavigation;
  80. } else if ('dateKeyboardNavigation' in this.element.data()) {
  81. this.keyboardNavigation = this.element.data('date-keyboard-navigation');
  82. }
  83. this.viewMode = this.startViewMode = 0;
  84. switch(options.startView || this.element.data('date-start-view')){
  85. case 2:
  86. case 'decade':
  87. this.viewMode = this.startViewMode = 2;
  88. break;
  89. case 1:
  90. case 'year':
  91. this.viewMode = this.startViewMode = 1;
  92. break;
  93. }
  94. this.minViewMode = options.minViewMode||this.element.data('date-min-view-mode')||0;
  95. if (typeof this.minViewMode === 'string') {
  96. switch (this.minViewMode) {
  97. case 'months':
  98. this.minViewMode = 1;
  99. break;
  100. case 'years':
  101. this.minViewMode = 2;
  102. break;
  103. default:
  104. this.minViewMode = 0;
  105. break;
  106. }
  107. }
  108. this.viewMode = this.startViewMode = Math.max(this.startViewMode, this.minViewMode);
  109. this.todayBtn = (options.todayBtn||this.element.data('date-today-btn')||false);
  110. this.todayHighlight = (options.todayHighlight||this.element.data('date-today-highlight')||false);
  111. this.calendarWeeks = false;
  112. if ('calendarWeeks' in options) {
  113. this.calendarWeeks = options.calendarWeeks;
  114. } else if ('dateCalendarWeeks' in this.element.data()) {
  115. this.calendarWeeks = this.element.data('date-calendar-weeks');
  116. }
  117. if (this.calendarWeeks)
  118. this.picker.find('tfoot th.today')
  119. .attr('colspan', function(i, val){
  120. return parseInt(val) + 1;
  121. });
  122. this._allow_update = false;
  123. this.weekStart = ((options.weekStart||this.element.data('date-weekstart')||dates[this.language].weekStart||0) % 7);
  124. this.weekEnd = ((this.weekStart + 6) % 7);
  125. this.startDate = -Infinity;
  126. this.endDate = Infinity;
  127. this.daysOfWeekDisabled = [];
  128. this.setStartDate(options.startDate||this.element.data('date-startdate'));
  129. this.setEndDate(options.endDate||this.element.data('date-enddate'));
  130. this.setDaysOfWeekDisabled(options.daysOfWeekDisabled||this.element.data('date-days-of-week-disabled'));
  131. this.fillDow();
  132. this.fillMonths();
  133. this._allow_update = true;
  134. this.update();
  135. this.showMode();
  136. if(this.isInline) {
  137. this.show();
  138. }
  139. };
  140. Datepicker.prototype = {
  141. constructor: Datepicker,
  142. _events: [],
  143. _attachEvents: function(){
  144. this._detachEvents();
  145. if (this.isInput) { // single input
  146. this._events = [
  147. [this.element, {
  148. focus: $.proxy(this.show, this),
  149. keyup: $.proxy(this.update, this),
  150. keydown: $.proxy(this.keydown, this)
  151. }]
  152. ];
  153. }
  154. else if (this.component && this.hasInput){ // component: input + button
  155. this._events = [
  156. // For components that are not readonly, allow keyboard nav
  157. [this.element.find('input'), {
  158. focus: $.proxy(this.show, this),
  159. keyup: $.proxy(this.update, this),
  160. keydown: $.proxy(this.keydown, this)
  161. }],
  162. [this.component, {
  163. click: $.proxy(this.show, this)
  164. }]
  165. ];
  166. }
  167. else if (this.element.is('div')) { // inline datepicker
  168. this.isInline = true;
  169. }
  170. else {
  171. this._events = [
  172. [this.element, {
  173. click: $.proxy(this.show, this)
  174. }]
  175. ];
  176. }
  177. for (var i=0, el, ev; i<this._events.length; i++){
  178. el = this._events[i][0];
  179. ev = this._events[i][1];
  180. el.on(ev);
  181. }
  182. },
  183. _detachEvents: function(){
  184. for (var i=0, el, ev; i<this._events.length; i++){
  185. el = this._events[i][0];
  186. ev = this._events[i][1];
  187. el.off(ev);
  188. }
  189. this._events = [];
  190. },
  191. show: function(e) {
  192. if (!this.isInline)
  193. this.picker.appendTo('body');
  194. this.picker.show();
  195. this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
  196. this.place();
  197. $(window).on('resize', $.proxy(this.place, this));
  198. if (e) {
  199. e.preventDefault();
  200. }
  201. this.element.trigger({
  202. type: 'show',
  203. date: this.date
  204. });
  205. },
  206. hide: function(e){
  207. if(this.isInline) return;
  208. if (!this.picker.is(':visible')) return;
  209. this.picker.hide().detach();
  210. $(window).off('resize', this.place);
  211. this.viewMode = this.startViewMode;
  212. this.showMode();
  213. if (!this.isInput) {
  214. $(document).off('mousedown', this.hide);
  215. }
  216. if (
  217. this.forceParse &&
  218. (
  219. this.isInput && this.element.val() ||
  220. this.hasInput && this.element.find('input').val()
  221. )
  222. )
  223. this.setValue();
  224. this.element.trigger({
  225. type: 'hide',
  226. date: this.date
  227. });
  228. },
  229. remove: function() {
  230. this._detachEvents();
  231. this.picker.remove();
  232. delete this.element.data().datepicker;
  233. if (!this.isInput) {
  234. delete this.element.data().date;
  235. }
  236. },
  237. getDate: function() {
  238. var d = this.getUTCDate();
  239. return new Date(d.getTime() + (d.getTimezoneOffset()*60000));
  240. },
  241. getUTCDate: function() {
  242. return this.date;
  243. },
  244. setDate: function(d) {
  245. this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset()*60000)));
  246. },
  247. setUTCDate: function(d) {
  248. this.date = d;
  249. this.setValue();
  250. },
  251. setValue: function() {
  252. var formatted = this.getFormattedDate();
  253. if (!this.isInput) {
  254. if (this.component){
  255. this.element.find('input').val(formatted);
  256. }
  257. this.element.data('date', formatted);
  258. } else {
  259. this.element.val(formatted);
  260. }
  261. },
  262. getFormattedDate: function(format) {
  263. if (format === undefined)
  264. format = this.format;
  265. return DPGlobal.formatDate(this.date, format, this.language);
  266. },
  267. setStartDate: function(startDate){
  268. this.startDate = startDate||-Infinity;
  269. if (this.startDate !== -Infinity) {
  270. this.startDate = DPGlobal.parseDate(this.startDate, this.format, this.language);
  271. }
  272. this.update();
  273. this.updateNavArrows();
  274. },
  275. setEndDate: function(endDate){
  276. this.endDate = endDate||Infinity;
  277. if (this.endDate !== Infinity) {
  278. this.endDate = DPGlobal.parseDate(this.endDate, this.format, this.language);
  279. }
  280. this.update();
  281. this.updateNavArrows();
  282. },
  283. setDaysOfWeekDisabled: function(daysOfWeekDisabled){
  284. this.daysOfWeekDisabled = daysOfWeekDisabled||[];
  285. if (!$.isArray(this.daysOfWeekDisabled)) {
  286. this.daysOfWeekDisabled = this.daysOfWeekDisabled.split(/,\s*/);
  287. }
  288. this.daysOfWeekDisabled = $.map(this.daysOfWeekDisabled, function (d) {
  289. return parseInt(d, 10);
  290. });
  291. this.update();
  292. this.updateNavArrows();
  293. },
  294. place: function(){
  295. if(this.isInline) return;
  296. var zIndex = parseInt(this.element.parents().filter(function() {
  297. return $(this).css('z-index') != 'auto';
  298. }).first().css('z-index'))+10;
  299. var offset = this.component ? this.component.parent().offset() : this.element.offset();
  300. var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(true);
  301. this.picker.css({
  302. top: offset.top + height,
  303. left: offset.left,
  304. zIndex: zIndex
  305. });
  306. },
  307. _allow_update: true,
  308. update: function(){
  309. if (!this._allow_update) return;
  310. var date, fromArgs = false;
  311. if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {
  312. date = arguments[0];
  313. fromArgs = true;
  314. } else {
  315. date = this.isInput ? this.element.val() : this.element.data('date') || this.element.find('input').val();
  316. }
  317. this.date = DPGlobal.parseDate(date, this.format, this.language);
  318. if(fromArgs) this.setValue();
  319. if (this.date < this.startDate) {
  320. this.viewDate = new Date(this.startDate);
  321. } else if (this.date > this.endDate) {
  322. this.viewDate = new Date(this.endDate);
  323. } else {
  324. this.viewDate = new Date(this.date);
  325. }
  326. this.fill();
  327. },
  328. fillDow: function(){
  329. var dowCnt = this.weekStart,
  330. html = '<tr>';
  331. if(this.calendarWeeks){
  332. var cell = '<th class="cw">&nbsp;</th>';
  333. html += cell;
  334. this.picker.find('.datepicker-days thead tr:first-child').prepend(cell);
  335. }
  336. while (dowCnt < this.weekStart + 7) {
  337. html += '<th class="dow">'+dates[this.language].daysMin[(dowCnt++)%7]+'</th>';
  338. }
  339. html += '</tr>';
  340. this.picker.find('.datepicker-days thead').append(html);
  341. },
  342. fillMonths: function(){
  343. var html = '',
  344. i = 0;
  345. while (i < 12) {
  346. html += '<span class="month">'+dates[this.language].monthsShort[i++]+'</span>';
  347. }
  348. this.picker.find('.datepicker-months td').html(html);
  349. },
  350. fill: function() {
  351. var d = new Date(this.viewDate),
  352. year = d.getUTCFullYear(),
  353. month = d.getUTCMonth(),
  354. startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity,
  355. startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity,
  356. endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity,
  357. endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity,
  358. currentDate = this.date && this.date.valueOf(),
  359. today = new Date();
  360. this.picker.find('.datepicker-days thead th.switch')
  361. .text(dates[this.language].months[month]+' '+year);
  362. this.picker.find('tfoot th.today')
  363. .text(dates[this.language].today)
  364. .toggle(this.todayBtn !== false);
  365. this.updateNavArrows();
  366. this.fillMonths();
  367. var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
  368. day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
  369. prevMonth.setUTCDate(day);
  370. prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7)%7);
  371. var nextMonth = new Date(prevMonth);
  372. nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
  373. nextMonth = nextMonth.valueOf();
  374. var html = [];
  375. var clsName;
  376. while(prevMonth.valueOf() < nextMonth) {
  377. if (prevMonth.getUTCDay() == this.weekStart) {
  378. html.push('<tr>');
  379. if(this.calendarWeeks){
  380. // ISO 8601: First week contains first thursday.
  381. // ISO also states week starts on Monday, but we can be more abstract here.
  382. var
  383. // Start of current week: based on weekstart/current date
  384. ws = new Date(+prevMonth + (this.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5),
  385. // Thursday of this week
  386. th = new Date(+ws + (7 + 4 - ws.getUTCDay()) % 7 * 864e5),
  387. // First Thursday of year, year from thursday
  388. yth = new Date(+(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5),
  389. // Calendar week: ms between thursdays, div ms per day, div 7 days
  390. calWeek = (th - yth) / 864e5 / 7 + 1;
  391. html.push('<td class="cw">'+ calWeek +'</td>');
  392. }
  393. }
  394. clsName = '';
  395. if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) {
  396. clsName += ' old';
  397. } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) {
  398. clsName += ' new';
  399. }
  400. // Compare internal UTC date with local today, not UTC today
  401. if (this.todayHighlight &&
  402. prevMonth.getUTCFullYear() == today.getFullYear() &&
  403. prevMonth.getUTCMonth() == today.getMonth() &&
  404. prevMonth.getUTCDate() == today.getDate()) {
  405. clsName += ' today';
  406. }
  407. if (currentDate && prevMonth.valueOf() == currentDate) {
  408. clsName += ' active';
  409. }
  410. if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate ||
  411. $.inArray(prevMonth.getUTCDay(), this.daysOfWeekDisabled) !== -1) {
  412. clsName += ' disabled';
  413. }
  414. html.push('<td class="day'+clsName+'">'+prevMonth.getUTCDate() + '</td>');
  415. if (prevMonth.getUTCDay() == this.weekEnd) {
  416. html.push('</tr>');
  417. }
  418. prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
  419. }
  420. this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
  421. var currentYear = this.date && this.date.getUTCFullYear();
  422. var months = this.picker.find('.datepicker-months')
  423. .find('th:eq(1)')
  424. .text(year)
  425. .end()
  426. .find('span').removeClass('active');
  427. if (currentYear && currentYear == year) {
  428. months.eq(this.date.getUTCMonth()).addClass('active');
  429. }
  430. if (year < startYear || year > endYear) {
  431. months.addClass('disabled');
  432. }
  433. if (year == startYear) {
  434. months.slice(0, startMonth).addClass('disabled');
  435. }
  436. if (year == endYear) {
  437. months.slice(endMonth+1).addClass('disabled');
  438. }
  439. html = '';
  440. year = parseInt(year/10, 10) * 10;
  441. var yearCont = this.picker.find('.datepicker-years')
  442. .find('th:eq(1)')
  443. .text(year + '-' + (year + 9))
  444. .end()
  445. .find('td');
  446. year -= 1;
  447. for (var i = -1; i < 11; i++) {
  448. html += '<span class="year'+(i == -1 || i == 10 ? ' old' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
  449. year += 1;
  450. }
  451. yearCont.html(html);
  452. },
  453. updateNavArrows: function() {
  454. if (!this._allow_update) return;
  455. var d = new Date(this.viewDate),
  456. year = d.getUTCFullYear(),
  457. month = d.getUTCMonth();
  458. switch (this.viewMode) {
  459. case 0:
  460. if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) {
  461. this.picker.find('.prev').css({visibility: 'hidden'});
  462. } else {
  463. this.picker.find('.prev').css({visibility: 'visible'});
  464. }
  465. if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) {
  466. this.picker.find('.next').css({visibility: 'hidden'});
  467. } else {
  468. this.picker.find('.next').css({visibility: 'visible'});
  469. }
  470. break;
  471. case 1:
  472. case 2:
  473. if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) {
  474. this.picker.find('.prev').css({visibility: 'hidden'});
  475. } else {
  476. this.picker.find('.prev').css({visibility: 'visible'});
  477. }
  478. if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) {
  479. this.picker.find('.next').css({visibility: 'hidden'});
  480. } else {
  481. this.picker.find('.next').css({visibility: 'visible'});
  482. }
  483. break;
  484. }
  485. },
  486. click: function(e) {
  487. e.preventDefault();
  488. var target = $(e.target).closest('span, td, th');
  489. if (target.length == 1) {
  490. switch(target[0].nodeName.toLowerCase()) {
  491. case 'th':
  492. switch(target[0].className) {
  493. case 'switch':
  494. this.showMode(1);
  495. break;
  496. case 'prev':
  497. case 'next':
  498. var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);
  499. switch(this.viewMode){
  500. case 0:
  501. this.viewDate = this.moveMonth(this.viewDate, dir);
  502. break;
  503. case 1:
  504. case 2:
  505. this.viewDate = this.moveYear(this.viewDate, dir);
  506. break;
  507. }
  508. this.fill();
  509. break;
  510. case 'today':
  511. var date = new Date();
  512. date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
  513. this.showMode(-2);
  514. var which = this.todayBtn == 'linked' ? null : 'view';
  515. this._setDate(date, which);
  516. break;
  517. }
  518. break;
  519. case 'span':
  520. if (!target.is('.disabled')) {
  521. this.viewDate.setUTCDate(1);
  522. if (target.is('.month')) {
  523. var day = 1;
  524. var month = target.parent().find('span').index(target);
  525. var year = this.viewDate.getUTCFullYear();
  526. this.viewDate.setUTCMonth(month);
  527. this.element.trigger({
  528. type: 'changeMonth',
  529. date: this.viewDate
  530. });
  531. if ( this.minViewMode == 1 ) {
  532. this._setDate(UTCDate(year, month, day,0,0,0,0));
  533. }
  534. } else {
  535. var year = parseInt(target.text(), 10)||0;
  536. var day = 1;
  537. var month = 0;
  538. this.viewDate.setUTCFullYear(year);
  539. this.element.trigger({
  540. type: 'changeYear',
  541. date: this.viewDate
  542. });
  543. if ( this.minViewMode == 2 ) {
  544. this._setDate(UTCDate(year, month, day,0,0,0,0));
  545. }
  546. }
  547. this.showMode(-1);
  548. this.fill();
  549. }
  550. break;
  551. case 'td':
  552. if (target.is('.day') && !target.is('.disabled')){
  553. var day = parseInt(target.text(), 10)||1;
  554. var year = this.viewDate.getUTCFullYear(),
  555. month = this.viewDate.getUTCMonth();
  556. if (target.is('.old')) {
  557. if (month === 0) {
  558. month = 11;
  559. year -= 1;
  560. } else {
  561. month -= 1;
  562. }
  563. } else if (target.is('.new')) {
  564. if (month == 11) {
  565. month = 0;
  566. year += 1;
  567. } else {
  568. month += 1;
  569. }
  570. }
  571. this._setDate(UTCDate(year, month, day,0,0,0,0));
  572. }
  573. break;
  574. }
  575. }
  576. },
  577. _setDate: function(date, which){
  578. if (!which || which == 'date')
  579. this.date = date;
  580. if (!which || which == 'view')
  581. this.viewDate = date;
  582. this.fill();
  583. this.setValue();
  584. this.element.trigger({
  585. type: 'changeDate',
  586. date: this.date
  587. });
  588. var element;
  589. if (this.isInput) {
  590. element = this.element;
  591. } else if (this.component){
  592. element = this.element.find('input');
  593. }
  594. if (element) {
  595. element.change();
  596. if (this.autoclose && (!which || which == 'date')) {
  597. this.hide();
  598. }
  599. }
  600. },
  601. moveMonth: function(date, dir){
  602. if (!dir) return date;
  603. var new_date = new Date(date.valueOf()),
  604. day = new_date.getUTCDate(),
  605. month = new_date.getUTCMonth(),
  606. mag = Math.abs(dir),
  607. new_month, test;
  608. dir = dir > 0 ? 1 : -1;
  609. if (mag == 1){
  610. test = dir == -1
  611. // If going back one month, make sure month is not current month
  612. // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
  613. ? function(){ return new_date.getUTCMonth() == month; }
  614. // If going forward one month, make sure month is as expected
  615. // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
  616. : function(){ return new_date.getUTCMonth() != new_month; };
  617. new_month = month + dir;
  618. new_date.setUTCMonth(new_month);
  619. // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
  620. if (new_month < 0 || new_month > 11)
  621. new_month = (new_month + 12) % 12;
  622. } else {
  623. // For magnitudes >1, move one month at a time...
  624. for (var i=0; i<mag; i++)
  625. // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
  626. new_date = this.moveMonth(new_date, dir);
  627. // ...then reset the day, keeping it in the new month
  628. new_month = new_date.getUTCMonth();
  629. new_date.setUTCDate(day);
  630. test = function(){ return new_month != new_date.getUTCMonth(); };
  631. }
  632. // Common date-resetting loop -- if date is beyond end of month, make it
  633. // end of month
  634. while (test()){
  635. new_date.setUTCDate(--day);
  636. new_date.setUTCMonth(new_month);
  637. }
  638. return new_date;
  639. },
  640. moveYear: function(date, dir){
  641. return this.moveMonth(date, dir*12);
  642. },
  643. dateWithinRange: function(date){
  644. return date >= this.startDate && date <= this.endDate;
  645. },
  646. keydown: function(e){
  647. if (this.picker.is(':not(:visible)')){
  648. if (e.keyCode == 27) // allow escape to hide and re-show picker
  649. this.show();
  650. return;
  651. }
  652. var dateChanged = false,
  653. dir, day, month,
  654. newDate, newViewDate;
  655. switch(e.keyCode){
  656. case 27: // escape
  657. this.hide();
  658. e.preventDefault();
  659. break;
  660. case 37: // left
  661. case 39: // right
  662. if (!this.keyboardNavigation) break;
  663. dir = e.keyCode == 37 ? -1 : 1;
  664. if (e.ctrlKey){
  665. newDate = this.moveYear(this.date, dir);
  666. newViewDate = this.moveYear(this.viewDate, dir);
  667. } else if (e.shiftKey){
  668. newDate = this.moveMonth(this.date, dir);
  669. newViewDate = this.moveMonth(this.viewDate, dir);
  670. } else {
  671. newDate = new Date(this.date);
  672. newDate.setUTCDate(this.date.getUTCDate() + dir);
  673. newViewDate = new Date(this.viewDate);
  674. newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir);
  675. }
  676. if (this.dateWithinRange(newDate)){
  677. this.date = newDate;
  678. this.viewDate = newViewDate;
  679. this.setValue();
  680. this.update();
  681. e.preventDefault();
  682. dateChanged = true;
  683. }
  684. break;
  685. case 38: // up
  686. case 40: // down
  687. if (!this.keyboardNavigation) break;
  688. dir = e.keyCode == 38 ? -1 : 1;
  689. if (e.ctrlKey){
  690. newDate = this.moveYear(this.date, dir);
  691. newViewDate = this.moveYear(this.viewDate, dir);
  692. } else if (e.shiftKey){
  693. newDate = this.moveMonth(this.date, dir);
  694. newViewDate = this.moveMonth(this.viewDate, dir);
  695. } else {
  696. newDate = new Date(this.date);
  697. newDate.setUTCDate(this.date.getUTCDate() + dir * 7);
  698. newViewDate = new Date(this.viewDate);
  699. newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7);
  700. }
  701. if (this.dateWithinRange(newDate)){
  702. this.date = newDate;
  703. this.viewDate = newViewDate;
  704. this.setValue();
  705. this.update();
  706. e.preventDefault();
  707. dateChanged = true;
  708. }
  709. break;
  710. case 13: // enter
  711. this.hide();
  712. e.preventDefault();
  713. break;
  714. case 9: // tab
  715. this.hide();
  716. break;
  717. }
  718. if (dateChanged){
  719. this.element.trigger({
  720. type: 'changeDate',
  721. date: this.date
  722. });
  723. var element;
  724. if (this.isInput) {
  725. element = this.element;
  726. } else if (this.component){
  727. element = this.element.find('input');
  728. }
  729. if (element) {
  730. element.change();
  731. }
  732. }
  733. },
  734. showMode: function(dir) {
  735. if (dir) {
  736. this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir));
  737. }
  738. /*
  739. vitalets: fixing bug of very special conditions:
  740. jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover.
  741. Method show() does not set display css correctly and datepicker is not shown.
  742. Changed to .css('display', 'block') solve the problem.
  743. See https://github.com/vitalets/x-editable/issues/37
  744. In jquery 1.7.2+ everything works fine.
  745. */
  746. //this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
  747. this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block');
  748. this.updateNavArrows();
  749. }
  750. };
  751. $.fn.datepicker = function ( option ) {
  752. var args = Array.apply(null, arguments);
  753. args.shift();
  754. return this.each(function () {
  755. var $this = $(this),
  756. data = $this.data('datepicker'),
  757. options = typeof option == 'object' && option;
  758. if (!data) {
  759. $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
  760. }
  761. if (typeof option == 'string' && typeof data[option] == 'function') {
  762. data[option].apply(data, args);
  763. }
  764. });
  765. };
  766. $.fn.datepicker.defaults = {
  767. };
  768. $.fn.datepicker.Constructor = Datepicker;
  769. var dates = $.fn.datepicker.dates = {
  770. en: {
  771. days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
  772. daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
  773. daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
  774. months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
  775. monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
  776. today: "Today"
  777. }
  778. };
  779. var DPGlobal = {
  780. modes: [
  781. {
  782. clsName: 'days',
  783. navFnc: 'Month',
  784. navStep: 1
  785. },
  786. {
  787. clsName: 'months',
  788. navFnc: 'FullYear',
  789. navStep: 1
  790. },
  791. {
  792. clsName: 'years',
  793. navFnc: 'FullYear',
  794. navStep: 10
  795. }],
  796. isLeapYear: function (year) {
  797. return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
  798. },
  799. getDaysInMonth: function (year, month) {
  800. return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
  801. },
  802. validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,
  803. nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,
  804. parseFormat: function(format){
  805. // IE treats \0 as a string end in inputs (truncating the value),
  806. // so it's a bad format delimiter, anyway
  807. var separators = format.replace(this.validParts, '\0').split('\0'),
  808. parts = format.match(this.validParts);
  809. if (!separators || !separators.length || !parts || parts.length === 0){
  810. throw new Error("Invalid date format.");
  811. }
  812. return {separators: separators, parts: parts};
  813. },
  814. parseDate: function(date, format, language) {
  815. if (date instanceof Date) return date;
  816. if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) {
  817. var part_re = /([\-+]\d+)([dmwy])/,
  818. parts = date.match(/([\-+]\d+)([dmwy])/g),
  819. part, dir;
  820. date = new Date();
  821. for (var i=0; i<parts.length; i++) {
  822. part = part_re.exec(parts[i]);
  823. dir = parseInt(part[1]);
  824. switch(part[2]){
  825. case 'd':
  826. date.setUTCDate(date.getUTCDate() + dir);
  827. break;
  828. case 'm':
  829. date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);
  830. break;
  831. case 'w':
  832. date.setUTCDate(date.getUTCDate() + dir * 7);
  833. break;
  834. case 'y':
  835. date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);
  836. break;
  837. }
  838. }
  839. return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
  840. }
  841. var parts = date && date.match(this.nonpunctuation) || [],
  842. date = new Date(),
  843. parsed = {},
  844. setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
  845. setters_map = {
  846. yyyy: function(d,v){ return d.setUTCFullYear(v); },
  847. yy: function(d,v){ return d.setUTCFullYear(2000+v); },
  848. m: function(d,v){
  849. v -= 1;
  850. while (v<0) v += 12;
  851. v %= 12;
  852. d.setUTCMonth(v);
  853. while (d.getUTCMonth() != v)
  854. d.setUTCDate(d.getUTCDate()-1);
  855. return d;
  856. },
  857. d: function(d,v){ return d.setUTCDate(v); }
  858. },
  859. val, filtered, part;
  860. setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
  861. setters_map['dd'] = setters_map['d'];
  862. date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
  863. var fparts = format.parts.slice();
  864. // Remove noop parts
  865. if (parts.length != fparts.length) {
  866. fparts = $(fparts).filter(function(i,p){
  867. return $.inArray(p, setters_order) !== -1;
  868. }).toArray();
  869. }
  870. // Process remainder
  871. if (parts.length == fparts.length) {
  872. for (var i=0, cnt = fparts.length; i < cnt; i++) {
  873. val = parseInt(parts[i], 10);
  874. part = fparts[i];
  875. if (isNaN(val)) {
  876. switch(part) {
  877. case 'MM':
  878. filtered = $(dates[language].months).filter(function(){
  879. var m = this.slice(0, parts[i].length),
  880. p = parts[i].slice(0, m.length);
  881. return m == p;
  882. });
  883. val = $.inArray(filtered[0], dates[language].months) + 1;
  884. break;
  885. case 'M':
  886. filtered = $(dates[language].monthsShort).filter(function(){
  887. var m = this.slice(0, parts[i].length),
  888. p = parts[i].slice(0, m.length);
  889. return m == p;
  890. });
  891. val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
  892. break;
  893. }
  894. }
  895. parsed[part] = val;
  896. }
  897. for (var i=0, s; i<setters_order.length; i++){
  898. s = setters_order[i];
  899. if (s in parsed && !isNaN(parsed[s]))
  900. setters_map[s](date, parsed[s]);
  901. }
  902. }
  903. return date;
  904. },
  905. formatDate: function(date, format, language){
  906. var val = {
  907. d: date.getUTCDate(),
  908. D: dates[language].daysShort[date.getUTCDay()],
  909. DD: dates[language].days[date.getUTCDay()],
  910. m: date.getUTCMonth() + 1,
  911. M: dates[language].monthsShort[date.getUTCMonth()],
  912. MM: dates[language].months[date.getUTCMonth()],
  913. yy: date.getUTCFullYear().toString().substring(2),
  914. yyyy: date.getUTCFullYear()
  915. };
  916. val.dd = (val.d < 10 ? '0' : '') + val.d;
  917. val.mm = (val.m < 10 ? '0' : '') + val.m;
  918. var date = [],
  919. seps = $.extend([], format.separators);
  920. for (var i=0, cnt = format.parts.length; i < cnt; i++) {
  921. if (seps.length)
  922. date.push(seps.shift());
  923. date.push(val[format.parts[i]]);
  924. }
  925. return date.join('');
  926. },
  927. headTemplate: '<thead>'+
  928. '<tr>'+
  929. '<th class="prev"><i class="icon-arrow-left"/></th>'+
  930. '<th colspan="5" class="switch"></th>'+
  931. '<th class="next"><i class="icon-arrow-right"/></th>'+
  932. '</tr>'+
  933. '</thead>',
  934. contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
  935. footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr></tfoot>'
  936. };
  937. DPGlobal.template = '<div class="datepicker">'+
  938. '<div class="datepicker-days">'+
  939. '<table class=" table-condensed">'+
  940. DPGlobal.headTemplate+
  941. '<tbody></tbody>'+
  942. DPGlobal.footTemplate+
  943. '</table>'+
  944. '</div>'+
  945. '<div class="datepicker-months">'+
  946. '<table class="table-condensed">'+
  947. DPGlobal.headTemplate+
  948. DPGlobal.contTemplate+
  949. DPGlobal.footTemplate+
  950. '</table>'+
  951. '</div>'+
  952. '<div class="datepicker-years">'+
  953. '<table class="table-condensed">'+
  954. DPGlobal.headTemplate+
  955. DPGlobal.contTemplate+
  956. DPGlobal.footTemplate+
  957. '</table>'+
  958. '</div>'+
  959. '</div>';
  960. $.fn.datepicker.DPGlobal = DPGlobal;
  961. }( window.jQuery );