/* ========================================================= * bootstrap-datepicker.js * repo: https://github.com/eternicode/bootstrap-datepicker/ * demo: http://eternicode.github.io/bootstrap-datepicker/ * docs: http://bootstrap-datepicker.readthedocs.org/ * forked from http://www.eyecon.ro/bootstrap-datepicker * ========================================================= * started by stefan petre; improvements by andrew rowls + contributors * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. * ========================================================= */ (function($, undefined){ var $date_prev_icon = '«';//ace var $date_next_icon = '»';//ace var $window = $(window); function utcdate(){ return new date(date.utc.apply(date, arguments)); } function utctoday(){ var today = new date(); return utcdate(today.getfullyear(), today.getmonth(), today.getdate()); } function alias(method){ return function(){ return this[method].apply(this, arguments); }; } var datearray = (function(){ var extras = { get: function(i){ return this.slice(i)[0]; }, contains: function(d){ // array.indexof is not cross-browser; // $.inarray doesn't work with dates var val = d && d.valueof(); for (var i=0, l=this.length; i < l; i++) if (this[i].valueof() === val) return i; return -1; }, remove: function(i){ this.splice(i,1); }, replace: function(new_array){ if (!new_array) return; if (!$.isarray(new_array)) new_array = [new_array]; this.clear(); this.push.apply(this, new_array); }, clear: function(){ this.length = 0; }, copy: function(){ var a = new datearray(); a.replace(this); return a; } }; return function(){ var a = []; a.push.apply(a, arguments); $.extend(a, extras); return a; }; })(); var datepicker = function(element, options){ this.dates = new datearray(); this.viewdate = utctoday(); this.focusdate = null; this._process_options(options); this.element = $(element); this.isinline = false; this.isinput = this.element.is('input'); this.component = this.element.is('.date') ? this.element.find('.add-on, .input-group-addon, .btn') : false; this.hasinput = this.component && this.element.find('input').length; if (this.component && this.component.length === 0) this.component = false; this.picker = $(dpglobal.template); this._buildevents(); this._attachevents(); if (this.isinline){ this.picker.addclass('datepicker-inline').appendto(this.element); } else { this.picker.addclass('datepicker-dropdown dropdown-menu'); } if (this.o.rtl){ this.picker.addclass('datepicker-rtl'); } this.viewmode = this.o.startview; if (this.o.calendarweeks) this.picker.find('tfoot th.today') .attr('colspan', function(i, val){ return parseint(val) + 1; }); this._allow_update = false; this.setstartdate(this._o.startdate); this.setenddate(this._o.enddate); this.setdaysofweekdisabled(this.o.daysofweekdisabled); this.filldow(); this.fillmonths(); this._allow_update = true; this.update(); this.showmode(); if (this.isinline){ this.show(); } }; datepicker.prototype = { constructor: datepicker, _process_options: function(opts){ // store raw options for reference this._o = $.extend({}, this._o, opts); // processed options var o = this.o = $.extend({}, this._o); // check if "de-de" style date is available, if not language should // fallback to 2 letter code eg "de" var lang = o.language; if (!dates[lang]){ lang = lang.split('-')[0]; if (!dates[lang]) lang = defaults.language; } o.language = lang; switch (o.startview){ case 2: case 'decade': o.startview = 2; break; case 1: case 'year': o.startview = 1; break; default: o.startview = 0; } switch (o.minviewmode){ case 1: case 'months': o.minviewmode = 1; break; case 2: case 'years': o.minviewmode = 2; break; default: o.minviewmode = 0; } o.startview = math.max(o.startview, o.minviewmode); // true, false, or number > 0 if (o.multidate !== true){ o.multidate = number(o.multidate) || false; if (o.multidate !== false) o.multidate = math.max(0, o.multidate); else o.multidate = 1; } o.multidateseparator = string(o.multidateseparator); o.weekstart %= 7; o.weekend = ((o.weekstart + 6) % 7); var format = dpglobal.parseformat(o.format); if (o.startdate !== -infinity){ if (!!o.startdate){ if (o.startdate instanceof date) o.startdate = this._local_to_utc(this._zero_time(o.startdate)); else o.startdate = dpglobal.parsedate(o.startdate, format, o.language); } else { o.startdate = -infinity; } } if (o.enddate !== infinity){ if (!!o.enddate){ if (o.enddate instanceof date) o.enddate = this._local_to_utc(this._zero_time(o.enddate)); else o.enddate = dpglobal.parsedate(o.enddate, format, o.language); } else { o.enddate = infinity; } } o.daysofweekdisabled = o.daysofweekdisabled||[]; if (!$.isarray(o.daysofweekdisabled)) o.daysofweekdisabled = o.daysofweekdisabled.split(/[,\s]*/); o.daysofweekdisabled = $.map(o.daysofweekdisabled, function(d){ return parseint(d, 10); }); var plc = string(o.orientation).tolowercase().split(/\s+/g), _plc = o.orientation.tolowercase(); plc = $.grep(plc, function(word){ return (/^auto|left|right|top|bottom$/).test(word); }); o.orientation = {x: 'auto', y: 'auto'}; if (!_plc || _plc === 'auto') ; // no action else if (plc.length === 1){ switch (plc[0]){ case 'top': case 'bottom': o.orientation.y = plc[0]; break; case 'left': case 'right': o.orientation.x = plc[0]; break; } } else { _plc = $.grep(plc, function(word){ return (/^left|right$/).test(word); }); o.orientation.x = _plc[0] || 'auto'; _plc = $.grep(plc, function(word){ return (/^top|bottom$/).test(word); }); o.orientation.y = _plc[0] || 'auto'; } }, _events: [], _secondaryevents: [], _applyevents: function(evs){ for (var i=0, el, ch, ev; i < evs.length; i++){ el = evs[i][0]; if (evs[i].length === 2){ ch = undefined; ev = evs[i][1]; } else if (evs[i].length === 3){ ch = evs[i][1]; ev = evs[i][2]; } el.on(ev, ch); } }, _unapplyevents: function(evs){ for (var i=0, el, ev, ch; i < evs.length; i++){ el = evs[i][0]; if (evs[i].length === 2){ ch = undefined; ev = evs[i][1]; } else if (evs[i].length === 3){ ch = evs[i][1]; ev = evs[i][2]; } el.off(ev, ch); } }, _buildevents: function(){ if (this.isinput){ // single input this._events = [ [this.element, { focus: $.proxy(this.show, this), keyup: $.proxy(function(e){ if ($.inarray(e.keycode, [27,37,39,38,40,32,13,9]) === -1) this.update(); }, this), keydown: $.proxy(this.keydown, this) }] ]; } else if (this.component && this.hasinput){ // component: input + button this._events = [ // for components that are not readonly, allow keyboard nav [this.element.find('input'), { focus: $.proxy(this.show, this), keyup: $.proxy(function(e){ if ($.inarray(e.keycode, [27,37,39,38,40,32,13,9]) === -1) this.update(); }, this), keydown: $.proxy(this.keydown, this) }], [this.component, { click: $.proxy(this.show, this) }] ]; } else if (this.element.is('div')){ // inline datepicker this.isinline = true; } else { this._events = [ [this.element, { click: $.proxy(this.show, this) }] ]; } this._events.push( // component: listen for blur on element descendants [this.element, '*', { blur: $.proxy(function(e){ this._focused_from = e.target; }, this) }], // input: listen for blur on element [this.element, { blur: $.proxy(function(e){ this._focused_from = e.target; }, this) }] ); this._secondaryevents = [ [this.picker, { click: $.proxy(this.click, this) }], [$(window), { resize: $.proxy(this.place, this) }], [$(document), { 'mousedown touchstart': $.proxy(function(e){ // clicked outside the datepicker, hide it if (!( this.element.is(e.target) || this.element.find(e.target).length || this.picker.is(e.target) || this.picker.find(e.target).length )){ this.hide(); } }, this) }] ]; }, _attachevents: function(){ this._detachevents(); this._applyevents(this._events); }, _detachevents: function(){ this._unapplyevents(this._events); }, _attachsecondaryevents: function(){ this._detachsecondaryevents(); this._applyevents(this._secondaryevents); }, _detachsecondaryevents: function(){ this._unapplyevents(this._secondaryevents); }, _trigger: function(event, altdate){ var date = altdate || this.dates.get(-1), local_date = this._utc_to_local(date); this.element.trigger({ type: event, date: local_date, dates: $.map(this.dates, this._utc_to_local), format: $.proxy(function(ix, format){ if (arguments.length === 0){ ix = this.dates.length - 1; format = this.o.format; } else if (typeof ix === 'string'){ format = ix; ix = this.dates.length - 1; } format = format || this.o.format; var date = this.dates.get(ix); return dpglobal.formatdate(date, format, this.o.language); }, this) }); }, show: function(){ if (!this.isinline) this.picker.appendto('body'); this.picker.show(); this.place(); this._attachsecondaryevents(); this._trigger('show'); }, hide: function(){ if (this.isinline) return; if (!this.picker.is(':visible')) return; this.focusdate = null; this.picker.hide().detach(); this._detachsecondaryevents(); this.viewmode = this.o.startview; this.showmode(); if ( this.o.forceparse && ( this.isinput && this.element.val() || this.hasinput && this.element.find('input').val() ) ) this.setvalue(); this._trigger('hide'); }, remove: function(){ this.hide(); this._detachevents(); this._detachsecondaryevents(); this.picker.remove(); delete this.element.data().datepicker; if (!this.isinput){ delete this.element.data().date; } }, _utc_to_local: function(utc){ return utc && new date(utc.gettime() + (utc.gettimezoneoffset()*60000)); }, _local_to_utc: function(local){ return local && new date(local.gettime() - (local.gettimezoneoffset()*60000)); }, _zero_time: function(local){ return local && new date(local.getfullyear(), local.getmonth(), local.getdate()); }, _zero_utc_time: function(utc){ return utc && new date(date.utc(utc.getutcfullyear(), utc.getutcmonth(), utc.getutcdate())); }, getdates: function(){ return $.map(this.dates, this._utc_to_local); }, getutcdates: function(){ return $.map(this.dates, function(d){ return new date(d); }); }, getdate: function(){ return this._utc_to_local(this.getutcdate()); }, getutcdate: function(){ return new date(this.dates.get(-1)); }, setdates: function(){ var args = $.isarray(arguments[0]) ? arguments[0] : arguments; this.update.apply(this, args); this._trigger('changedate'); this.setvalue(); }, setutcdates: function(){ var args = $.isarray(arguments[0]) ? arguments[0] : arguments; this.update.apply(this, $.map(args, this._utc_to_local)); this._trigger('changedate'); this.setvalue(); }, setdate: alias('setdates'), setutcdate: alias('setutcdates'), setvalue: function(){ var formatted = this.getformatteddate(); if (!this.isinput){ if (this.component){ this.element.find('input').val(formatted).change(); } } else { this.element.val(formatted).change(); } }, getformatteddate: function(format){ if (format === undefined) format = this.o.format; var lang = this.o.language; return $.map(this.dates, function(d){ return dpglobal.formatdate(d, format, lang); }).join(this.o.multidateseparator); }, setstartdate: function(startdate){ this._process_options({startdate: startdate}); this.update(); this.updatenavarrows(); }, setenddate: function(enddate){ this._process_options({enddate: enddate}); this.update(); this.updatenavarrows(); }, setdaysofweekdisabled: function(daysofweekdisabled){ this._process_options({daysofweekdisabled: daysofweekdisabled}); this.update(); this.updatenavarrows(); }, place: function(){ if (this.isinline) return; var calendarwidth = this.picker.outerwidth(), calendarheight = this.picker.outerheight(), visualpadding = 10, windowwidth = $window.width(), windowheight = $window.height(), scrolltop = $window.scrolltop(); var zindex = parseint(this.element.parents().filter(function(){ return $(this).css('z-index') !== 'auto'; }).first().css('z-index'))+9999; var offset = this.component ? this.component.parent().offset() : this.element.offset(); var height = this.component ? this.component.outerheight(true) : this.element.outerheight(false); var width = this.component ? this.component.outerwidth(true) : this.element.outerwidth(false); var left = offset.left, top = offset.top; this.picker.removeclass( 'datepicker-orient-top datepicker-orient-bottom '+ 'datepicker-orient-right datepicker-orient-left' ); if (this.o.orientation.x !== 'auto'){ this.picker.addclass('datepicker-orient-' + this.o.orientation.x); if (this.o.orientation.x === 'right') left -= calendarwidth - width; } // auto x orientation is best-placement: if it crosses a window // edge, fudge it sideways else { // default to left this.picker.addclass('datepicker-orient-left'); if (offset.left < 0) left -= offset.left - visualpadding; else if (offset.left + calendarwidth > windowwidth) left = windowwidth - calendarwidth - visualpadding; } // auto y orientation is best-situation: top or bottom, no fudging, // decision based on which shows more of the calendar var yorient = this.o.orientation.y, top_overflow, bottom_overflow; if (yorient === 'auto'){ top_overflow = -scrolltop + offset.top - calendarheight; bottom_overflow = scrolltop + windowheight - (offset.top + height + calendarheight); if (math.max(top_overflow, bottom_overflow) === bottom_overflow) yorient = 'top'; else yorient = 'bottom'; } this.picker.addclass('datepicker-orient-' + yorient); if (yorient === 'top') top += height; else top -= calendarheight + parseint(this.picker.css('padding-top')); this.picker.css({ top: top, left: left, zindex: zindex }); }, _allow_update: true, update: function(){ if (!this._allow_update) return; var olddates = this.dates.copy(), dates = [], fromargs = false; if (arguments.length){ $.each(arguments, $.proxy(function(i, date){ if (date instanceof date) date = this._local_to_utc(date); dates.push(date); }, this)); fromargs = true; } else { dates = this.isinput ? this.element.val() : this.element.data('date') || this.element.find('input').val(); if (dates && this.o.multidate) dates = dates.split(this.o.multidateseparator); else dates = [dates]; delete this.element.data().date; } dates = $.map(dates, $.proxy(function(date){ return dpglobal.parsedate(date, this.o.format, this.o.language); }, this)); dates = $.grep(dates, $.proxy(function(date){ return ( date < this.o.startdate || date > this.o.enddate || !date ); }, this), true); this.dates.replace(dates); if (this.dates.length) this.viewdate = new date(this.dates.get(-1)); else if (this.viewdate < this.o.startdate) this.viewdate = new date(this.o.startdate); else if (this.viewdate > this.o.enddate) this.viewdate = new date(this.o.enddate); if (fromargs){ // setting date by clicking this.setvalue(); } else if (dates.length){ // setting date by typing if (string(olddates) !== string(this.dates)) this._trigger('changedate'); } if (!this.dates.length && olddates.length) this._trigger('cleardate'); this.fill(); }, filldow: function(){ var dowcnt = this.o.weekstart, html = ''; if (this.o.calendarweeks){ var cell = ' '; html += cell; this.picker.find('.datepicker-days thead tr:first-child').prepend(cell); } while (dowcnt < this.o.weekstart + 7){ html += ''+dates[this.o.language].daysmin[(dowcnt++)%7]+''; } html += ''; this.picker.find('.datepicker-days thead').append(html); }, fillmonths: function(){ var html = '', i = 0; while (i < 12){ html += ''+dates[this.o.language].monthsshort[i++]+''; } this.picker.find('.datepicker-months td').html(html); }, setrange: function(range){ if (!range || !range.length) delete this.range; else this.range = $.map(range, function(d){ return d.valueof(); }); this.fill(); }, getclassnames: function(date){ var cls = [], year = this.viewdate.getutcfullyear(), month = this.viewdate.getutcmonth(), today = new date(); if (date.getutcfullyear() < year || (date.getutcfullyear() === year && date.getutcmonth() < month)){ cls.push('old'); } else if (date.getutcfullyear() > year || (date.getutcfullyear() === year && date.getutcmonth() > month)){ cls.push('new'); } if (this.focusdate && date.valueof() === this.focusdate.valueof()) cls.push('focused'); // compare internal utc date with local today, not utc today if (this.o.todayhighlight && date.getutcfullyear() === today.getfullyear() && date.getutcmonth() === today.getmonth() && date.getutcdate() === today.getdate()){ cls.push('today'); } if (this.dates.contains(date) !== -1) cls.push('active'); if (date.valueof() < this.o.startdate || date.valueof() > this.o.enddate || $.inarray(date.getutcday(), this.o.daysofweekdisabled) !== -1){ cls.push('disabled'); } if (this.range){ if (date > this.range[0] && date < this.range[this.range.length-1]){ cls.push('range'); } if ($.inarray(date.valueof(), this.range) !== -1){ cls.push('selected'); } } return cls; }, fill: function(){ var d = new date(this.viewdate), year = d.getutcfullyear(), month = d.getutcmonth(), startyear = this.o.startdate !== -infinity ? this.o.startdate.getutcfullyear() : -infinity, startmonth = this.o.startdate !== -infinity ? this.o.startdate.getutcmonth() : -infinity, endyear = this.o.enddate !== infinity ? this.o.enddate.getutcfullyear() : infinity, endmonth = this.o.enddate !== infinity ? this.o.enddate.getutcmonth() : infinity, todaytxt = dates[this.o.language].today || dates['en'].today || '', cleartxt = dates[this.o.language].clear || dates['en'].clear || '', tooltip; this.picker.find('.datepicker-days thead th.datepicker-switch') .text(dates[this.o.language].months[month]+' '+year); this.picker.find('tfoot th.today') .text(todaytxt) .toggle(this.o.todaybtn !== false); this.picker.find('tfoot th.clear') .text(cleartxt) .toggle(this.o.clearbtn !== false); this.updatenavarrows(); this.fillmonths(); var prevmonth = utcdate(year, month-1, 28), day = dpglobal.getdaysinmonth(prevmonth.getutcfullyear(), prevmonth.getutcmonth()); prevmonth.setutcdate(day); prevmonth.setutcdate(day - (prevmonth.getutcday() - this.o.weekstart + 7)%7); var nextmonth = new date(prevmonth); nextmonth.setutcdate(nextmonth.getutcdate() + 42); nextmonth = nextmonth.valueof(); var html = []; var clsname; while (prevmonth.valueof() < nextmonth){ if (prevmonth.getutcday() === this.o.weekstart){ html.push(''); if (this.o.calendarweeks){ // iso 8601: first week contains first thursday. // iso also states week starts on monday, but we can be more abstract here. var // start of current week: based on weekstart/current date ws = new date(+prevmonth + (this.o.weekstart - prevmonth.getutcday() - 7) % 7 * 864e5), // thursday of this week th = new date(number(ws) + (7 + 4 - ws.getutcday()) % 7 * 864e5), // first thursday of year, year from thursday yth = new date(number(yth = utcdate(th.getutcfullyear(), 0, 1)) + (7 + 4 - yth.getutcday())%7*864e5), // calendar week: ms between thursdays, div ms per day, div 7 days calweek = (th - yth) / 864e5 / 7 + 1; html.push(''+ calweek +''); } } clsname = this.getclassnames(prevmonth); clsname.push('day'); if (this.o.beforeshowday !== $.noop){ var before = this.o.beforeshowday(this._utc_to_local(prevmonth)); if (before === undefined) before = {}; else if (typeof(before) === 'boolean') before = {enabled: before}; else if (typeof(before) === 'string') before = {classes: before}; if (before.enabled === false) clsname.push('disabled'); if (before.classes) clsname = clsname.concat(before.classes.split(/\s+/)); if (before.tooltip) tooltip = before.tooltip; } clsname = $.unique(clsname); html.push(''+prevmonth.getutcdate() + ''); if (prevmonth.getutcday() === this.o.weekend){ html.push(''); } prevmonth.setutcdate(prevmonth.getutcdate()+1); } this.picker.find('.datepicker-days tbody').empty().append(html.join('')); var months = this.picker.find('.datepicker-months') .find('th:eq(1)') .text(year) .end() .find('span').removeclass('active'); $.each(this.dates, function(i, d){ if (d.getutcfullyear() === year) months.eq(d.getutcmonth()).addclass('active'); }); if (year < startyear || year > endyear){ months.addclass('disabled'); } if (year === startyear){ months.slice(0, startmonth).addclass('disabled'); } if (year === endyear){ months.slice(endmonth+1).addclass('disabled'); } html = ''; year = parseint(year/10, 10) * 10; var yearcont = this.picker.find('.datepicker-years') .find('th:eq(1)') .text(year + '-' + (year + 9)) .end() .find('td'); year -= 1; var years = $.map(this.dates, function(d){ return d.getutcfullyear(); }), classes; for (var i = -1; i < 11; i++){ classes = ['year']; if (i === -1) classes.push('old'); else if (i === 10) classes.push('new'); if ($.inarray(year, years) !== -1) classes.push('active'); if (year < startyear || year > endyear) classes.push('disabled'); html += ''+year+''; year += 1; } yearcont.html(html); }, updatenavarrows: function(){ if (!this._allow_update) return; var d = new date(this.viewdate), year = d.getutcfullyear(), month = d.getutcmonth(); switch (this.viewmode){ case 0: if (this.o.startdate !== -infinity && year <= this.o.startdate.getutcfullyear() && month <= this.o.startdate.getutcmonth()){ this.picker.find('.prev').css({visibility: 'hidden'}); } else { this.picker.find('.prev').css({visibility: 'visible'}); } if (this.o.enddate !== infinity && year >= this.o.enddate.getutcfullyear() && month >= this.o.enddate.getutcmonth()){ this.picker.find('.next').css({visibility: 'hidden'}); } else { this.picker.find('.next').css({visibility: 'visible'}); } break; case 1: case 2: if (this.o.startdate !== -infinity && year <= this.o.startdate.getutcfullyear()){ this.picker.find('.prev').css({visibility: 'hidden'}); } else { this.picker.find('.prev').css({visibility: 'visible'}); } if (this.o.enddate !== infinity && year >= this.o.enddate.getutcfullyear()){ this.picker.find('.next').css({visibility: 'hidden'}); } else { this.picker.find('.next').css({visibility: 'visible'}); } break; } }, click: function(e){ e.preventdefault(); var target = $(e.target).closest('span, td, th'), year, month, day; if (target.length === 1){ switch (target[0].nodename.tolowercase()){ case 'th': switch (target[0].classname){ case 'datepicker-switch': this.showmode(1); break; case 'prev': case 'next': var dir = dpglobal.modes[this.viewmode].navstep * (target[0].classname === 'prev' ? -1 : 1); switch (this.viewmode){ case 0: this.viewdate = this.movemonth(this.viewdate, dir); this._trigger('changemonth', this.viewdate); break; case 1: case 2: this.viewdate = this.moveyear(this.viewdate, dir); if (this.viewmode === 1) this._trigger('changeyear', this.viewdate); break; } this.fill(); break; case 'today': var date = new date(); date = utcdate(date.getfullyear(), date.getmonth(), date.getdate(), 0, 0, 0); this.showmode(-2); var which = this.o.todaybtn === 'linked' ? null : 'view'; this._setdate(date, which); break; case 'clear': var element; if (this.isinput) element = this.element; else if (this.component) element = this.element.find('input'); if (element) element.val("").change(); this.update(); this._trigger('changedate'); if (this.o.autoclose) this.hide(); break; } break; case 'span': if (!target.is('.disabled')){ this.viewdate.setutcdate(1); if (target.is('.month')){ day = 1; month = target.parent().find('span').index(target); year = this.viewdate.getutcfullyear(); this.viewdate.setutcmonth(month); this._trigger('changemonth', this.viewdate); if (this.o.minviewmode === 1){ this._setdate(utcdate(year, month, day)); } } else { day = 1; month = 0; year = parseint(target.text(), 10)||0; this.viewdate.setutcfullyear(year); this._trigger('changeyear', this.viewdate); if (this.o.minviewmode === 2){ this._setdate(utcdate(year, month, day)); } } this.showmode(-1); this.fill(); } break; case 'td': if (target.is('.day') && !target.is('.disabled')){ day = parseint(target.text(), 10)||1; year = this.viewdate.getutcfullyear(); month = this.viewdate.getutcmonth(); if (target.is('.old')){ if (month === 0){ month = 11; year -= 1; } else { month -= 1; } } else if (target.is('.new')){ if (month === 11){ month = 0; year += 1; } else { month += 1; } } this._setdate(utcdate(year, month, day)); } break; } } if (this.picker.is(':visible') && this._focused_from){ $(this._focused_from).focus(); } delete this._focused_from; }, _toggle_multidate: function(date){ var ix = this.dates.contains(date); if (!date){ this.dates.clear(); } else if (ix !== -1){ this.dates.remove(ix); } else { this.dates.push(date); } if (typeof this.o.multidate === 'number') while (this.dates.length > this.o.multidate) this.dates.remove(0); }, _setdate: function(date, which){ if (!which || which === 'date') this._toggle_multidate(date && new date(date)); if (!which || which === 'view') this.viewdate = date && new date(date); this.fill(); this.setvalue(); this._trigger('changedate'); var element; if (this.isinput){ element = this.element; } else if (this.component){ element = this.element.find('input'); } if (element){ element.change(); } if (this.o.autoclose && (!which || which === 'date')){ this.hide(); } }, movemonth: function(date, dir){ if (!date) return undefined; if (!dir) return date; var new_date = new date(date.valueof()), day = new_date.getutcdate(), month = new_date.getutcmonth(), mag = math.abs(dir), new_month, test; dir = dir > 0 ? 1 : -1; if (mag === 1){ test = dir === -1 // if going back one month, make sure month is not current month // (eg, mar 31 -> feb 31 == feb 28, not mar 02) ? function(){ return new_date.getutcmonth() === month; } // if going forward one month, make sure month is as expected // (eg, jan 31 -> feb 31 == feb 28, not mar 02) : function(){ return new_date.getutcmonth() !== new_month; }; new_month = month + dir; new_date.setutcmonth(new_month); // dec -> jan (12) or jan -> dec (-1) -- limit expected date to 0-11 if (new_month < 0 || new_month > 11) new_month = (new_month + 12) % 12; } else { // for magnitudes >1, move one month at a time... for (var i=0; i < mag; i++) // ...which might decrease the day (eg, jan 31 to feb 28, etc)... new_date = this.movemonth(new_date, dir); // ...then reset the day, keeping it in the new month new_month = new_date.getutcmonth(); new_date.setutcdate(day); test = function(){ return new_month !== new_date.getutcmonth(); }; } // common date-resetting loop -- if date is beyond end of month, make it // end of month while (test()){ new_date.setutcdate(--day); new_date.setutcmonth(new_month); } return new_date; }, moveyear: function(date, dir){ return this.movemonth(date, dir*12); }, datewithinrange: function(date){ return date >= this.o.startdate && date <= this.o.enddate; }, keydown: function(e){ if (this.picker.is(':not(:visible)')){ if (e.keycode === 27) // allow escape to hide and re-show picker this.show(); return; } var datechanged = false, dir, newdate, newviewdate, focusdate = this.focusdate || this.viewdate; switch (e.keycode){ case 27: // escape if (this.focusdate){ this.focusdate = null; this.viewdate = this.dates.get(-1) || this.viewdate; this.fill(); } else this.hide(); e.preventdefault(); break; case 37: // left case 39: // right if (!this.o.keyboardnavigation) break; dir = e.keycode === 37 ? -1 : 1; if (e.ctrlkey){ newdate = this.moveyear(this.dates.get(-1) || utctoday(), dir); newviewdate = this.moveyear(focusdate, dir); this._trigger('changeyear', this.viewdate); } else if (e.shiftkey){ newdate = this.movemonth(this.dates.get(-1) || utctoday(), dir); newviewdate = this.movemonth(focusdate, dir); this._trigger('changemonth', this.viewdate); } else { newdate = new date(this.dates.get(-1) || utctoday()); newdate.setutcdate(newdate.getutcdate() + dir); newviewdate = new date(focusdate); newviewdate.setutcdate(focusdate.getutcdate() + dir); } if (this.datewithinrange(newdate)){ this.focusdate = this.viewdate = newviewdate; this.setvalue(); this.fill(); e.preventdefault(); } break; case 38: // up case 40: // down if (!this.o.keyboardnavigation) break; dir = e.keycode === 38 ? -1 : 1; if (e.ctrlkey){ newdate = this.moveyear(this.dates.get(-1) || utctoday(), dir); newviewdate = this.moveyear(focusdate, dir); this._trigger('changeyear', this.viewdate); } else if (e.shiftkey){ newdate = this.movemonth(this.dates.get(-1) || utctoday(), dir); newviewdate = this.movemonth(focusdate, dir); this._trigger('changemonth', this.viewdate); } else { newdate = new date(this.dates.get(-1) || utctoday()); newdate.setutcdate(newdate.getutcdate() + dir * 7); newviewdate = new date(focusdate); newviewdate.setutcdate(focusdate.getutcdate() + dir * 7); } if (this.datewithinrange(newdate)){ this.focusdate = this.viewdate = newviewdate; this.setvalue(); this.fill(); e.preventdefault(); } break; case 32: // spacebar // spacebar is used in manually typing dates in some formats. // as such, its behavior should not be hijacked. break; case 13: // enter focusdate = this.focusdate || this.dates.get(-1) || this.viewdate; this._toggle_multidate(focusdate); datechanged = true; this.focusdate = null; this.viewdate = this.dates.get(-1) || this.viewdate; this.setvalue(); this.fill(); if (this.picker.is(':visible')){ e.preventdefault(); if (this.o.autoclose) this.hide(); } break; case 9: // tab this.focusdate = null; this.viewdate = this.dates.get(-1) || this.viewdate; this.fill(); this.hide(); break; } if (datechanged){ if (this.dates.length) this._trigger('changedate'); else this._trigger('cleardate'); var element; if (this.isinput){ element = this.element; } else if (this.component){ element = this.element.find('input'); } if (element){ element.change(); } } }, showmode: function(dir){ if (dir){ this.viewmode = math.max(this.o.minviewmode, math.min(2, this.viewmode + dir)); } this.picker .find('>div') .hide() .filter('.datepicker-'+dpglobal.modes[this.viewmode].clsname) .css('display', 'block'); this.updatenavarrows(); } }; var daterangepicker = function(element, options){ this.element = $(element); this.inputs = $.map(options.inputs, function(i){ return i.jquery ? i[0] : i; }); delete options.inputs; $(this.inputs) .datepicker(options) .bind('changedate', $.proxy(this.dateupdated, this)); this.pickers = $.map(this.inputs, function(i){ return $(i).data('datepicker'); }); this.updatedates(); }; daterangepicker.prototype = { updatedates: function(){ this.dates = $.map(this.pickers, function(i){ return i.getutcdate(); }); this.updateranges(); }, updateranges: function(){ var range = $.map(this.dates, function(d){ return d.valueof(); }); $.each(this.pickers, function(i, p){ p.setrange(range); }); }, dateupdated: function(e){ // `this.updating` is a workaround for preventing infinite recursion // between `changedate` triggering and `setutcdate` calling. until // there is a better mechanism. if (this.updating) return; this.updating = true; var dp = $(e.target).data('datepicker'), new_date = dp.getutcdate(), i = $.inarray(e.target, this.inputs), l = this.inputs.length; if (i === -1) return; $.each(this.pickers, function(i, p){ if (!p.getutcdate()) p.setutcdate(new_date); }); if (new_date < this.dates[i]){ // date being moved earlier/left while (i >= 0 && new_date < this.dates[i]){ this.pickers[i--].setutcdate(new_date); } } else if (new_date > this.dates[i]){ // date being moved later/right while (i < l && new_date > this.dates[i]){ this.pickers[i++].setutcdate(new_date); } } this.updatedates(); delete this.updating; }, remove: function(){ $.map(this.pickers, function(p){ p.remove(); }); delete this.element.data().datepicker; } }; function opts_from_el(el, prefix){ // derive options from element data-attrs var data = $(el).data(), out = {}, inkey, replace = new regexp('^' + prefix.tolowercase() + '([a-z])'); prefix = new regexp('^' + prefix.tolowercase()); function re_lower(_,a){ return a.tolowercase(); } for (var key in data) if (prefix.test(key)){ inkey = key.replace(replace, re_lower); out[inkey] = data[key]; } return out; } function opts_from_locale(lang){ // derive options from locale plugins var out = {}; // check if "de-de" style date is available, if not language should // fallback to 2 letter code eg "de" if (!dates[lang]){ lang = lang.split('-')[0]; if (!dates[lang]) return; } var d = dates[lang]; $.each(locale_opts, function(i,k){ if (k in d) out[k] = d[k]; }); return out; } var old = $.fn.datepicker; $.fn.datepicker = function(option){ var args = array.apply(null, arguments); args.shift(); var internal_return; this.each(function(){ var $this = $(this), data = $this.data('datepicker'), options = typeof option === 'object' && option; if (!data){ var elopts = opts_from_el(this, 'date'), // preliminary otions xopts = $.extend({}, defaults, elopts, options), locopts = opts_from_locale(xopts.language), // options priority: js args, data-attrs, locales, defaults opts = $.extend({}, defaults, locopts, elopts, options); if ($this.is('.input-daterange') || opts.inputs){ var ropts = { inputs: opts.inputs || $this.find('input').toarray() }; $this.data('datepicker', (data = new daterangepicker(this, $.extend(opts, ropts)))); } else { $this.data('datepicker', (data = new datepicker(this, opts))); } } if (typeof option === 'string' && typeof data[option] === 'function'){ internal_return = data[option].apply(data, args); if (internal_return !== undefined) return false; } }); if (internal_return !== undefined) return internal_return; else return this; }; var defaults = $.fn.datepicker.defaults = { autoclose: false, beforeshowday: $.noop, calendarweeks: false, clearbtn: false, daysofweekdisabled: [], enddate: infinity, forceparse: true, format: 'mm/dd/yyyy', keyboardnavigation: true, language: 'en', minviewmode: 0, multidate: false, multidateseparator: ',', orientation: "auto", rtl: false, startdate: -infinity, startview: 0, todaybtn: false, todayhighlight: false, weekstart: 0 }; var locale_opts = $.fn.datepicker.locale_opts = [ 'format', 'rtl', 'weekstart' ]; $.fn.datepicker.constructor = datepicker; var dates = $.fn.datepicker.dates = { en: { days: ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"], daysshort: ["sun", "mon", "tue", "wed", "thu", "fri", "sat", "sun"], daysmin: ["su", "mo", "tu", "we", "th", "fr", "sa", "su"], months: ["january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"], monthsshort: ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"], today: "today", clear: "clear" } }; var dpglobal = { modes: [ { clsname: 'days', navfnc: 'month', navstep: 1 }, { clsname: 'months', navfnc: 'fullyear', navstep: 1 }, { clsname: 'years', navfnc: 'fullyear', navstep: 10 }], isleapyear: function(year){ return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); }, getdaysinmonth: function(year, month){ return [31, (dpglobal.isleapyear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; }, validparts: /dd?|dd?|mm?|mm?|yy(?:yy)?/g, nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g, parseformat: function(format){ // ie treats \0 as a string end in inputs (truncating the value), // so it's a bad format delimiter, anyway var separators = format.replace(this.validparts, '\0').split('\0'), parts = format.match(this.validparts); if (!separators || !separators.length || !parts || parts.length === 0){ throw new error("invalid date format."); } return {separators: separators, parts: parts}; }, parsedate: function(date, format, language){ if (!date) return undefined; if (date instanceof date) return date; if (typeof format === 'string') format = dpglobal.parseformat(format); var part_re = /([\-+]\d+)([dmwy])/, parts = date.match(/([\-+]\d+)([dmwy])/g), part, dir, i; if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)){ date = new date(); for (i=0; i < parts.length; i++){ part = part_re.exec(parts[i]); dir = parseint(part[1]); switch (part[2]){ case 'd': date.setutcdate(date.getutcdate() + dir); break; case 'm': date = datepicker.prototype.movemonth.call(datepicker.prototype, date, dir); break; case 'w': date.setutcdate(date.getutcdate() + dir * 7); break; case 'y': date = datepicker.prototype.moveyear.call(datepicker.prototype, date, dir); break; } } return utcdate(date.getutcfullyear(), date.getutcmonth(), date.getutcdate(), 0, 0, 0); } parts = date && date.match(this.nonpunctuation) || []; date = new date(); var parsed = {}, setters_order = ['yyyy', 'yy', 'm', 'mm', 'm', 'mm', 'd', 'dd'], setters_map = { yyyy: function(d,v){ return d.setutcfullyear(v); }, yy: function(d,v){ return d.setutcfullyear(2000+v); }, m: function(d,v){ if (isnan(d)) return d; v -= 1; while (v < 0) v += 12; v %= 12; d.setutcmonth(v); while (d.getutcmonth() !== v) d.setutcdate(d.getutcdate()-1); return d; }, d: function(d,v){ return d.setutcdate(v); } }, val, filtered; setters_map['m'] = setters_map['mm'] = setters_map['mm'] = setters_map['m']; setters_map['dd'] = setters_map['d']; date = utcdate(date.getfullyear(), date.getmonth(), date.getdate(), 0, 0, 0); var fparts = format.parts.slice(); // remove noop parts if (parts.length !== fparts.length){ fparts = $(fparts).filter(function(i,p){ return $.inarray(p, setters_order) !== -1; }).toarray(); } // process remainder function match_part(){ var m = this.slice(0, parts[i].length), p = parts[i].slice(0, m.length); return m === p; } if (parts.length === fparts.length){ var cnt; for (i=0, cnt = fparts.length; i < cnt; i++){ val = parseint(parts[i], 10); part = fparts[i]; if (isnan(val)){ switch (part){ case 'mm': filtered = $(dates[language].months).filter(match_part); val = $.inarray(filtered[0], dates[language].months) + 1; break; case 'm': filtered = $(dates[language].monthsshort).filter(match_part); val = $.inarray(filtered[0], dates[language].monthsshort) + 1; break; } } parsed[part] = val; } var _date, s; for (i=0; i < setters_order.length; i++){ s = setters_order[i]; if (s in parsed && !isnan(parsed[s])){ _date = new date(date); setters_map[s](_date, parsed[s]); if (!isnan(_date)) date = _date; } } } return date; }, formatdate: function(date, format, language){ if (!date) return ''; if (typeof format === 'string') format = dpglobal.parseformat(format); var val = { d: date.getutcdate(), d: dates[language].daysshort[date.getutcday()], dd: dates[language].days[date.getutcday()], m: date.getutcmonth() + 1, m: dates[language].monthsshort[date.getutcmonth()], mm: dates[language].months[date.getutcmonth()], yy: date.getutcfullyear().tostring().substring(2), yyyy: date.getutcfullyear() }; val.dd = (val.d < 10 ? '0' : '') + val.d; val.mm = (val.m < 10 ? '0' : '') + val.m; date = []; var seps = $.extend([], format.separators); for (var i=0, cnt = format.parts.length; i <= cnt; i++){ if (seps.length) date.push(seps.shift()); date.push(val[format.parts[i]]); } return date.join(''); }, headtemplate: ''+ ''+ ''+$date_prev_icon+''+//ace ''+ ''+$date_next_icon+''+//ace ''+ '', conttemplate: '', foottemplate: ''+ ''+ ''+ ''+ ''+ ''+ ''+ '' }; dpglobal.template = '
'+ '
'+ ''+ dpglobal.headtemplate+ ''+ dpglobal.foottemplate+ '
'+ '
'+ '
'+ ''+ dpglobal.headtemplate+ dpglobal.conttemplate+ dpglobal.foottemplate+ '
'+ '
'+ '
'+ ''+ dpglobal.headtemplate+ dpglobal.conttemplate+ dpglobal.foottemplate+ '
'+ '
'+ '
'; $.fn.datepicker.dpglobal = dpglobal; /* datepicker no conflict * =================== */ $.fn.datepicker.noconflict = function(){ $.fn.datepicker = old; return this; }; /* datepicker data-api * ================== */ $(document).on( 'focus.datepicker.data-api click.datepicker.data-api', '[data-provide="datepicker"]', function(e){ var $this = $(this); if ($this.data('datepicker')) return; e.preventdefault(); // component click requires us to explicitly show it $this.datepicker('show'); } ); $(function(){ $('[data-provide="datepicker-inline"]').datepicker(); }); }(window.jquery));