]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/static/AdminLTE-2.3.7/plugins/datepicker/bootstrap-datepicker.js
1 /* =========================================================
2 * bootstrap-datepicker.js
3 * Repo: https://github.com/eternicode/bootstrap-datepicker/
4 * Demo: http://eternicode.github.io/bootstrap-datepicker/
5 * Docs: http://bootstrap-datepicker.readthedocs.org/
6 * Forked from http://www.eyecon.ro/bootstrap-datepicker
7 * =========================================================
8 * Started by Stefan Petre; improvements by Andrew Rowls + contributors
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * Unless required by applicable law or agreed to in writing, software
17 * distributed under the License is distributed on an "AS IS" BASIS,
18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 * See the License for the specific language governing permissions and
20 * limitations under the License.
21 * ========================================================= */
23 (function($, undefined){
25 var $window
= $(window
);
28 return new Date(Date
.UTC
.apply(Date
, arguments
));
31 var today
= new Date();
32 return UTCDate(today
.getFullYear(), today
.getMonth(), today
.getDate());
34 function alias(method
){
36 return this[method
].apply(this, arguments
);
40 var DateArray
= (function(){
43 return this.slice(i
)[0];
45 contains: function(d
){
46 // Array.indexOf is not cross-browser;
47 // $.inArray doesn't work with Dates
48 var val
= d
&& d
.valueOf();
49 for (var i
=0, l
=this.length
; i
< l
; i
++)
50 if (this[i
].valueOf() === val
)
57 replace: function(new_array
){
60 if (!$.isArray(new_array
))
61 new_array
= [new_array
];
63 this.push
.apply(this, new_array
);
69 var a
= new DateArray();
77 a
.push
.apply(a
, arguments
);
86 var Datepicker = function(element
, options
){
87 this.dates
= new DateArray();
88 this.viewDate
= UTCToday();
89 this.focusDate
= null;
91 this._process_options(options
);
93 this.element
= $(element
);
94 this.isInline
= false;
95 this.isInput
= this.element
.is('input');
96 this.component
= this.element
.is('.date') ? this.element
.find('.add-on, .input-group-addon, .btn') : false;
97 this.hasInput
= this.component
&& this.element
.find('input').length
;
98 if (this.component
&& this.component
.length
=== 0)
99 this.component
= false;
101 this.picker
= $(DPGlobal
.template
);
103 this._attachEvents();
106 this.picker
.addClass('datepicker-inline').appendTo(this.element
);
109 this.picker
.addClass('datepicker-dropdown dropdown-menu');
113 this.picker
.addClass('datepicker-rtl');
116 this.viewMode
= this.o
.startView
;
118 if (this.o
.calendarWeeks
)
119 this.picker
.find('tfoot th.today')
120 .attr('colspan', function(i
, val
){
121 return parseInt(val
) + 1;
124 this._allow_update
= false;
126 this.setStartDate(this._o
.startDate
);
127 this.setEndDate(this._o
.endDate
);
128 this.setDaysOfWeekDisabled(this.o
.daysOfWeekDisabled
);
133 this._allow_update
= true;
143 Datepicker
.prototype = {
144 constructor: Datepicker
,
146 _process_options: function(opts
){
147 // Store raw options for reference
148 this._o
= $.extend({}, this._o
, opts
);
150 var o
= this.o
= $.extend({}, this._o
);
152 // Check if "de-DE" style date is available, if not language should
153 // fallback to 2 letter code eg "de"
154 var lang
= o
.language
;
156 lang
= lang
.split('-')[0];
158 lang
= defaults
.language
;
162 switch (o
.startView
){
175 switch (o
.minViewMode
){
188 o
.startView
= Math
.max(o
.startView
, o
.minViewMode
);
190 // true, false, or Number > 0
191 if (o
.multidate
!== true){
192 o
.multidate
= Number(o
.multidate
) || false;
193 if (o
.multidate
!== false)
194 o
.multidate
= Math
.max(0, o
.multidate
);
198 o
.multidateSeparator
= String(o
.multidateSeparator
);
201 o
.weekEnd
= ((o
.weekStart
+ 6) % 7);
203 var format
= DPGlobal
.parseFormat(o
.format
);
204 if (o
.startDate
!== -Infinity
){
206 if (o
.startDate
instanceof Date
)
207 o
.startDate
= this._local_to_utc(this._zero_time(o
.startDate
));
209 o
.startDate
= DPGlobal
.parseDate(o
.startDate
, format
, o
.language
);
212 o
.startDate
= -Infinity
;
215 if (o
.endDate
!== Infinity
){
217 if (o
.endDate
instanceof Date
)
218 o
.endDate
= this._local_to_utc(this._zero_time(o
.endDate
));
220 o
.endDate
= DPGlobal
.parseDate(o
.endDate
, format
, o
.language
);
223 o
.endDate
= Infinity
;
227 o
.daysOfWeekDisabled
= o
.daysOfWeekDisabled
||[];
228 if (!$.isArray(o
.daysOfWeekDisabled
))
229 o
.daysOfWeekDisabled
= o
.daysOfWeekDisabled
.split(/[,\s]*/);
230 o
.daysOfWeekDisabled
= $.map(o
.daysOfWeekDisabled
, function(d
){
231 return parseInt(d
, 10);
234 var plc
= String(o
.orientation
).toLowerCase().split(/\s+/g),
235 _plc
= o
.orientation
.toLowerCase();
236 plc
= $.grep(plc
, function(word
){
237 return (/^auto|left|right|top|bottom$/).test(word
);
239 o
.orientation
= {x
: 'auto', y
: 'auto'};
240 if (!_plc
|| _plc
=== 'auto')
242 else if (plc
.length
=== 1){
246 o
.orientation
.y
= plc
[0];
250 o
.orientation
.x
= plc
[0];
255 _plc
= $.grep(plc
, function(word
){
256 return (/^left|right$/).test(word
);
258 o
.orientation
.x
= _plc
[0] || 'auto';
260 _plc
= $.grep(plc
, function(word
){
261 return (/^top|bottom$/).test(word
);
263 o
.orientation
.y
= _plc
[0] || 'auto';
267 _secondaryEvents
: [],
268 _applyEvents: function(evs
){
269 for (var i
=0, el
, ch
, ev
; i
< evs
.length
; i
++){
271 if (evs
[i
].length
=== 2){
275 else if (evs
[i
].length
=== 3){
282 _unapplyEvents: function(evs
){
283 for (var i
=0, el
, ev
, ch
; i
< evs
.length
; i
++){
285 if (evs
[i
].length
=== 2){
289 else if (evs
[i
].length
=== 3){
296 _buildEvents: function(){
297 if (this.isInput
){ // single input
300 focus
: $.proxy(this.show
, this),
301 keyup
: $.proxy(function(e
){
302 if ($.inArray(e
.keyCode
, [27,37,39,38,40,32,13,9]) === -1)
305 keydown
: $.proxy(this.keydown
, this)
309 else if (this.component
&& this.hasInput
){ // component: input + button
311 // For components that are not readonly, allow keyboard nav
312 [this.element
.find('input'), {
313 focus
: $.proxy(this.show
, this),
314 keyup
: $.proxy(function(e
){
315 if ($.inArray(e
.keyCode
, [27,37,39,38,40,32,13,9]) === -1)
318 keydown
: $.proxy(this.keydown
, this)
321 click
: $.proxy(this.show
, this)
325 else if (this.element
.is('div')){ // inline datepicker
326 this.isInline
= true;
331 click
: $.proxy(this.show
, this)
336 // Component: listen for blur on element descendants
337 [this.element
, '*', {
338 blur
: $.proxy(function(e
){
339 this._focused_from
= e
.target
;
342 // Input: listen for blur on element
344 blur
: $.proxy(function(e
){
345 this._focused_from
= e
.target
;
350 this._secondaryEvents
= [
352 click
: $.proxy(this.click
, this)
355 resize
: $.proxy(this.place
, this)
358 'mousedown touchstart': $.proxy(function(e
){
359 // Clicked outside the datepicker, hide it
361 this.element
.is(e
.target
) ||
362 this.element
.find(e
.target
).length
||
363 this.picker
.is(e
.target
) ||
364 this.picker
.find(e
.target
).length
372 _attachEvents: function(){
373 this._detachEvents();
374 this._applyEvents(this._events
);
376 _detachEvents: function(){
377 this._unapplyEvents(this._events
);
379 _attachSecondaryEvents: function(){
380 this._detachSecondaryEvents();
381 this._applyEvents(this._secondaryEvents
);
383 _detachSecondaryEvents: function(){
384 this._unapplyEvents(this._secondaryEvents
);
386 _trigger: function(event
, altdate
){
387 var date
= altdate
|| this.dates
.get(-1),
388 local_date
= this._utc_to_local(date
);
390 this.element
.trigger({
393 dates
: $.map(this.dates
, this._utc_to_local
),
394 format
: $.proxy(function(ix
, format
){
395 if (arguments
.length
=== 0){
396 ix
= this.dates
.length
- 1;
397 format
= this.o
.format
;
399 else if (typeof ix
=== 'string'){
401 ix
= this.dates
.length
- 1;
403 format
= format
|| this.o
.format
;
404 var date
= this.dates
.get(ix
);
405 return DPGlobal
.formatDate(date
, format
, this.o
.language
);
412 this.picker
.appendTo('body');
415 this._attachSecondaryEvents();
416 this._trigger('show');
422 if (!this.picker
.is(':visible'))
424 this.focusDate
= null;
425 this.picker
.hide().detach();
426 this._detachSecondaryEvents();
427 this.viewMode
= this.o
.startView
;
433 this.isInput
&& this.element
.val() ||
434 this.hasInput
&& this.element
.find('input').val()
438 this._trigger('hide');
443 this._detachEvents();
444 this._detachSecondaryEvents();
445 this.picker
.remove();
446 delete this.element
.data().datepicker
;
448 delete this.element
.data().date
;
452 _utc_to_local: function(utc
){
453 return utc
&& new Date(utc
.getTime() + (utc
.getTimezoneOffset()*60000));
455 _local_to_utc: function(local
){
456 return local
&& new Date(local
.getTime() - (local
.getTimezoneOffset()*60000));
458 _zero_time: function(local
){
459 return local
&& new Date(local
.getFullYear(), local
.getMonth(), local
.getDate());
461 _zero_utc_time: function(utc
){
462 return utc
&& new Date(Date
.UTC(utc
.getUTCFullYear(), utc
.getUTCMonth(), utc
.getUTCDate()));
465 getDates: function(){
466 return $.map(this.dates
, this._utc_to_local
);
469 getUTCDates: function(){
470 return $.map(this.dates
, function(d
){
476 return this._utc_to_local(this.getUTCDate());
479 getUTCDate: function(){
480 return new Date(this.dates
.get(-1));
483 setDates: function(){
484 var args
= $.isArray(arguments
[0]) ? arguments
[0] : arguments
;
485 this.update
.apply(this, args
);
486 this._trigger('changeDate');
490 setUTCDates: function(){
491 var args
= $.isArray(arguments
[0]) ? arguments
[0] : arguments
;
492 this.update
.apply(this, $.map(args
, this._utc_to_local
));
493 this._trigger('changeDate');
497 setDate
: alias('setDates'),
498 setUTCDate
: alias('setUTCDates'),
500 setValue: function(){
501 var formatted
= this.getFormattedDate();
504 this.element
.find('input').val(formatted
).change();
508 this.element
.val(formatted
).change();
512 getFormattedDate: function(format
){
513 if (format
=== undefined)
514 format
= this.o
.format
;
516 var lang
= this.o
.language
;
517 return $.map(this.dates
, function(d
){
518 return DPGlobal
.formatDate(d
, format
, lang
);
519 }).join(this.o
.multidateSeparator
);
522 setStartDate: function(startDate
){
523 this._process_options({startDate
: startDate
});
525 this.updateNavArrows();
528 setEndDate: function(endDate
){
529 this._process_options({endDate
: endDate
});
531 this.updateNavArrows();
534 setDaysOfWeekDisabled: function(daysOfWeekDisabled
){
535 this._process_options({daysOfWeekDisabled
: daysOfWeekDisabled
});
537 this.updateNavArrows();
543 var calendarWidth
= this.picker
.outerWidth(),
544 calendarHeight
= this.picker
.outerHeight(),
546 windowWidth
= $window
.width(),
547 windowHeight
= $window
.height(),
548 scrollTop
= $window
.scrollTop();
550 var zIndex
= parseInt(this.element
.parents().filter(function(){
551 return $(this).css('z-index') !== 'auto';
552 }).first().css('z-index'))+10;
553 var offset
= this.component
? this.component
.parent().offset() : this.element
.offset();
554 var height
= this.component
? this.component
.outerHeight(true) : this.element
.outerHeight(false);
555 var width
= this.component
? this.component
.outerWidth(true) : this.element
.outerWidth(false);
556 var left
= offset
.left
,
559 this.picker
.removeClass(
560 'datepicker-orient-top datepicker-orient-bottom '+
561 'datepicker-orient-right datepicker-orient-left'
564 if (this.o
.orientation
.x
!== 'auto'){
565 this.picker
.addClass('datepicker-orient-' + this.o
.orientation
.x
);
566 if (this.o
.orientation
.x
=== 'right')
567 left
-= calendarWidth
- width
;
569 // auto x orientation is best-placement: if it crosses a window
570 // edge, fudge it sideways
573 this.picker
.addClass('datepicker-orient-left');
575 left
-= offset
.left
- visualPadding
;
576 else if (offset
.left
+ calendarWidth
> windowWidth
)
577 left
= windowWidth
- calendarWidth
- visualPadding
;
580 // auto y orientation is best-situation: top or bottom, no fudging,
581 // decision based on which shows more of the calendar
582 var yorient
= this.o
.orientation
.y
,
583 top_overflow
, bottom_overflow
;
584 if (yorient
=== 'auto'){
585 top_overflow
= -scrollTop
+ offset
.top
- calendarHeight
;
586 bottom_overflow
= scrollTop
+ windowHeight
- (offset
.top
+ height
+ calendarHeight
);
587 if (Math
.max(top_overflow
, bottom_overflow
) === bottom_overflow
)
592 this.picker
.addClass('datepicker-orient-' + yorient
);
593 if (yorient
=== 'top')
596 top
-= calendarHeight
+ parseInt(this.picker
.css('padding-top'));
607 if (!this._allow_update
)
610 var oldDates
= this.dates
.copy(),
613 if (arguments
.length
){
614 $.each(arguments
, $.proxy(function(i
, date
){
615 if (date
instanceof Date
)
616 date
= this._local_to_utc(date
);
624 : this.element
.data('date') || this.element
.find('input').val();
625 if (dates
&& this.o
.multidate
)
626 dates
= dates
.split(this.o
.multidateSeparator
);
629 delete this.element
.data().date
;
632 dates
= $.map(dates
, $.proxy(function(date
){
633 return DPGlobal
.parseDate(date
, this.o
.format
, this.o
.language
);
635 dates
= $.grep(dates
, $.proxy(function(date
){
637 date
< this.o
.startDate
||
638 date
> this.o
.endDate
||
642 this.dates
.replace(dates
);
644 if (this.dates
.length
)
645 this.viewDate
= new Date(this.dates
.get(-1));
646 else if (this.viewDate
< this.o
.startDate
)
647 this.viewDate
= new Date(this.o
.startDate
);
648 else if (this.viewDate
> this.o
.endDate
)
649 this.viewDate
= new Date(this.o
.endDate
);
652 // setting date by clicking
655 else if (dates
.length
){
656 // setting date by typing
657 if (String(oldDates
) !== String(this.dates
))
658 this._trigger('changeDate');
660 if (!this.dates
.length
&& oldDates
.length
)
661 this._trigger('clearDate');
667 var dowCnt
= this.o
.weekStart
,
669 if (this.o
.calendarWeeks
){
670 var cell
= '<th class="cw"> </th>';
672 this.picker
.find('.datepicker-days thead tr:first-child').prepend(cell
);
674 while (dowCnt
< this.o
.weekStart
+ 7){
675 html
+= '<th class="dow">'+dates
[this.o
.language
].daysMin
[(dowCnt
++)%7]+'</th>';
678 this.picker
.find('.datepicker-days thead').append(html
);
681 fillMonths: function(){
685 html
+= '<span class="month">'+dates
[this.o
.language
].monthsShort
[i
++]+'</span>';
687 this.picker
.find('.datepicker-months td').html(html
);
690 setRange: function(range
){
691 if (!range
|| !range
.length
)
694 this.range
= $.map(range
, function(d
){
700 getClassNames: function(date
){
702 year
= this.viewDate
.getUTCFullYear(),
703 month
= this.viewDate
.getUTCMonth(),
705 if (date
.getUTCFullYear() < year
|| (date
.getUTCFullYear() === year
&& date
.getUTCMonth() < month
)){
708 else if (date
.getUTCFullYear() > year
|| (date
.getUTCFullYear() === year
&& date
.getUTCMonth() > month
)){
711 if (this.focusDate
&& date
.valueOf() === this.focusDate
.valueOf())
713 // Compare internal UTC date with local today, not UTC today
714 if (this.o
.todayHighlight
&&
715 date
.getUTCFullYear() === today
.getFullYear() &&
716 date
.getUTCMonth() === today
.getMonth() &&
717 date
.getUTCDate() === today
.getDate()){
720 if (this.dates
.contains(date
) !== -1)
722 if (date
.valueOf() < this.o
.startDate
|| date
.valueOf() > this.o
.endDate
||
723 $.inArray(date
.getUTCDay(), this.o
.daysOfWeekDisabled
) !== -1){
724 cls
.push('disabled');
727 if (date
> this.range
[0] && date
< this.range
[this.range
.length
-1]){
730 if ($.inArray(date
.valueOf(), this.range
) !== -1){
731 cls
.push('selected');
738 var d
= new Date(this.viewDate
),
739 year
= d
.getUTCFullYear(),
740 month
= d
.getUTCMonth(),
741 startYear
= this.o
.startDate
!== -Infinity
? this.o
.startDate
.getUTCFullYear() : -Infinity
,
742 startMonth
= this.o
.startDate
!== -Infinity
? this.o
.startDate
.getUTCMonth() : -Infinity
,
743 endYear
= this.o
.endDate
!== Infinity
? this.o
.endDate
.getUTCFullYear() : Infinity
,
744 endMonth
= this.o
.endDate
!== Infinity
? this.o
.endDate
.getUTCMonth() : Infinity
,
745 todaytxt
= dates
[this.o
.language
].today
|| dates
['en'].today
|| '',
746 cleartxt
= dates
[this.o
.language
].clear
|| dates
['en'].clear
|| '',
748 this.picker
.find('.datepicker-days thead th.datepicker-switch')
749 .text(dates
[this.o
.language
].months
[month
]+' '+year
);
750 this.picker
.find('tfoot th.today')
752 .toggle(this.o
.todayBtn
!== false);
753 this.picker
.find('tfoot th.clear')
755 .toggle(this.o
.clearBtn
!== false);
756 this.updateNavArrows();
758 var prevMonth
= UTCDate(year
, month
-1, 28),
759 day
= DPGlobal
.getDaysInMonth(prevMonth
.getUTCFullYear(), prevMonth
.getUTCMonth());
760 prevMonth
.setUTCDate(day
);
761 prevMonth
.setUTCDate(day
- (prevMonth
.getUTCDay() - this.o
.weekStart
+ 7)%7);
762 var nextMonth
= new Date(prevMonth
);
763 nextMonth
.setUTCDate(nextMonth
.getUTCDate() + 42);
764 nextMonth
= nextMonth
.valueOf();
767 while (prevMonth
.valueOf() < nextMonth
){
768 if (prevMonth
.getUTCDay() === this.o
.weekStart
){
770 if (this.o
.calendarWeeks
){
771 // ISO 8601: First week contains first thursday.
772 // ISO also states week starts on Monday, but we can be more abstract here.
774 // Start of current week: based on weekstart/current date
775 ws
= new Date(+prevMonth
+ (this.o
.weekStart
- prevMonth
.getUTCDay() - 7) % 7 * 864e5
),
776 // Thursday of this week
777 th
= new Date(Number(ws
) + (7 + 4 - ws
.getUTCDay()) % 7 * 864e5
),
778 // First Thursday of year, year from thursday
779 yth
= new Date(Number(yth
= UTCDate(th
.getUTCFullYear(), 0, 1)) + (7 + 4 - yth
.getUTCDay())%7*864e5
),
780 // Calendar week: ms between thursdays, div ms per day, div 7 days
781 calWeek
= (th
- yth
) / 864e5
/ 7 + 1;
782 html
.push('<td class="cw">'+ calWeek
+'</td>');
786 clsName
= this.getClassNames(prevMonth
);
789 if (this.o
.beforeShowDay
!== $.noop
){
790 var before
= this.o
.beforeShowDay(this._utc_to_local(prevMonth
));
791 if (before
=== undefined)
793 else if (typeof(before
) === 'boolean')
794 before
= {enabled
: before
};
795 else if (typeof(before
) === 'string')
796 before
= {classes
: before
};
797 if (before
.enabled
=== false)
798 clsName
.push('disabled');
800 clsName
= clsName
.concat(before
.classes
.split(/\s+/));
802 tooltip
= before
.tooltip
;
805 clsName
= $.unique(clsName
);
806 html
.push('<td class="'+clsName
.join(' ')+'"' + (tooltip
? ' title="'+tooltip
+'"' : '') + '>'+prevMonth
.getUTCDate() + '</td>');
807 if (prevMonth
.getUTCDay() === this.o
.weekEnd
){
810 prevMonth
.setUTCDate(prevMonth
.getUTCDate()+1);
812 this.picker
.find('.datepicker-days tbody').empty().append(html
.join(''));
814 var months
= this.picker
.find('.datepicker-months')
818 .find('span').removeClass('active');
820 $.each(this.dates
, function(i
, d
){
821 if (d
.getUTCFullYear() === year
)
822 months
.eq(d
.getUTCMonth()).addClass('active');
825 if (year
< startYear
|| year
> endYear
){
826 months
.addClass('disabled');
828 if (year
=== startYear
){
829 months
.slice(0, startMonth
).addClass('disabled');
831 if (year
=== endYear
){
832 months
.slice(endMonth
+1).addClass('disabled');
836 year
= parseInt(year
/10, 10) * 10;
837 var yearCont
= this.picker
.find('.datepicker-years')
839 .text(year
+ '-' + (year
+ 9))
843 var years
= $.map(this.dates
, function(d
){
844 return d
.getUTCFullYear();
847 for (var i
= -1; i
< 11; i
++){
853 if ($.inArray(year
, years
) !== -1)
854 classes
.push('active');
855 if (year
< startYear
|| year
> endYear
)
856 classes
.push('disabled');
857 html
+= '<span class="' + classes
.join(' ') + '">'+year
+'</span>';
863 updateNavArrows: function(){
864 if (!this._allow_update
)
867 var d
= new Date(this.viewDate
),
868 year
= d
.getUTCFullYear(),
869 month
= d
.getUTCMonth();
870 switch (this.viewMode
){
872 if (this.o
.startDate
!== -Infinity
&& year
<= this.o
.startDate
.getUTCFullYear() && month
<= this.o
.startDate
.getUTCMonth()){
873 this.picker
.find('.prev').css({visibility
: 'hidden'});
876 this.picker
.find('.prev').css({visibility
: 'visible'});
878 if (this.o
.endDate
!== Infinity
&& year
>= this.o
.endDate
.getUTCFullYear() && month
>= this.o
.endDate
.getUTCMonth()){
879 this.picker
.find('.next').css({visibility
: 'hidden'});
882 this.picker
.find('.next').css({visibility
: 'visible'});
887 if (this.o
.startDate
!== -Infinity
&& year
<= this.o
.startDate
.getUTCFullYear()){
888 this.picker
.find('.prev').css({visibility
: 'hidden'});
891 this.picker
.find('.prev').css({visibility
: 'visible'});
893 if (this.o
.endDate
!== Infinity
&& year
>= this.o
.endDate
.getUTCFullYear()){
894 this.picker
.find('.next').css({visibility
: 'hidden'});
897 this.picker
.find('.next').css({visibility
: 'visible'});
905 var target
= $(e
.target
).closest('span, td, th'),
907 if (target
.length
=== 1){
908 switch (target
[0].nodeName
.toLowerCase()){
910 switch (target
[0].className
){
911 case 'datepicker-switch':
916 var dir
= DPGlobal
.modes
[this.viewMode
].navStep
* (target
[0].className
=== 'prev' ? -1 : 1);
917 switch (this.viewMode
){
919 this.viewDate
= this.moveMonth(this.viewDate
, dir
);
920 this._trigger('changeMonth', this.viewDate
);
924 this.viewDate
= this.moveYear(this.viewDate
, dir
);
925 if (this.viewMode
=== 1)
926 this._trigger('changeYear', this.viewDate
);
932 var date
= new Date();
933 date
= UTCDate(date
.getFullYear(), date
.getMonth(), date
.getDate(), 0, 0, 0);
936 var which
= this.o
.todayBtn
=== 'linked' ? null : 'view';
937 this._setDate(date
, which
);
942 element
= this.element
;
943 else if (this.component
)
944 element
= this.element
.find('input');
946 element
.val("").change();
948 this._trigger('changeDate');
949 if (this.o
.autoclose
)
955 if (!target
.is('.disabled')){
956 this.viewDate
.setUTCDate(1);
957 if (target
.is('.month')){
959 month
= target
.parent().find('span').index(target
);
960 year
= this.viewDate
.getUTCFullYear();
961 this.viewDate
.setUTCMonth(month
);
962 this._trigger('changeMonth', this.viewDate
);
963 if (this.o
.minViewMode
=== 1){
964 this._setDate(UTCDate(year
, month
, day
));
970 year
= parseInt(target
.text(), 10)||0;
971 this.viewDate
.setUTCFullYear(year
);
972 this._trigger('changeYear', this.viewDate
);
973 if (this.o
.minViewMode
=== 2){
974 this._setDate(UTCDate(year
, month
, day
));
982 if (target
.is('.day') && !target
.is('.disabled')){
983 day
= parseInt(target
.text(), 10)||1;
984 year
= this.viewDate
.getUTCFullYear();
985 month
= this.viewDate
.getUTCMonth();
986 if (target
.is('.old')){
995 else if (target
.is('.new')){
1004 this._setDate(UTCDate(year
, month
, day
));
1009 if (this.picker
.is(':visible') && this._focused_from
){
1010 $(this._focused_from
).focus();
1012 delete this._focused_from
;
1015 _toggle_multidate: function(date
){
1016 var ix
= this.dates
.contains(date
);
1020 else if (ix
!== -1){
1021 this.dates
.remove(ix
);
1024 this.dates
.push(date
);
1026 if (typeof this.o
.multidate
=== 'number')
1027 while (this.dates
.length
> this.o
.multidate
)
1028 this.dates
.remove(0);
1031 _setDate: function(date
, which
){
1032 if (!which
|| which
=== 'date')
1033 this._toggle_multidate(date
&& new Date(date
));
1034 if (!which
|| which
=== 'view')
1035 this.viewDate
= date
&& new Date(date
);
1039 this._trigger('changeDate');
1042 element
= this.element
;
1044 else if (this.component
){
1045 element
= this.element
.find('input');
1050 if (this.o
.autoclose
&& (!which
|| which
=== 'date')){
1055 moveMonth: function(date
, dir
){
1060 var new_date
= new Date(date
.valueOf()),
1061 day
= new_date
.getUTCDate(),
1062 month
= new_date
.getUTCMonth(),
1063 mag
= Math
.abs(dir
),
1065 dir
= dir
> 0 ? 1 : -1;
1068 // If going back one month, make sure month is not current month
1069 // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
1071 return new_date
.getUTCMonth() === month
;
1073 // If going forward one month, make sure month is as expected
1074 // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
1076 return new_date
.getUTCMonth() !== new_month
;
1078 new_month
= month
+ dir
;
1079 new_date
.setUTCMonth(new_month
);
1080 // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
1081 if (new_month
< 0 || new_month
> 11)
1082 new_month
= (new_month
+ 12) % 12;
1085 // For magnitudes >1, move one month at a time...
1086 for (var i
=0; i
< mag
; i
++)
1087 // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
1088 new_date
= this.moveMonth(new_date
, dir
);
1089 // ...then reset the day, keeping it in the new month
1090 new_month
= new_date
.getUTCMonth();
1091 new_date
.setUTCDate(day
);
1093 return new_month
!== new_date
.getUTCMonth();
1096 // Common date-resetting loop -- if date is beyond end of month, make it
1099 new_date
.setUTCDate(--day
);
1100 new_date
.setUTCMonth(new_month
);
1105 moveYear: function(date
, dir
){
1106 return this.moveMonth(date
, dir
*12);
1109 dateWithinRange: function(date
){
1110 return date
>= this.o
.startDate
&& date
<= this.o
.endDate
;
1113 keydown: function(e
){
1114 if (this.picker
.is(':not(:visible)')){
1115 if (e
.keyCode
=== 27) // allow escape to hide and re-show picker
1119 var dateChanged
= false,
1120 dir
, newDate
, newViewDate
,
1121 focusDate
= this.focusDate
|| this.viewDate
;
1124 if (this.focusDate
){
1125 this.focusDate
= null;
1126 this.viewDate
= this.dates
.get(-1) || this.viewDate
;
1135 if (!this.o
.keyboardNavigation
)
1137 dir
= e
.keyCode
=== 37 ? -1 : 1;
1139 newDate
= this.moveYear(this.dates
.get(-1) || UTCToday(), dir
);
1140 newViewDate
= this.moveYear(focusDate
, dir
);
1141 this._trigger('changeYear', this.viewDate
);
1143 else if (e
.shiftKey
){
1144 newDate
= this.moveMonth(this.dates
.get(-1) || UTCToday(), dir
);
1145 newViewDate
= this.moveMonth(focusDate
, dir
);
1146 this._trigger('changeMonth', this.viewDate
);
1149 newDate
= new Date(this.dates
.get(-1) || UTCToday());
1150 newDate
.setUTCDate(newDate
.getUTCDate() + dir
);
1151 newViewDate
= new Date(focusDate
);
1152 newViewDate
.setUTCDate(focusDate
.getUTCDate() + dir
);
1154 if (this.dateWithinRange(newDate
)){
1155 this.focusDate
= this.viewDate
= newViewDate
;
1163 if (!this.o
.keyboardNavigation
)
1165 dir
= e
.keyCode
=== 38 ? -1 : 1;
1167 newDate
= this.moveYear(this.dates
.get(-1) || UTCToday(), dir
);
1168 newViewDate
= this.moveYear(focusDate
, dir
);
1169 this._trigger('changeYear', this.viewDate
);
1171 else if (e
.shiftKey
){
1172 newDate
= this.moveMonth(this.dates
.get(-1) || UTCToday(), dir
);
1173 newViewDate
= this.moveMonth(focusDate
, dir
);
1174 this._trigger('changeMonth', this.viewDate
);
1177 newDate
= new Date(this.dates
.get(-1) || UTCToday());
1178 newDate
.setUTCDate(newDate
.getUTCDate() + dir
* 7);
1179 newViewDate
= new Date(focusDate
);
1180 newViewDate
.setUTCDate(focusDate
.getUTCDate() + dir
* 7);
1182 if (this.dateWithinRange(newDate
)){
1183 this.focusDate
= this.viewDate
= newViewDate
;
1189 case 32: // spacebar
1190 // Spacebar is used in manually typing dates in some formats.
1191 // As such, its behavior should not be hijacked.
1194 focusDate
= this.focusDate
|| this.dates
.get(-1) || this.viewDate
;
1195 this._toggle_multidate(focusDate
);
1197 this.focusDate
= null;
1198 this.viewDate
= this.dates
.get(-1) || this.viewDate
;
1201 if (this.picker
.is(':visible')){
1203 if (this.o
.autoclose
)
1208 this.focusDate
= null;
1209 this.viewDate
= this.dates
.get(-1) || this.viewDate
;
1215 if (this.dates
.length
)
1216 this._trigger('changeDate');
1218 this._trigger('clearDate');
1221 element
= this.element
;
1223 else if (this.component
){
1224 element
= this.element
.find('input');
1232 showMode: function(dir
){
1234 this.viewMode
= Math
.max(this.o
.minViewMode
, Math
.min(2, this.viewMode
+ dir
));
1239 .filter('.datepicker-'+DPGlobal
.modes
[this.viewMode
].clsName
)
1240 .css('display', 'block');
1241 this.updateNavArrows();
1245 var DateRangePicker = function(element
, options
){
1246 this.element
= $(element
);
1247 this.inputs
= $.map(options
.inputs
, function(i
){
1248 return i
.jquery
? i
[0] : i
;
1250 delete options
.inputs
;
1253 .datepicker(options
)
1254 .bind('changeDate', $.proxy(this.dateUpdated
, this));
1256 this.pickers
= $.map(this.inputs
, function(i
){
1257 return $(i
).data('datepicker');
1261 DateRangePicker
.prototype = {
1262 updateDates: function(){
1263 this.dates
= $.map(this.pickers
, function(i
){
1264 return i
.getUTCDate();
1266 this.updateRanges();
1268 updateRanges: function(){
1269 var range
= $.map(this.dates
, function(d
){
1272 $.each(this.pickers
, function(i
, p
){
1276 dateUpdated: function(e
){
1277 // `this.updating` is a workaround for preventing infinite recursion
1278 // between `changeDate` triggering and `setUTCDate` calling. Until
1279 // there is a better mechanism.
1282 this.updating
= true;
1284 var dp
= $(e
.target
).data('datepicker'),
1285 new_date
= dp
.getUTCDate(),
1286 i
= $.inArray(e
.target
, this.inputs
),
1287 l
= this.inputs
.length
;
1291 $.each(this.pickers
, function(i
, p
){
1292 if (!p
.getUTCDate())
1293 p
.setUTCDate(new_date
);
1296 if (new_date
< this.dates
[i
]){
1297 // Date being moved earlier/left
1298 while (i
>= 0 && new_date
< this.dates
[i
]){
1299 this.pickers
[i
--].setUTCDate(new_date
);
1302 else if (new_date
> this.dates
[i
]){
1303 // Date being moved later/right
1304 while (i
< l
&& new_date
> this.dates
[i
]){
1305 this.pickers
[i
++].setUTCDate(new_date
);
1310 delete this.updating
;
1313 $.map(this.pickers
, function(p
){ p
.remove(); });
1314 delete this.element
.data().datepicker
;
1318 function opts_from_el(el
, prefix
){
1319 // Derive options from element data-attrs
1320 var data
= $(el
).data(),
1322 replace
= new RegExp('^' + prefix
.toLowerCase() + '([A-Z])');
1323 prefix
= new RegExp('^' + prefix
.toLowerCase());
1324 function re_lower(_
,a
){
1325 return a
.toLowerCase();
1327 for (var key
in data
)
1328 if (prefix
.test(key
)){
1329 inkey
= key
.replace(replace
, re_lower
);
1330 out
[inkey
] = data
[key
];
1335 function opts_from_locale(lang
){
1336 // Derive options from locale plugins
1338 // Check if "de-DE" style date is available, if not language should
1339 // fallback to 2 letter code eg "de"
1341 lang
= lang
.split('-')[0];
1345 var d
= dates
[lang
];
1346 $.each(locale_opts
, function(i
,k
){
1353 var old
= $.fn
.datepicker
;
1354 $.fn
.datepicker = function(option
){
1355 var args
= Array
.apply(null, arguments
);
1357 var internal_return
;
1358 this.each(function(){
1359 var $this = $(this),
1360 data
= $this.data('datepicker'),
1361 options
= typeof option
=== 'object' && option
;
1363 var elopts
= opts_from_el(this, 'date'),
1364 // Preliminary otions
1365 xopts
= $.extend({}, defaults
, elopts
, options
),
1366 locopts
= opts_from_locale(xopts
.language
),
1367 // Options priority: js args, data-attrs, locales, defaults
1368 opts
= $.extend({}, defaults
, locopts
, elopts
, options
);
1369 if ($this.is('.input-daterange') || opts
.inputs
){
1371 inputs
: opts
.inputs
|| $this.find('input').toArray()
1373 $this.data('datepicker', (data
= new DateRangePicker(this, $.extend(opts
, ropts
))));
1376 $this.data('datepicker', (data
= new Datepicker(this, opts
)));
1379 if (typeof option
=== 'string' && typeof data
[option
] === 'function'){
1380 internal_return
= data
[option
].apply(data
, args
);
1381 if (internal_return
!== undefined)
1385 if (internal_return
!== undefined)
1386 return internal_return
;
1391 var defaults
= $.fn
.datepicker
.defaults
= {
1393 beforeShowDay
: $.noop
,
1394 calendarWeeks
: false,
1396 daysOfWeekDisabled
: [],
1399 format
: 'mm/dd/yyyy',
1400 keyboardNavigation
: true,
1404 multidateSeparator
: ',',
1405 orientation
: "auto",
1407 startDate
: -Infinity
,
1410 todayHighlight
: false,
1413 var locale_opts
= $.fn
.datepicker
.locale_opts
= [
1418 $.fn
.datepicker
.Constructor
= Datepicker
;
1419 var dates
= $.fn
.datepicker
.dates
= {
1421 days
: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
1422 daysShort
: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
1423 daysMin
: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
1424 months
: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
1425 monthsShort
: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
1448 isLeapYear: function(year
){
1449 return (((year
% 4 === 0) && (year
% 100 !== 0)) || (year
% 400 === 0));
1451 getDaysInMonth: function(year
, month
){
1452 return [31, (DPGlobal
.isLeapYear(year
) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month
];
1454 validParts
: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,
1455 nonpunctuation
: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,
1456 parseFormat: function(format
){
1457 // IE treats \0 as a string end in inputs (truncating the value),
1458 // so it's a bad format delimiter, anyway
1459 var separators
= format
.replace(this.validParts
, '\0').split('\0'),
1460 parts
= format
.match(this.validParts
);
1461 if (!separators
|| !separators
.length
|| !parts
|| parts
.length
=== 0){
1462 throw new Error("Invalid date format.");
1464 return {separators
: separators
, parts
: parts
};
1466 parseDate: function(date
, format
, language
){
1469 if (date
instanceof Date
)
1471 if (typeof format
=== 'string')
1472 format
= DPGlobal
.parseFormat(format
);
1473 var part_re
= /([\-+]\d+)([dmwy])/,
1474 parts
= date
.match(/([\-+]\d+)([dmwy])/g),
1476 if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date
)){
1478 for (i
=0; i
< parts
.length
; i
++){
1479 part
= part_re
.exec(parts
[i
]);
1480 dir
= parseInt(part
[1]);
1483 date
.setUTCDate(date
.getUTCDate() + dir
);
1486 date
= Datepicker
.prototype.moveMonth
.call(Datepicker
.prototype, date
, dir
);
1489 date
.setUTCDate(date
.getUTCDate() + dir
* 7);
1492 date
= Datepicker
.prototype.moveYear
.call(Datepicker
.prototype, date
, dir
);
1496 return UTCDate(date
.getUTCFullYear(), date
.getUTCMonth(), date
.getUTCDate(), 0, 0, 0);
1498 parts
= date
&& date
.match(this.nonpunctuation
) || [];
1501 setters_order
= ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
1503 yyyy: function(d
,v
){
1504 return d
.setUTCFullYear(v
);
1507 return d
.setUTCFullYear(2000+v
);
1513 while (v
< 0) v
+= 12;
1516 while (d
.getUTCMonth() !== v
)
1517 d
.setUTCDate(d
.getUTCDate()-1);
1521 return d
.setUTCDate(v
);
1525 setters_map
['M'] = setters_map
['MM'] = setters_map
['mm'] = setters_map
['m'];
1526 setters_map
['dd'] = setters_map
['d'];
1527 date
= UTCDate(date
.getFullYear(), date
.getMonth(), date
.getDate(), 0, 0, 0);
1528 var fparts
= format
.parts
.slice();
1529 // Remove noop parts
1530 if (parts
.length
!== fparts
.length
){
1531 fparts
= $(fparts
).filter(function(i
,p
){
1532 return $.inArray(p
, setters_order
) !== -1;
1535 // Process remainder
1536 function match_part(){
1537 var m
= this.slice(0, parts
[i
].length
),
1538 p
= parts
[i
].slice(0, m
.length
);
1541 if (parts
.length
=== fparts
.length
){
1543 for (i
=0, cnt
= fparts
.length
; i
< cnt
; i
++){
1544 val
= parseInt(parts
[i
], 10);
1549 filtered
= $(dates
[language
].months
).filter(match_part
);
1550 val
= $.inArray(filtered
[0], dates
[language
].months
) + 1;
1553 filtered
= $(dates
[language
].monthsShort
).filter(match_part
);
1554 val
= $.inArray(filtered
[0], dates
[language
].monthsShort
) + 1;
1561 for (i
=0; i
< setters_order
.length
; i
++){
1562 s
= setters_order
[i
];
1563 if (s
in parsed
&& !isNaN(parsed
[s
])){
1564 _date
= new Date(date
);
1565 setters_map
[s
](_date
, parsed
[s
]);
1573 formatDate: function(date
, format
, language
){
1576 if (typeof format
=== 'string')
1577 format
= DPGlobal
.parseFormat(format
);
1579 d
: date
.getUTCDate(),
1580 D
: dates
[language
].daysShort
[date
.getUTCDay()],
1581 DD
: dates
[language
].days
[date
.getUTCDay()],
1582 m
: date
.getUTCMonth() + 1,
1583 M
: dates
[language
].monthsShort
[date
.getUTCMonth()],
1584 MM
: dates
[language
].months
[date
.getUTCMonth()],
1585 yy
: date
.getUTCFullYear().toString().substring(2),
1586 yyyy
: date
.getUTCFullYear()
1588 val
.dd
= (val
.d
< 10 ? '0' : '') + val
.d
;
1589 val
.mm
= (val
.m
< 10 ? '0' : '') + val
.m
;
1591 var seps
= $.extend([], format
.separators
);
1592 for (var i
=0, cnt
= format
.parts
.length
; i
<= cnt
; i
++){
1594 date
.push(seps
.shift());
1595 date
.push(val
[format
.parts
[i
]]);
1597 return date
.join('');
1599 headTemplate
: '<thead>'+
1601 '<th class="prev">«</th>'+
1602 '<th colspan="5" class="datepicker-switch"></th>'+
1603 '<th class="next">»</th>'+
1606 contTemplate
: '<tbody><tr><td colspan="7"></td></tr></tbody>',
1607 footTemplate
: '<tfoot>'+
1609 '<th colspan="7" class="today"></th>'+
1612 '<th colspan="7" class="clear"></th>'+
1616 DPGlobal
.template
= '<div class="datepicker">'+
1617 '<div class="datepicker-days">'+
1618 '<table class="table table-condensed">'+
1619 DPGlobal
.headTemplate
+
1621 DPGlobal
.footTemplate
+
1624 '<div class="datepicker-months">'+
1625 '<table class="table table-condensed">'+
1626 DPGlobal
.headTemplate
+
1627 DPGlobal
.contTemplate
+
1628 DPGlobal
.footTemplate
+
1631 '<div class="datepicker-years">'+
1632 '<table class="table table-condensed">'+
1633 DPGlobal
.headTemplate
+
1634 DPGlobal
.contTemplate
+
1635 DPGlobal
.footTemplate
+
1640 $.fn
.datepicker
.DPGlobal
= DPGlobal
;
1643 /* DATEPICKER NO CONFLICT
1644 * =================== */
1646 $.fn
.datepicker
.noConflict = function(){
1647 $.fn
.datepicker
= old
;
1652 /* DATEPICKER DATA-API
1653 * ================== */
1656 'focus.datepicker.data-api click.datepicker.data-api',
1657 '[data-provide="datepicker"]',
1659 var $this = $(this);
1660 if ($this.data('datepicker'))
1663 // component click requires us to explicitly show it
1664 $this.datepicker('show');
1668 $('[data-provide="datepicker-inline"]').datepicker();