1 /* =========================================================
2 * bootstrap-datepicker.js
3 * http://www.eyecon.ro/bootstrap-datepicker
4 * =========================================================
5 * Copyright 2012 Stefan Petre
6 * Improvements by Andrew Rowls
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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 * ========================================================= */
23 var $window = $(window);
26 return new Date(Date.UTC.apply(Date, arguments));
29 var today = new Date();
30 return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
36 var Datepicker = function(element, options) {
39 this._process_options(options);
41 this.element = $(element);
42 this.isInline = false;
43 this.isInput = this.element.is('input');
44 this.component = this.element.is('.date') ? this.element.find('.add-on, .btn') : false;
45 this.hasInput = this.component && this.element.find('input').length;
46 if(this.component && this.component.length === 0)
47 this.component = false;
49 this.picker = $(DPGlobal.template);
54 this.picker.addClass('datepicker-inline').appendTo(this.element);
56 this.picker.addClass('datepicker-dropdown dropdown-menu');
60 this.picker.addClass('datepicker-rtl');
61 this.picker.find('.prev i, .next i')
62 .toggleClass('icon-arrow-left icon-arrow-right');
66 this.viewMode = this.o.startView;
68 if (this.o.calendarWeeks)
69 this.picker.find('tfoot th.today')
70 .attr('colspan', function(i, val){
71 return parseInt(val) + 1;
74 this._allow_update = false;
76 this.setStartDate(this._o.startDate);
77 this.setEndDate(this._o.endDate);
78 this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled);
83 this._allow_update = true;
93 Datepicker.prototype = {
94 constructor: Datepicker,
96 _process_options: function(opts){
97 // Store raw options for reference
98 this._o = $.extend({}, this._o, opts);
100 var o = this.o = $.extend({}, this._o);
102 // Check if "de-DE" style date is available, if not language should
103 // fallback to 2 letter code eg "de"
104 var lang = o.language;
106 lang = lang.split('-')[0];
108 lang = defaults.language;
125 switch (o.minViewMode) {
138 o.startView = Math.max(o.startView, o.minViewMode);
141 o.weekEnd = ((o.weekStart + 6) % 7);
143 var format = DPGlobal.parseFormat(o.format);
144 if (o.startDate !== -Infinity) {
146 if (o.startDate instanceof Date)
147 o.startDate = this._local_to_utc(this._zero_time(o.startDate));
149 o.startDate = DPGlobal.parseDate(o.startDate, format, o.language);
151 o.startDate = -Infinity;
154 if (o.endDate !== Infinity) {
156 if (o.endDate instanceof Date)
157 o.endDate = this._local_to_utc(this._zero_time(o.endDate));
159 o.endDate = DPGlobal.parseDate(o.endDate, format, o.language);
161 o.endDate = Infinity;
165 o.daysOfWeekDisabled = o.daysOfWeekDisabled||[];
166 if (!$.isArray(o.daysOfWeekDisabled))
167 o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/);
168 o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function (d) {
169 return parseInt(d, 10);
172 var plc = String(o.orientation).toLowerCase().split(/\s+/g),
173 _plc = o.orientation.toLowerCase();
174 plc = $.grep(plc, function(word){
175 return (/^auto|left|right|top|bottom$/).test(word);
177 o.orientation = {x: 'auto', y: 'auto'};
178 if (!_plc || _plc === 'auto')
180 else if (plc.length === 1){
184 o.orientation.y = plc[0];
188 o.orientation.x = plc[0];
193 _plc = $.grep(plc, function(word){
194 return (/^left|right$/).test(word);
196 o.orientation.x = _plc[0] || 'auto';
198 _plc = $.grep(plc, function(word){
199 return (/^top|bottom$/).test(word);
201 o.orientation.y = _plc[0] || 'auto';
205 _secondaryEvents: [],
206 _applyEvents: function(evs){
207 for (var i=0, el, ev; i<evs.length; i++){
213 _unapplyEvents: function(evs){
214 for (var i=0, el, ev; i<evs.length; i++){
220 _buildEvents: function(){
221 if (this.isInput) { // single input
224 focus: $.proxy(this.show, this),
225 keyup: $.proxy(this.update, this),
226 keydown: $.proxy(this.keydown, this)
230 else if (this.component && this.hasInput){ // component: input + button
232 // For components that are not readonly, allow keyboard nav
233 [this.element.find('input'), {
234 focus: $.proxy(this.show, this),
235 keyup: $.proxy(this.update, this),
236 keydown: $.proxy(this.keydown, this)
239 click: $.proxy(this.show, this)
243 else if (this.element.is('div')) { // inline datepicker
244 this.isInline = true;
249 click: $.proxy(this.show, this)
254 this._secondaryEvents = [
256 click: $.proxy(this.click, this)
259 resize: $.proxy(this.place, this)
262 'mousedown touchstart': $.proxy(function (e) {
263 // Clicked outside the datepicker, hide it
265 this.element.is(e.target) ||
266 this.element.find(e.target).length ||
267 this.picker.is(e.target) ||
268 this.picker.find(e.target).length
276 _attachEvents: function(){
277 this._detachEvents();
278 this._applyEvents(this._events);
280 _detachEvents: function(){
281 this._unapplyEvents(this._events);
283 _attachSecondaryEvents: function(){
284 this._detachSecondaryEvents();
285 this._applyEvents(this._secondaryEvents);
287 _detachSecondaryEvents: function(){
288 this._unapplyEvents(this._secondaryEvents);
290 _trigger: function(event, altdate){
291 var date = altdate || this.date,
292 local_date = this._utc_to_local(date);
294 this.element.trigger({
297 format: $.proxy(function(altformat){
298 var format = altformat || this.o.format;
299 return DPGlobal.formatDate(date, format, this.o.language);
306 this.picker.appendTo('body');
308 this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
310 this._attachSecondaryEvents();
314 this._trigger('show');
318 if(this.isInline) return;
319 if (!this.picker.is(':visible')) return;
320 this.picker.hide().detach();
321 this._detachSecondaryEvents();
322 this.viewMode = this.o.startView;
328 this.isInput && this.element.val() ||
329 this.hasInput && this.element.find('input').val()
333 this._trigger('hide');
338 this._detachEvents();
339 this._detachSecondaryEvents();
340 this.picker.remove();
341 delete this.element.data().datepicker;
343 delete this.element.data().date;
347 _utc_to_local: function(utc){
348 return new Date(utc.getTime() + (utc.getTimezoneOffset()*60000));
350 _local_to_utc: function(local){
351 return new Date(local.getTime() - (local.getTimezoneOffset()*60000));
353 _zero_time: function(local){
354 return new Date(local.getFullYear(), local.getMonth(), local.getDate());
356 _zero_utc_time: function(utc){
357 return new Date(Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate()));
360 getDate: function() {
361 return this._utc_to_local(this.getUTCDate());
364 getUTCDate: function() {
368 setDate: function(d) {
369 this.setUTCDate(this._local_to_utc(d));
372 setUTCDate: function(d) {
377 setValue: function() {
378 var formatted = this.getFormattedDate();
381 this.element.find('input').val(formatted).change();
384 this.element.val(formatted).change();
388 getFormattedDate: function(format) {
389 if (format === undefined)
390 format = this.o.format;
391 return DPGlobal.formatDate(this.date, format, this.o.language);
394 setStartDate: function(startDate){
395 this._process_options({startDate: startDate});
397 this.updateNavArrows();
400 setEndDate: function(endDate){
401 this._process_options({endDate: endDate});
403 this.updateNavArrows();
406 setDaysOfWeekDisabled: function(daysOfWeekDisabled){
407 this._process_options({daysOfWeekDisabled: daysOfWeekDisabled});
409 this.updateNavArrows();
413 if(this.isInline) return;
414 var calendarWidth = this.picker.outerWidth(),
415 calendarHeight = this.picker.outerHeight(),
417 windowWidth = $window.width(),
418 windowHeight = $window.height(),
419 scrollTop = $window.scrollTop();
421 var zIndex = parseInt(this.element.parents().filter(function() {
422 return $(this).css('z-index') != 'auto';
423 }).first().css('z-index'))+10;
424 var offset = this.component ? this.component.parent().offset() : this.element.offset();
425 var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false);
426 var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false);
427 var left = offset.left,
430 this.picker.removeClass(
431 'datepicker-orient-top datepicker-orient-bottom '+
432 'datepicker-orient-right datepicker-orient-left'
435 if (this.o.orientation.x !== 'auto') {
436 this.picker.addClass('datepicker-orient-' + this.o.orientation.x);
437 if (this.o.orientation.x === 'right')
438 left -= calendarWidth - width;
440 // auto x orientation is best-placement: if it crosses a window
441 // edge, fudge it sideways
444 this.picker.addClass('datepicker-orient-left');
446 left -= offset.left - visualPadding;
447 else if (offset.left + calendarWidth > windowWidth)
448 left = windowWidth - calendarWidth - visualPadding;
451 // auto y orientation is best-situation: top or bottom, no fudging,
452 // decision based on which shows more of the calendar
453 var yorient = this.o.orientation.y,
454 top_overflow, bottom_overflow;
455 if (yorient === 'auto') {
456 top_overflow = -scrollTop + offset.top - calendarHeight;
457 bottom_overflow = scrollTop + windowHeight - (offset.top + height + calendarHeight);
458 if (Math.max(top_overflow, bottom_overflow) === bottom_overflow)
463 this.picker.addClass('datepicker-orient-' + yorient);
464 if (yorient === 'top')
467 top -= calendarHeight + parseInt(this.picker.css('padding-top'));
478 if (!this._allow_update) return;
480 var oldDate = new Date(this.date),
481 date, fromArgs = false;
482 if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {
484 if (date instanceof Date)
485 date = this._local_to_utc(date);
488 date = this.isInput ? this.element.val() : this.element.data('date') || this.element.find('input').val();
489 delete this.element.data().date;
492 this.date = DPGlobal.parseDate(date, this.o.format, this.o.language);
495 // setting date by clicking
498 // setting date by typing
499 if (oldDate.getTime() !== this.date.getTime())
500 this._trigger('changeDate');
503 this._trigger('clearDate');
506 if (this.date < this.o.startDate) {
507 this.viewDate = new Date(this.o.startDate);
508 this.date = new Date(this.o.startDate);
509 } else if (this.date > this.o.endDate) {
510 this.viewDate = new Date(this.o.endDate);
511 this.date = new Date(this.o.endDate);
513 this.viewDate = new Date(this.date);
514 this.date = new Date(this.date);
520 var dowCnt = this.o.weekStart,
522 if(this.o.calendarWeeks){
523 var cell = '<th class="cw"> </th>';
525 this.picker.find('.datepicker-days thead tr:first-child').prepend(cell);
527 while (dowCnt < this.o.weekStart + 7) {
528 html += '<th class="dow">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>';
531 this.picker.find('.datepicker-days thead').append(html);
534 fillMonths: function(){
538 html += '<span class="month">'+dates[this.o.language].monthsShort[i++]+'</span>';
540 this.picker.find('.datepicker-months td').html(html);
543 setRange: function(range){
544 if (!range || !range.length)
547 this.range = $.map(range, function(d){ return d.valueOf(); });
551 getClassNames: function(date){
553 year = this.viewDate.getUTCFullYear(),
554 month = this.viewDate.getUTCMonth(),
555 currentDate = this.date.valueOf(),
557 if (date.getUTCFullYear() < year || (date.getUTCFullYear() == year && date.getUTCMonth() < month)) {
559 } else if (date.getUTCFullYear() > year || (date.getUTCFullYear() == year && date.getUTCMonth() > month)) {
562 // Compare internal UTC date with local today, not UTC today
563 if (this.o.todayHighlight &&
564 date.getUTCFullYear() == today.getFullYear() &&
565 date.getUTCMonth() == today.getMonth() &&
566 date.getUTCDate() == today.getDate()) {
569 if (date.valueOf() == currentDate) {
572 if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate ||
573 $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1) {
574 cls.push('disabled');
577 if (date > this.range[0] && date < this.range[this.range.length-1]){
580 if ($.inArray(date.valueOf(), this.range) != -1){
581 cls.push('selected');
588 var d = new Date(this.viewDate),
589 year = d.getUTCFullYear(),
590 month = d.getUTCMonth(),
591 startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity,
592 startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity,
593 endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity,
594 endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity,
595 currentDate = this.date && this.date.valueOf(),
597 this.picker.find('.datepicker-days thead th.datepicker-switch')
598 .text(dates[this.o.language].months[month]+' '+year);
599 this.picker.find('tfoot th.today')
600 .text(dates[this.o.language].today)
601 .toggle(this.o.todayBtn !== false);
602 this.picker.find('tfoot th.clear')
603 .text(dates[this.o.language].clear)
604 .toggle(this.o.clearBtn !== false);
605 this.updateNavArrows();
607 var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
608 day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
609 prevMonth.setUTCDate(day);
610 prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7);
611 var nextMonth = new Date(prevMonth);
612 nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
613 nextMonth = nextMonth.valueOf();
616 while(prevMonth.valueOf() < nextMonth) {
617 if (prevMonth.getUTCDay() == this.o.weekStart) {
619 if(this.o.calendarWeeks){
620 // ISO 8601: First week contains first thursday.
621 // ISO also states week starts on Monday, but we can be more abstract here.
623 // Start of current week: based on weekstart/current date
624 ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5),
625 // Thursday of this week
626 th = new Date(+ws + (7 + 4 - ws.getUTCDay()) % 7 * 864e5),
627 // First Thursday of year, year from thursday
628 yth = new Date(+(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5),
629 // Calendar week: ms between thursdays, div ms per day, div 7 days
630 calWeek = (th - yth) / 864e5 / 7 + 1;
631 html.push('<td class="cw">'+ calWeek +'</td>');
635 clsName = this.getClassNames(prevMonth);
638 if (this.o.beforeShowDay !== $.noop){
639 var before = this.o.beforeShowDay(this._utc_to_local(prevMonth));
640 if (before === undefined)
642 else if (typeof(before) === 'boolean')
643 before = {enabled: before};
644 else if (typeof(before) === 'string')
645 before = {classes: before};
646 if (before.enabled === false)
647 clsName.push('disabled');
649 clsName = clsName.concat(before.classes.split(/\s+/));
651 tooltip = before.tooltip;
654 clsName = $.unique(clsName);
655 html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + '>'+prevMonth.getUTCDate() + '</td>');
656 if (prevMonth.getUTCDay() == this.o.weekEnd) {
659 prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
661 this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
662 var currentYear = this.date && this.date.getUTCFullYear();
664 var months = this.picker.find('.datepicker-months')
668 .find('span').removeClass('active');
669 if (currentYear && currentYear == year) {
670 months.eq(this.date.getUTCMonth()).addClass('active');
672 if (year < startYear || year > endYear) {
673 months.addClass('disabled');
675 if (year == startYear) {
676 months.slice(0, startMonth).addClass('disabled');
678 if (year == endYear) {
679 months.slice(endMonth+1).addClass('disabled');
683 year = parseInt(year/10, 10) * 10;
684 var yearCont = this.picker.find('.datepicker-years')
686 .text(year + '-' + (year + 9))
690 for (var i = -1; i < 11; i++) {
691 html += '<span class="year'+(i == -1 ? ' old' : i == 10 ? ' new' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
697 updateNavArrows: function() {
698 if (!this._allow_update) return;
700 var d = new Date(this.viewDate),
701 year = d.getUTCFullYear(),
702 month = d.getUTCMonth();
703 switch (this.viewMode) {
705 if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()) {
706 this.picker.find('.prev').css({visibility: 'hidden'});
708 this.picker.find('.prev').css({visibility: 'visible'});
710 if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()) {
711 this.picker.find('.next').css({visibility: 'hidden'});
713 this.picker.find('.next').css({visibility: 'visible'});
718 if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()) {
719 this.picker.find('.prev').css({visibility: 'hidden'});
721 this.picker.find('.prev').css({visibility: 'visible'});
723 if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()) {
724 this.picker.find('.next').css({visibility: 'hidden'});
726 this.picker.find('.next').css({visibility: 'visible'});
734 var target = $(e.target).closest('span, td, th');
735 if (target.length == 1) {
736 switch(target[0].nodeName.toLowerCase()) {
738 switch(target[0].className) {
739 case 'datepicker-switch':
744 var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);
745 switch(this.viewMode){
747 this.viewDate = this.moveMonth(this.viewDate, dir);
748 this._trigger('changeMonth', this.viewDate);
752 this.viewDate = this.moveYear(this.viewDate, dir);
753 if (this.viewMode === 1)
754 this._trigger('changeYear', this.viewDate);
760 var date = new Date();
761 date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
764 var which = this.o.todayBtn == 'linked' ? null : 'view';
765 this._setDate(date, which);
770 element = this.element;
771 else if (this.component)
772 element = this.element.find('input');
774 element.val("").change();
775 this._trigger('changeDate');
777 if (this.o.autoclose)
783 if (!target.is('.disabled')) {
784 this.viewDate.setUTCDate(1);
785 if (target.is('.month')) {
787 var month = target.parent().find('span').index(target);
788 var year = this.viewDate.getUTCFullYear();
789 this.viewDate.setUTCMonth(month);
790 this._trigger('changeMonth', this.viewDate);
791 if (this.o.minViewMode === 1) {
792 this._setDate(UTCDate(year, month, day,0,0,0,0));
795 var year = parseInt(target.text(), 10)||0;
798 this.viewDate.setUTCFullYear(year);
799 this._trigger('changeYear', this.viewDate);
800 if (this.o.minViewMode === 2) {
801 this._setDate(UTCDate(year, month, day,0,0,0,0));
809 if (target.is('.day') && !target.is('.disabled')){
810 var day = parseInt(target.text(), 10)||1;
811 var year = this.viewDate.getUTCFullYear(),
812 month = this.viewDate.getUTCMonth();
813 if (target.is('.old')) {
820 } else if (target.is('.new')) {
828 this._setDate(UTCDate(year, month, day,0,0,0,0));
835 _setDate: function(date, which){
836 if (!which || which == 'date')
837 this.date = new Date(date);
838 if (!which || which == 'view')
839 this.viewDate = new Date(date);
842 this._trigger('changeDate');
845 element = this.element;
846 } else if (this.component){
847 element = this.element.find('input');
852 if (this.o.autoclose && (!which || which == 'date')) {
857 moveMonth: function(date, dir){
858 if (!dir) return date;
859 var new_date = new Date(date.valueOf()),
860 day = new_date.getUTCDate(),
861 month = new_date.getUTCMonth(),
864 dir = dir > 0 ? 1 : -1;
867 // If going back one month, make sure month is not current month
868 // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
869 ? function(){ return new_date.getUTCMonth() == month; }
870 // If going forward one month, make sure month is as expected
871 // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
872 : function(){ return new_date.getUTCMonth() != new_month; };
873 new_month = month + dir;
874 new_date.setUTCMonth(new_month);
875 // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
876 if (new_month < 0 || new_month > 11)
877 new_month = (new_month + 12) % 12;
879 // For magnitudes >1, move one month at a time...
880 for (var i=0; i<mag; i++)
881 // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
882 new_date = this.moveMonth(new_date, dir);
883 // ...then reset the day, keeping it in the new month
884 new_month = new_date.getUTCMonth();
885 new_date.setUTCDate(day);
886 test = function(){ return new_month != new_date.getUTCMonth(); };
888 // Common date-resetting loop -- if date is beyond end of month, make it
891 new_date.setUTCDate(--day);
892 new_date.setUTCMonth(new_month);
897 moveYear: function(date, dir){
898 return this.moveMonth(date, dir*12);
901 dateWithinRange: function(date){
902 return date >= this.o.startDate && date <= this.o.endDate;
905 keydown: function(e){
906 if (this.picker.is(':not(:visible)')){
907 if (e.keyCode == 27) // allow escape to hide and re-show picker
911 var dateChanged = false,
913 newDate, newViewDate;
921 if (!this.o.keyboardNavigation) break;
922 dir = e.keyCode == 37 ? -1 : 1;
924 newDate = this.moveYear(this.date, dir);
925 newViewDate = this.moveYear(this.viewDate, dir);
926 this._trigger('changeYear', this.viewDate);
927 } else if (e.shiftKey){
928 newDate = this.moveMonth(this.date, dir);
929 newViewDate = this.moveMonth(this.viewDate, dir);
930 this._trigger('changeMonth', this.viewDate);
932 newDate = new Date(this.date);
933 newDate.setUTCDate(this.date.getUTCDate() + dir);
934 newViewDate = new Date(this.viewDate);
935 newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir);
937 if (this.dateWithinRange(newDate)){
939 this.viewDate = newViewDate;
948 if (!this.o.keyboardNavigation) break;
949 dir = e.keyCode == 38 ? -1 : 1;
951 newDate = this.moveYear(this.date, dir);
952 newViewDate = this.moveYear(this.viewDate, dir);
953 this._trigger('changeYear', this.viewDate);
954 } else if (e.shiftKey){
955 newDate = this.moveMonth(this.date, dir);
956 newViewDate = this.moveMonth(this.viewDate, dir);
957 this._trigger('changeMonth', this.viewDate);
959 newDate = new Date(this.date);
960 newDate.setUTCDate(this.date.getUTCDate() + dir * 7);
961 newViewDate = new Date(this.viewDate);
962 newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7);
964 if (this.dateWithinRange(newDate)){
966 this.viewDate = newViewDate;
982 this._trigger('changeDate');
985 element = this.element;
986 } else if (this.component){
987 element = this.element.find('input');
995 showMode: function(dir) {
997 this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir));
1000 vitalets: fixing bug of very special conditions:
1001 jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover.
1002 Method show() does not set display css correctly and datepicker is not shown.
1003 Changed to .css('display', 'block') solve the problem.
1004 See https://github.com/vitalets/x-editable/issues/37
1006 In jquery 1.7.2+ everything works fine.
1008 //this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
1009 this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block');
1010 this.updateNavArrows();
1014 var DateRangePicker = function(element, options){
1015 this.element = $(element);
1016 this.inputs = $.map(options.inputs, function(i){ return i.jquery ? i[0] : i; });
1017 delete options.inputs;
1020 .datepicker(options)
1021 .bind('changeDate', $.proxy(this.dateUpdated, this));
1023 this.pickers = $.map(this.inputs, function(i){ return $(i).data('datepicker'); });
1026 DateRangePicker.prototype = {
1027 updateDates: function(){
1028 this.dates = $.map(this.pickers, function(i){ return i.date; });
1029 this.updateRanges();
1031 updateRanges: function(){
1032 var range = $.map(this.dates, function(d){ return d.valueOf(); });
1033 $.each(this.pickers, function(i, p){
1037 dateUpdated: function(e){
1038 var dp = $(e.target).data('datepicker'),
1039 new_date = dp.getUTCDate(),
1040 i = $.inArray(e.target, this.inputs),
1041 l = this.inputs.length;
1042 if (i == -1) return;
1044 if (new_date < this.dates[i]){
1045 // Date being moved earlier/left
1046 while (i>=0 && new_date < this.dates[i]){
1047 this.pickers[i--].setUTCDate(new_date);
1050 else if (new_date > this.dates[i]){
1051 // Date being moved later/right
1052 while (i<l && new_date > this.dates[i]){
1053 this.pickers[i++].setUTCDate(new_date);
1059 $.map(this.pickers, function(p){ p.remove(); });
1060 delete this.element.data().datepicker;
1064 function opts_from_el(el, prefix){
1065 // Derive options from element data-attrs
1066 var data = $(el).data(),
1068 replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'),
1069 prefix = new RegExp('^' + prefix.toLowerCase());
1070 for (var key in data)
1071 if (prefix.test(key)){
1072 inkey = key.replace(replace, function(_,a){ return a.toLowerCase(); });
1073 out[inkey] = data[key];
1078 function opts_from_locale(lang){
1079 // Derive options from locale plugins
1081 // Check if "de-DE" style date is available, if not language should
1082 // fallback to 2 letter code eg "de"
1084 lang = lang.split('-')[0]
1088 var d = dates[lang];
1089 $.each(locale_opts, function(i,k){
1096 var old = $.fn.datepicker;
1097 $.fn.datepicker = function ( option ) {
1098 var args = Array.apply(null, arguments);
1100 var internal_return,
1102 this.each(function () {
1103 var $this = $(this),
1104 data = $this.data('datepicker'),
1105 options = typeof option == 'object' && option;
1107 var elopts = opts_from_el(this, 'date'),
1108 // Preliminary otions
1109 xopts = $.extend({}, defaults, elopts, options),
1110 locopts = opts_from_locale(xopts.language),
1111 // Options priority: js args, data-attrs, locales, defaults
1112 opts = $.extend({}, defaults, locopts, elopts, options);
1113 if ($this.is('.input-daterange') || opts.inputs){
1115 inputs: opts.inputs || $this.find('input').toArray()
1117 $this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts))));
1120 $this.data('datepicker', (data = new Datepicker(this, opts)));
1123 if (typeof option == 'string' && typeof data[option] == 'function') {
1124 internal_return = data[option].apply(data, args);
1125 if (internal_return !== undefined)
1129 if (internal_return !== undefined)
1130 return internal_return;
1135 var defaults = $.fn.datepicker.defaults = {
1137 beforeShowDay: $.noop,
1138 calendarWeeks: false,
1140 daysOfWeekDisabled: [],
1143 format: 'mm/dd/yyyy',
1144 keyboardNavigation: true,
1147 orientation: "auto",
1149 startDate: -Infinity,
1152 todayHighlight: false,
1155 var locale_opts = $.fn.datepicker.locale_opts = [
1160 $.fn.datepicker.Constructor = Datepicker;
1161 var dates = $.fn.datepicker.dates = {
1163 days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
1164 daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
1165 daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
1166 months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
1167 monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
1190 isLeapYear: function (year) {
1191 return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
1193 getDaysInMonth: function (year, month) {
1194 return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
1196 validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,
1197 nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,
1198 parseFormat: function(format){
1199 // IE treats \0 as a string end in inputs (truncating the value),
1200 // so it's a bad format delimiter, anyway
1201 var separators = format.replace(this.validParts, '\0').split('\0'),
1202 parts = format.match(this.validParts);
1203 if (!separators || !separators.length || !parts || parts.length === 0){
1204 throw new Error("Invalid date format.");
1206 return {separators: separators, parts: parts};
1208 parseDate: function(date, format, language) {
1209 if (date instanceof Date) return date;
1210 if (typeof format === 'string')
1211 format = DPGlobal.parseFormat(format);
1212 if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) {
1213 var part_re = /([\-+]\d+)([dmwy])/,
1214 parts = date.match(/([\-+]\d+)([dmwy])/g),
1217 for (var i=0; i<parts.length; i++) {
1218 part = part_re.exec(parts[i]);
1219 dir = parseInt(part[1]);
1222 date.setUTCDate(date.getUTCDate() + dir);
1225 date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);
1228 date.setUTCDate(date.getUTCDate() + dir * 7);
1231 date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);
1235 return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
1237 var parts = date && date.match(this.nonpunctuation) || [],
1240 setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
1242 yyyy: function(d,v){ return d.setUTCFullYear(v); },
1243 yy: function(d,v){ return d.setUTCFullYear(2000+v); },
1248 while (v<0) v += 12;
1251 while (d.getUTCMonth() != v)
1252 d.setUTCDate(d.getUTCDate()-1);
1255 d: function(d,v){ return d.setUTCDate(v); }
1257 val, filtered, part;
1258 setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
1259 setters_map['dd'] = setters_map['d'];
1260 date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
1261 var fparts = format.parts.slice();
1262 // Remove noop parts
1263 if (parts.length != fparts.length) {
1264 fparts = $(fparts).filter(function(i,p){
1265 return $.inArray(p, setters_order) !== -1;
1268 // Process remainder
1269 if (parts.length == fparts.length) {
1270 for (var i=0, cnt = fparts.length; i < cnt; i++) {
1271 val = parseInt(parts[i], 10);
1276 filtered = $(dates[language].months).filter(function(){
1277 var m = this.slice(0, parts[i].length),
1278 p = parts[i].slice(0, m.length);
1281 val = $.inArray(filtered[0], dates[language].months) + 1;
1284 filtered = $(dates[language].monthsShort).filter(function(){
1285 var m = this.slice(0, parts[i].length),
1286 p = parts[i].slice(0, m.length);
1289 val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
1295 for (var i=0, _date, s; i<setters_order.length; i++){
1296 s = setters_order[i];
1297 if (s in parsed && !isNaN(parsed[s])){
1298 _date = new Date(date);
1299 setters_map[s](_date, parsed[s]);
1307 formatDate: function(date, format, language){
1308 if (typeof format === 'string')
1309 format = DPGlobal.parseFormat(format);
1311 d: date.getUTCDate(),
1312 D: dates[language].daysShort[date.getUTCDay()],
1313 DD: dates[language].days[date.getUTCDay()],
1314 m: date.getUTCMonth() + 1,
1315 M: dates[language].monthsShort[date.getUTCMonth()],
1316 MM: dates[language].months[date.getUTCMonth()],
1317 yy: date.getUTCFullYear().toString().substring(2),
1318 yyyy: date.getUTCFullYear()
1320 val.dd = (val.d < 10 ? '0' : '') + val.d;
1321 val.mm = (val.m < 10 ? '0' : '') + val.m;
1323 seps = $.extend([], format.separators);
1324 for (var i=0, cnt = format.parts.length; i <= cnt; i++) {
1326 date.push(seps.shift());
1327 date.push(val[format.parts[i]]);
1329 return date.join('');
1331 headTemplate: '<thead>'+
1333 '<th class="prev">«</th>'+
1334 '<th colspan="5" class="datepicker-switch"></th>'+
1335 '<th class="next">»</th>'+
1338 contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
1339 footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr><tr><th colspan="7" class="clear"></th></tr></tfoot>'
1341 DPGlobal.template = '<div class="datepicker">'+
1342 '<div class="datepicker-days">'+
1343 '<table class=" table-condensed">'+
1344 DPGlobal.headTemplate+
1346 DPGlobal.footTemplate+
1349 '<div class="datepicker-months">'+
1350 '<table class="table-condensed">'+
1351 DPGlobal.headTemplate+
1352 DPGlobal.contTemplate+
1353 DPGlobal.footTemplate+
1356 '<div class="datepicker-years">'+
1357 '<table class="table-condensed">'+
1358 DPGlobal.headTemplate+
1359 DPGlobal.contTemplate+
1360 DPGlobal.footTemplate+
1365 $.fn.datepicker.DPGlobal = DPGlobal;
1368 /* DATEPICKER NO CONFLICT
1369 * =================== */
1371 $.fn.datepicker.noConflict = function(){
1372 $.fn.datepicker = old;
1377 /* DATEPICKER DATA-API
1378 * ================== */
1381 'focus.datepicker.data-api click.datepicker.data-api',
1382 '[data-provide="datepicker"]',
1384 var $this = $(this);
1385 if ($this.data('datepicker')) return;
1387 // component click requires us to explicitly show it
1388 $this.datepicker('show');
1392 $('[data-provide="datepicker-inline"]').datepicker();
1395 }( window.jQuery ));