bootstrap-datepicker.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. /* =========================================================
  2. * bootstrap-datepicker.js
  3. * http://www.eyecon.ro/bootstrap-datepicker
  4. * =========================================================
  5. * Copyright 2012 Stefan Petre
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. * ========================================================= */
  19. !function( $ ) {
  20. // Picker object
  21. var Datepicker = function(element, options){
  22. this.element = $(element);
  23. this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
  24. this.picker = $(DPGlobal.template)
  25. .appendTo('body')
  26. .on({
  27. click: $.proxy(this.click, this),
  28. mousedown: $.proxy(this.mousedown, this)
  29. });
  30. this.isInput = this.element.is('input');
  31. this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
  32. if (this.isInput) {
  33. this.element.on({
  34. focus: $.proxy(this.show, this),
  35. blur: $.proxy(this.hide, this),
  36. keyup: $.proxy(this.update, this)
  37. });
  38. } else {
  39. if (this.component){
  40. this.component.on('click', $.proxy(this.show, this));
  41. } else {
  42. this.element.on('click', $.proxy(this.show, this));
  43. }
  44. }
  45. this.minViewMode = options.minViewMode||this.element.data('date-minviewmode')||0;
  46. if (typeof this.minViewMode === 'string') {
  47. switch (this.minViewMode) {
  48. case 'months':
  49. this.minViewMode = 1;
  50. break;
  51. case 'years':
  52. this.minViewMode = 2;
  53. break;
  54. default:
  55. this.minViewMode = 0;
  56. break;
  57. }
  58. }
  59. this.viewMode = options.viewMode||this.element.data('date-viewmode')||0;
  60. if (typeof this.viewMode === 'string') {
  61. switch (this.viewMode) {
  62. case 'months':
  63. this.viewMode = 1;
  64. break;
  65. case 'years':
  66. this.viewMode = 2;
  67. break;
  68. default:
  69. this.viewMode = 0;
  70. break;
  71. }
  72. }
  73. this.startViewMode = this.viewMode;
  74. this.weekStart = options.weekStart||this.element.data('date-weekstart')||0;
  75. this.weekEnd = this.weekStart === 0 ? 6 : this.weekStart - 1;
  76. this.fillDow();
  77. this.fillMonths();
  78. this.update();
  79. this.showMode();
  80. };
  81. Datepicker.prototype = {
  82. constructor: Datepicker,
  83. show: function(e) {
  84. this.picker.show();
  85. this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
  86. this.place();
  87. $(window).on('resize', $.proxy(this.place, this));
  88. if (e ) {
  89. e.stopPropagation();
  90. e.preventDefault();
  91. }
  92. if (!this.isInput) {
  93. $(document).on('mousedown', $.proxy(this.hide, this));
  94. }
  95. this.element.trigger({
  96. type: 'show',
  97. date: this.date
  98. });
  99. },
  100. hide: function(){
  101. this.picker.hide();
  102. $(window).off('resize', this.place);
  103. this.viewMode = this.startViewMode;
  104. this.showMode();
  105. if (!this.isInput) {
  106. $(document).off('mousedown', this.hide);
  107. }
  108. this.set();
  109. this.element.trigger({
  110. type: 'hide',
  111. date: this.date
  112. });
  113. },
  114. set: function() {
  115. var formated = DPGlobal.formatDate(this.date, this.format);
  116. if (!this.isInput) {
  117. if (this.component){
  118. this.element.find('input').prop('value', formated);
  119. }
  120. this.element.data('date', formated);
  121. } else {
  122. this.element.prop('value', formated);
  123. }
  124. },
  125. setValue: function(newDate) {
  126. if (typeof newDate === 'string') {
  127. this.date = DPGlobal.parseDate(newDate, this.format);
  128. } else {
  129. this.date = new Date(newDate);
  130. }
  131. this.set();
  132. this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
  133. this.fill();
  134. },
  135. place: function(){
  136. var offset = this.component ? this.component.offset() : this.element.offset();
  137. this.picker.css({
  138. top: offset.top + this.height,
  139. left: offset.left
  140. });
  141. },
  142. update: function(newDate){
  143. this.date = DPGlobal.parseDate(
  144. typeof newDate === 'string' ? newDate : (this.isInput ? this.element.prop('value') : this.element.data('date')),
  145. this.format
  146. );
  147. this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
  148. this.fill();
  149. },
  150. fillDow: function(){
  151. var dowCnt = this.weekStart;
  152. var html = '<tr>';
  153. while (dowCnt < this.weekStart + 7) {
  154. html += '<th class="dow">'+DPGlobal.dates.daysMin[(dowCnt++)%7]+'</th>';
  155. }
  156. html += '</tr>';
  157. this.picker.find('.datepicker-days thead').append(html);
  158. },
  159. fillMonths: function(){
  160. var html = '';
  161. var i = 0
  162. while (i < 12) {
  163. html += '<span class="month">'+DPGlobal.dates.monthsShort[i++]+'</span>';
  164. }
  165. this.picker.find('.datepicker-months td').append(html);
  166. },
  167. fill: function() {
  168. var d = new Date(this.viewDate),
  169. year = d.getFullYear(),
  170. month = d.getMonth(),
  171. currentDate = this.date.valueOf();
  172. this.picker.find('.datepicker-days th:eq(1)')
  173. .text(DPGlobal.dates.months[month]+' '+year);
  174. var prevMonth = new Date(year, month-1, 28,0,0,0,0),
  175. day = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth());
  176. prevMonth.setDate(day);
  177. prevMonth.setDate(day - (prevMonth.getDay() - this.weekStart + 7)%7);
  178. var nextMonth = new Date(prevMonth);
  179. nextMonth.setDate(nextMonth.getDate() + 42);
  180. nextMonth = nextMonth.valueOf();
  181. html = [];
  182. var clsName;
  183. while(prevMonth.valueOf() < nextMonth) {
  184. if (prevMonth.getDay() === this.weekStart) {
  185. html.push('<tr>');
  186. }
  187. clsName = '';
  188. if (prevMonth.getMonth() < month) {
  189. clsName += ' old';
  190. } else if (prevMonth.getMonth() > month) {
  191. clsName += ' new';
  192. }
  193. if (prevMonth.valueOf() === currentDate) {
  194. clsName += ' active';
  195. }
  196. html.push('<td class="day'+clsName+'">'+prevMonth.getDate() + '</td>');
  197. if (prevMonth.getDay() === this.weekEnd) {
  198. html.push('</tr>');
  199. }
  200. prevMonth.setDate(prevMonth.getDate()+1);
  201. }
  202. this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
  203. var currentYear = this.date.getFullYear();
  204. var months = this.picker.find('.datepicker-months')
  205. .find('th:eq(1)')
  206. .text(year)
  207. .end()
  208. .find('span').removeClass('active');
  209. if (currentYear === year) {
  210. months.eq(this.date.getMonth()).addClass('active');
  211. }
  212. html = '';
  213. year = parseInt(year/10, 10) * 10;
  214. var yearCont = this.picker.find('.datepicker-years')
  215. .find('th:eq(1)')
  216. .text(year + '-' + (year + 9))
  217. .end()
  218. .find('td');
  219. year -= 1;
  220. for (var i = -1; i < 11; i++) {
  221. html += '<span class="year'+(i === -1 || i === 10 ? ' old' : '')+(currentYear === year ? ' active' : '')+'">'+year+'</span>';
  222. year += 1;
  223. }
  224. yearCont.html(html);
  225. },
  226. click: function(e) {
  227. e.stopPropagation();
  228. e.preventDefault();
  229. var target = $(e.target).closest('span, td, th');
  230. if (target.length === 1) {
  231. switch(target[0].nodeName.toLowerCase()) {
  232. case 'th':
  233. switch(target[0].className) {
  234. case 'switch':
  235. this.showMode(1);
  236. break;
  237. case 'prev':
  238. case 'next':
  239. this.viewDate['set'+DPGlobal.modes[this.viewMode].navFnc].call(
  240. this.viewDate,
  241. this.viewDate['get'+DPGlobal.modes[this.viewMode].navFnc].call(this.viewDate) +
  242. DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1)
  243. );
  244. this.fill();
  245. this.set();
  246. break;
  247. }
  248. break;
  249. case 'span':
  250. if (target.is('.month')) {
  251. var month = target.parent().find('span').index(target);
  252. this.viewDate.setMonth(month);
  253. } else {
  254. var year = parseInt(target.text(), 10)||0;
  255. this.viewDate.setFullYear(year);
  256. }
  257. if (this.viewMode !== 0) {
  258. this.date = new Date(this.viewDate);
  259. this.element.trigger({
  260. type: 'changeDate',
  261. date: this.date,
  262. viewMode: DPGlobal.modes[this.viewMode].clsName
  263. });
  264. }
  265. this.showMode(-1);
  266. this.fill();
  267. this.set();
  268. break;
  269. case 'td':
  270. if (target.is('.day')){
  271. var day = parseInt(target.text(), 10)||1;
  272. var month = this.viewDate.getMonth();
  273. if (target.is('.old')) {
  274. month -= 1;
  275. } else if (target.is('.new')) {
  276. month += 1;
  277. }
  278. var year = this.viewDate.getFullYear();
  279. this.date = new Date(year, month, day,0,0,0,0);
  280. this.viewDate = new Date(year, month, Math.min(28, day),0,0,0,0);
  281. this.fill();
  282. this.set();
  283. this.element.trigger({
  284. type: 'changeDate',
  285. date: this.date,
  286. viewMode: DPGlobal.modes[this.viewMode].clsName
  287. });
  288. }
  289. break;
  290. }
  291. }
  292. },
  293. mousedown: function(e){
  294. e.stopPropagation();
  295. e.preventDefault();
  296. },
  297. showMode: function(dir) {
  298. if (dir) {
  299. this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir));
  300. }
  301. this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
  302. }
  303. };
  304. $.fn.datepicker = function ( option, val ) {
  305. return this.each(function () {
  306. var $this = $(this),
  307. data = $this.data('datepicker'),
  308. options = typeof option === 'object' && option;
  309. if (!data) {
  310. $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
  311. }
  312. if (typeof option === 'string') data[option](val);
  313. });
  314. };
  315. $.fn.datepicker.defaults = {
  316. };
  317. $.fn.datepicker.Constructor = Datepicker;
  318. var DPGlobal = {
  319. modes: [
  320. {
  321. clsName: 'days',
  322. navFnc: 'Month',
  323. navStep: 1
  324. },
  325. {
  326. clsName: 'months',
  327. navFnc: 'FullYear',
  328. navStep: 1
  329. },
  330. {
  331. clsName: 'years',
  332. navFnc: 'FullYear',
  333. navStep: 10
  334. }],
  335. dates:{
  336. days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
  337. daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
  338. daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
  339. months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
  340. monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
  341. },
  342. isLeapYear: function (year) {
  343. return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
  344. },
  345. getDaysInMonth: function (year, month) {
  346. return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
  347. },
  348. parseFormat: function(format){
  349. var separator = format.match(/[.\/\-\s].*?/),
  350. parts = format.split(/\W+/);
  351. if (!separator || !parts || parts.length === 0){
  352. throw new Error("Invalid date format.");
  353. }
  354. return {separator: separator, parts: parts};
  355. },
  356. parseDate: function(date, format) {
  357. var parts = date.split(format.separator),
  358. date = new Date(),
  359. val;
  360. date.setHours(0);
  361. date.setMinutes(0);
  362. date.setSeconds(0);
  363. date.setMilliseconds(0);
  364. if (parts.length === format.parts.length) {
  365. for (var i=0, cnt = format.parts.length; i < cnt; i++) {
  366. val = parseInt(parts[i], 10)||1;
  367. switch(format.parts[i]) {
  368. case 'dd':
  369. case 'd':
  370. date.setDate(val);
  371. break;
  372. case 'mm':
  373. case 'm':
  374. date.setMonth(val - 1);
  375. break;
  376. case 'yy':
  377. date.setFullYear(2000 + val);
  378. break;
  379. case 'yyyy':
  380. date.setFullYear(val);
  381. break;
  382. }
  383. }
  384. }
  385. return date;
  386. },
  387. formatDate: function(date, format){
  388. var val = {
  389. d: date.getDate(),
  390. m: date.getMonth() + 1,
  391. yy: date.getFullYear().toString().substring(2),
  392. yyyy: date.getFullYear()
  393. };
  394. val.dd = (val.d < 10 ? '0' : '') + val.d;
  395. val.mm = (val.m < 10 ? '0' : '') + val.m;
  396. var date = [];
  397. for (var i=0, cnt = format.parts.length; i < cnt; i++) {
  398. date.push(val[format.parts[i]]);
  399. }
  400. return date.join(format.separator);
  401. },
  402. headTemplate: '<thead>'+
  403. '<tr>'+
  404. '<th class="prev">&lsaquo;</th>'+
  405. '<th colspan="5" class="switch"></th>'+
  406. '<th class="next">&rsaquo;</th>'+
  407. '</tr>'+
  408. '</thead>',
  409. contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>'
  410. };
  411. DPGlobal.template = '<div class="datepicker dropdown-menu">'+
  412. '<div class="datepicker-days">'+
  413. '<table class=" table-condensed">'+
  414. DPGlobal.headTemplate+
  415. '<tbody></tbody>'+
  416. '</table>'+
  417. '</div>'+
  418. '<div class="datepicker-months">'+
  419. '<table class="table-condensed">'+
  420. DPGlobal.headTemplate+
  421. DPGlobal.contTemplate+
  422. '</table>'+
  423. '</div>'+
  424. '<div class="datepicker-years">'+
  425. '<table class="table-condensed">'+
  426. DPGlobal.headTemplate+
  427. DPGlobal.contTemplate+
  428. '</table>'+
  429. '</div>'+
  430. '</div>';
  431. }( window.jQuery )