/** * @version: 3.0.5 * @author: dan grossman http://www.dangrossman.info/ * @copyright: copyright (c) 2012-2019 dan grossman. all rights reserved. * @license: licensed under the mit license. see http://www.opensource.org/licenses/mit-license.php * @website: http://www.daterangepicker.com/ */ // following the umd template https://github.com/umdjs/umd/blob/master/templates/returnexportsglobal.js (function (root, factory) { if (typeof define === 'function' && define.amd) { // amd. make globaly available as well define(['moment', 'jquery'], function (moment, jquery) { if (!jquery.fn) jquery.fn = {}; // webpack server rendering if (typeof moment !== 'function' && moment.default) moment = moment.default return factory(moment, jquery); }); } else if (typeof module === 'object' && module.exports) { // node / browserify //isomorphic issue var jquery = (typeof window != 'undefined') ? window.jquery : undefined; if (!jquery) { jquery = require('jquery'); if (!jquery.fn) jquery.fn = {}; } var moment = (typeof window != 'undefined' && typeof window.moment != 'undefined') ? window.moment : require('moment'); module.exports = factory(moment, jquery); } else { // browser globals root.daterangepicker = factory(root.moment, root.jquery); } }(this, function(moment, $) { var daterangepicker = function(element, options, cb) { //default settings for options this.parentel = 'body'; this.element = $(element); this.startdate = moment().startof('day'); this.enddate = moment().endof('day'); this.mindate = false; this.maxdate = false; this.maxspan = false; this.autoapply = false; this.singledatepicker = false; this.showdropdowns = false; this.minyear = moment().subtract(100, 'year').format('yyyy'); this.maxyear = moment().add(100, 'year').format('yyyy'); this.showweeknumbers = false; this.showisoweeknumbers = false; this.showcustomrangelabel = true; this.timepicker = false; this.timepicker24hour = false; this.timepickerincrement = 1; this.timepickerseconds = false; this.linkedcalendars = true; this.autoupdateinput = true; this.alwaysshowcalendars = false; this.ranges = {}; this.opens = 'right'; if (this.element.hasclass('pull-right')) this.opens = 'left'; this.drops = 'down'; if (this.element.hasclass('dropup')) this.drops = 'up'; this.buttonclasses = 'btn btn-sm'; this.applybuttonclasses = 'btn-primary'; this.cancelbuttonclasses = 'btn-default'; this.locale = { direction: 'ltr', format: moment.localedata().longdateformat('l'), separator: ' - ', applylabel: 'apply', cancellabel: 'cancel', weeklabel: 'w', customrangelabel: 'custom range', daysofweek: moment.weekdaysmin(), monthnames: moment.monthsshort(), firstday: moment.localedata().firstdayofweek() }; this.callback = function() { }; //some state information this.isshowing = false; this.leftcalendar = {}; this.rightcalendar = {}; //custom options from user if (typeof options !== 'object' || options === null) options = {}; //allow setting options with data attributes //data-api options will be overwritten with custom javascript options options = $.extend(this.element.data(), options); //html template for the picker ui if (typeof options.template !== 'string' && !(options.template instanceof $)) options.template = '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '' + '' + ' ' + '
' + '
'; this.parentel = (options.parentel && $(options.parentel).length) ? $(options.parentel) : $(this.parentel); this.container = $(options.template).appendto(this.parentel); // // handle all the possible options overriding defaults // if (typeof options.locale === 'object') { if (typeof options.locale.direction === 'string') this.locale.direction = options.locale.direction; if (typeof options.locale.format === 'string') this.locale.format = options.locale.format; if (typeof options.locale.separator === 'string') this.locale.separator = options.locale.separator; if (typeof options.locale.daysofweek === 'object') this.locale.daysofweek = options.locale.daysofweek.slice(); if (typeof options.locale.monthnames === 'object') this.locale.monthnames = options.locale.monthnames.slice(); if (typeof options.locale.firstday === 'number') this.locale.firstday = options.locale.firstday; if (typeof options.locale.applylabel === 'string') this.locale.applylabel = options.locale.applylabel; if (typeof options.locale.cancellabel === 'string') this.locale.cancellabel = options.locale.cancellabel; if (typeof options.locale.weeklabel === 'string') this.locale.weeklabel = options.locale.weeklabel; if (typeof options.locale.customrangelabel === 'string'){ //support unicode chars in the custom range name. var elem = document.createelement('textarea'); elem.innerhtml = options.locale.customrangelabel; var rangehtml = elem.value; this.locale.customrangelabel = rangehtml; } } this.container.addclass(this.locale.direction); if (typeof options.startdate === 'string') this.startdate = moment(options.startdate, this.locale.format); if (typeof options.enddate === 'string') this.enddate = moment(options.enddate, this.locale.format); if (typeof options.mindate === 'string') this.mindate = moment(options.mindate, this.locale.format); if (typeof options.maxdate === 'string') this.maxdate = moment(options.maxdate, this.locale.format); if (typeof options.startdate === 'object') this.startdate = moment(options.startdate); if (typeof options.enddate === 'object') this.enddate = moment(options.enddate); if (typeof options.mindate === 'object') this.mindate = moment(options.mindate); if (typeof options.maxdate === 'object') this.maxdate = moment(options.maxdate); // sanity check for bad options if (this.mindate && this.startdate.isbefore(this.mindate)) this.startdate = this.mindate.clone(); // sanity check for bad options if (this.maxdate && this.enddate.isafter(this.maxdate)) this.enddate = this.maxdate.clone(); if (typeof options.applybuttonclasses === 'string') this.applybuttonclasses = options.applybuttonclasses; if (typeof options.applyclass === 'string') //backwards compat this.applybuttonclasses = options.applyclass; if (typeof options.cancelbuttonclasses === 'string') this.cancelbuttonclasses = options.cancelbuttonclasses; if (typeof options.cancelclass === 'string') //backwards compat this.cancelbuttonclasses = options.cancelclass; if (typeof options.maxspan === 'object') this.maxspan = options.maxspan; if (typeof options.datelimit === 'object') //backwards compat this.maxspan = options.datelimit; if (typeof options.opens === 'string') this.opens = options.opens; if (typeof options.drops === 'string') this.drops = options.drops; if (typeof options.showweeknumbers === 'boolean') this.showweeknumbers = options.showweeknumbers; if (typeof options.showisoweeknumbers === 'boolean') this.showisoweeknumbers = options.showisoweeknumbers; if (typeof options.buttonclasses === 'string') this.buttonclasses = options.buttonclasses; if (typeof options.buttonclasses === 'object') this.buttonclasses = options.buttonclasses.join(' '); if (typeof options.showdropdowns === 'boolean') this.showdropdowns = options.showdropdowns; if (typeof options.minyear === 'number') this.minyear = options.minyear; if (typeof options.maxyear === 'number') this.maxyear = options.maxyear; if (typeof options.showcustomrangelabel === 'boolean') this.showcustomrangelabel = options.showcustomrangelabel; if (typeof options.singledatepicker === 'boolean') { this.singledatepicker = options.singledatepicker; if (this.singledatepicker) this.enddate = this.startdate.clone(); } if (typeof options.timepicker === 'boolean') this.timepicker = options.timepicker; if (typeof options.timepickerseconds === 'boolean') this.timepickerseconds = options.timepickerseconds; if (typeof options.timepickerincrement === 'number') this.timepickerincrement = options.timepickerincrement; if (typeof options.timepicker24hour === 'boolean') this.timepicker24hour = options.timepicker24hour; if (typeof options.autoapply === 'boolean') this.autoapply = options.autoapply; if (typeof options.autoupdateinput === 'boolean') this.autoupdateinput = options.autoupdateinput; if (typeof options.linkedcalendars === 'boolean') this.linkedcalendars = options.linkedcalendars; if (typeof options.isinvaliddate === 'function') this.isinvaliddate = options.isinvaliddate; if (typeof options.iscustomdate === 'function') this.iscustomdate = options.iscustomdate; if (typeof options.alwaysshowcalendars === 'boolean') this.alwaysshowcalendars = options.alwaysshowcalendars; // update day names order to firstday if (this.locale.firstday != 0) { var iterator = this.locale.firstday; while (iterator > 0) { this.locale.daysofweek.push(this.locale.daysofweek.shift()); iterator--; } } var start, end, range; //if no start/end dates set, check if an input element contains initial values if (typeof options.startdate === 'undefined' && typeof options.enddate === 'undefined') { if ($(this.element).is(':text')) { var val = $(this.element).val(), split = val.split(this.locale.separator); start = end = null; if (split.length == 2) { start = moment(split[0], this.locale.format); end = moment(split[1], this.locale.format); } else if (this.singledatepicker && val !== "") { start = moment(val, this.locale.format); end = moment(val, this.locale.format); } if (start !== null && end !== null) { this.setstartdate(start); this.setenddate(end); } } } if (typeof options.ranges === 'object') { for (range in options.ranges) { if (typeof options.ranges[range][0] === 'string') start = moment(options.ranges[range][0], this.locale.format); else start = moment(options.ranges[range][0]); if (typeof options.ranges[range][1] === 'string') end = moment(options.ranges[range][1], this.locale.format); else end = moment(options.ranges[range][1]); // if the start or end date exceed those allowed by the mindate or maxspan // options, shorten the range to the allowable period. if (this.mindate && start.isbefore(this.mindate)) start = this.mindate.clone(); var maxdate = this.maxdate; if (this.maxspan && maxdate && start.clone().add(this.maxspan).isafter(maxdate)) maxdate = start.clone().add(this.maxspan); if (maxdate && end.isafter(maxdate)) end = maxdate.clone(); // if the end of the range is before the minimum or the start of the range is // after the maximum, don't display this range option at all. if ((this.mindate && end.isbefore(this.mindate, this.timepicker ? 'minute' : 'day')) || (maxdate && start.isafter(maxdate, this.timepicker ? 'minute' : 'day'))) continue; //support unicode chars in the range names. var elem = document.createelement('textarea'); elem.innerhtml = range; var rangehtml = elem.value; this.ranges[rangehtml] = [start, end]; } var list = ''; this.container.find('.ranges').prepend(list); } if (typeof cb === 'function') { this.callback = cb; } if (!this.timepicker) { this.startdate = this.startdate.startof('day'); this.enddate = this.enddate.endof('day'); this.container.find('.calendar-time').hide(); } //can't be used together for now if (this.timepicker && this.autoapply) this.autoapply = false; if (this.autoapply) { this.container.addclass('auto-apply'); } if (typeof options.ranges === 'object') this.container.addclass('show-ranges'); if (this.singledatepicker) { this.container.addclass('single'); this.container.find('.drp-calendar.left').addclass('single'); this.container.find('.drp-calendar.left').show(); this.container.find('.drp-calendar.right').hide(); if (!this.timepicker) { this.container.addclass('auto-apply'); } } if ((typeof options.ranges === 'undefined' && !this.singledatepicker) || this.alwaysshowcalendars) { this.container.addclass('show-calendar'); } this.container.addclass('opens' + this.opens); //apply css classes and labels to buttons this.container.find('.applybtn, .cancelbtn').addclass(this.buttonclasses); if (this.applybuttonclasses.length) this.container.find('.applybtn').addclass(this.applybuttonclasses); if (this.cancelbuttonclasses.length) this.container.find('.cancelbtn').addclass(this.cancelbuttonclasses); this.container.find('.applybtn').html(this.locale.applylabel); this.container.find('.cancelbtn').html(this.locale.cancellabel); // // event listeners // this.container.find('.drp-calendar') .on('click.daterangepicker', '.prev', $.proxy(this.clickprev, this)) .on('click.daterangepicker', '.next', $.proxy(this.clicknext, this)) .on('mousedown.daterangepicker', 'td.available', $.proxy(this.clickdate, this)) .on('mouseenter.daterangepicker', 'td.available', $.proxy(this.hoverdate, this)) .on('change.daterangepicker', 'select.yearselect', $.proxy(this.monthoryearchanged, this)) .on('change.daterangepicker', 'select.monthselect', $.proxy(this.monthoryearchanged, this)) .on('change.daterangepicker', 'select.hourselect,select.minuteselect,select.secondselect,select.ampmselect', $.proxy(this.timechanged, this)) this.container.find('.ranges') .on('click.daterangepicker', 'li', $.proxy(this.clickrange, this)) this.container.find('.drp-buttons') .on('click.daterangepicker', 'button.applybtn', $.proxy(this.clickapply, this)) .on('click.daterangepicker', 'button.cancelbtn', $.proxy(this.clickcancel, this)) if (this.element.is('input') || this.element.is('button')) { this.element.on({ 'click.daterangepicker': $.proxy(this.show, this), 'focus.daterangepicker': $.proxy(this.show, this), 'keyup.daterangepicker': $.proxy(this.elementchanged, this), 'keydown.daterangepicker': $.proxy(this.keydown, this) //ie 11 compatibility }); } else { this.element.on('click.daterangepicker', $.proxy(this.toggle, this)); this.element.on('keydown.daterangepicker', $.proxy(this.toggle, this)); } // // if attached to a text input, set the initial value // this.updateelement(); }; daterangepicker.prototype = { constructor: daterangepicker, setstartdate: function(startdate) { if (typeof startdate === 'string') this.startdate = moment(startdate, this.locale.format); if (typeof startdate === 'object') this.startdate = moment(startdate); if (!this.timepicker) this.startdate = this.startdate.startof('day'); if (this.timepicker && this.timepickerincrement) this.startdate.minute(math.round(this.startdate.minute() / this.timepickerincrement) * this.timepickerincrement); if (this.mindate && this.startdate.isbefore(this.mindate)) { this.startdate = this.mindate.clone(); if (this.timepicker && this.timepickerincrement) this.startdate.minute(math.round(this.startdate.minute() / this.timepickerincrement) * this.timepickerincrement); } if (this.maxdate && this.startdate.isafter(this.maxdate)) { this.startdate = this.maxdate.clone(); if (this.timepicker && this.timepickerincrement) this.startdate.minute(math.floor(this.startdate.minute() / this.timepickerincrement) * this.timepickerincrement); } if (!this.isshowing) this.updateelement(); this.updatemonthsinview(); }, setenddate: function(enddate) { if (typeof enddate === 'string') this.enddate = moment(enddate, this.locale.format); if (typeof enddate === 'object') this.enddate = moment(enddate); if (!this.timepicker) this.enddate = this.enddate.endof('day'); if (this.timepicker && this.timepickerincrement) this.enddate.minute(math.round(this.enddate.minute() / this.timepickerincrement) * this.timepickerincrement); if (this.enddate.isbefore(this.startdate)) this.enddate = this.startdate.clone(); if (this.maxdate && this.enddate.isafter(this.maxdate)) this.enddate = this.maxdate.clone(); if (this.maxspan && this.startdate.clone().add(this.maxspan).isbefore(this.enddate)) this.enddate = this.startdate.clone().add(this.maxspan); this.previousrighttime = this.enddate.clone(); this.container.find('.drp-selected').html(this.startdate.format(this.locale.format) + this.locale.separator + this.enddate.format(this.locale.format)); if (!this.isshowing) this.updateelement(); this.updatemonthsinview(); }, isinvaliddate: function() { return false; }, iscustomdate: function() { return false; }, updateview: function() { if (this.timepicker) { this.rendertimepicker('left'); this.rendertimepicker('right'); if (!this.enddate) { this.container.find('.right .calendar-time select').prop('disabled', true).addclass('disabled'); } else { this.container.find('.right .calendar-time select').prop('disabled', false).removeclass('disabled'); } } if (this.enddate) this.container.find('.drp-selected').html(this.startdate.format(this.locale.format) + this.locale.separator + this.enddate.format(this.locale.format)); this.updatemonthsinview(); this.updatecalendars(); this.updateforminputs(); }, updatemonthsinview: function() { if (this.enddate) { //if both dates are visible already, do nothing if (!this.singledatepicker && this.leftcalendar.month && this.rightcalendar.month && (this.startdate.format('yyyy-mm') == this.leftcalendar.month.format('yyyy-mm') || this.startdate.format('yyyy-mm') == this.rightcalendar.month.format('yyyy-mm')) && (this.enddate.format('yyyy-mm') == this.leftcalendar.month.format('yyyy-mm') || this.enddate.format('yyyy-mm') == this.rightcalendar.month.format('yyyy-mm')) ) { return; } this.leftcalendar.month = this.startdate.clone().date(2); if (!this.linkedcalendars && (this.enddate.month() != this.startdate.month() || this.enddate.year() != this.startdate.year())) { this.rightcalendar.month = this.enddate.clone().date(2); } else { this.rightcalendar.month = this.startdate.clone().date(2).add(1, 'month'); } } else { if (this.leftcalendar.month.format('yyyy-mm') != this.startdate.format('yyyy-mm') && this.rightcalendar.month.format('yyyy-mm') != this.startdate.format('yyyy-mm')) { this.leftcalendar.month = this.startdate.clone().date(2); this.rightcalendar.month = this.startdate.clone().date(2).add(1, 'month'); } } if (this.maxdate && this.linkedcalendars && !this.singledatepicker && this.rightcalendar.month > this.maxdate) { this.rightcalendar.month = this.maxdate.clone().date(2); this.leftcalendar.month = this.maxdate.clone().date(2).subtract(1, 'month'); } }, updatecalendars: function() { if (this.timepicker) { var hour, minute, second; if (this.enddate) { hour = parseint(this.container.find('.left .hourselect').val(), 10); minute = parseint(this.container.find('.left .minuteselect').val(), 10); if (isnan(minute)) { minute = parseint(this.container.find('.left .minuteselect option:last').val(), 10); } second = this.timepickerseconds ? parseint(this.container.find('.left .secondselect').val(), 10) : 0; if (!this.timepicker24hour) { var ampm = this.container.find('.left .ampmselect').val(); if (ampm === 'pm' && hour < 12) hour += 12; if (ampm === 'am' && hour === 12) hour = 0; } } else { hour = parseint(this.container.find('.right .hourselect').val(), 10); minute = parseint(this.container.find('.right .minuteselect').val(), 10); if (isnan(minute)) { minute = parseint(this.container.find('.right .minuteselect option:last').val(), 10); } second = this.timepickerseconds ? parseint(this.container.find('.right .secondselect').val(), 10) : 0; if (!this.timepicker24hour) { var ampm = this.container.find('.right .ampmselect').val(); if (ampm === 'pm' && hour < 12) hour += 12; if (ampm === 'am' && hour === 12) hour = 0; } } this.leftcalendar.month.hour(hour).minute(minute).second(second); this.rightcalendar.month.hour(hour).minute(minute).second(second); } this.rendercalendar('left'); this.rendercalendar('right'); //highlight any predefined range matching the current start and end dates this.container.find('.ranges li').removeclass('active'); if (this.enddate == null) return; this.calculatechosenlabel(); }, rendercalendar: function(side) { // // build the matrix of dates that will populate the calendar // var calendar = side == 'left' ? this.leftcalendar : this.rightcalendar; var month = calendar.month.month(); var year = calendar.month.year(); var hour = calendar.month.hour(); var minute = calendar.month.minute(); var second = calendar.month.second(); var daysinmonth = moment([year, month]).daysinmonth(); var firstday = moment([year, month, 1]); var lastday = moment([year, month, daysinmonth]); var lastmonth = moment(firstday).subtract(1, 'month').month(); var lastyear = moment(firstday).subtract(1, 'month').year(); var daysinlastmonth = moment([lastyear, lastmonth]).daysinmonth(); var dayofweek = firstday.day(); //initialize a 6 rows x 7 columns array for the calendar var calendar = []; calendar.firstday = firstday; calendar.lastday = lastday; for (var i = 0; i < 6; i++) { calendar[i] = []; } //populate the calendar with date objects var startday = daysinlastmonth - dayofweek + this.locale.firstday + 1; if (startday > daysinlastmonth) startday -= 7; if (dayofweek == this.locale.firstday) startday = daysinlastmonth - 6; var curdate = moment([lastyear, lastmonth, startday, 12, minute, second]); var col, row; for (var i = 0, col = 0, row = 0; i < 42; i++, col++, curdate = moment(curdate).add(24, 'hour')) { if (i > 0 && col % 7 === 0) { col = 0; row++; } calendar[row][col] = curdate.clone().hour(hour).minute(minute).second(second); curdate.hour(12); if (this.mindate && calendar[row][col].format('yyyy-mm-dd') == this.mindate.format('yyyy-mm-dd') && calendar[row][col].isbefore(this.mindate) && side == 'left') { calendar[row][col] = this.mindate.clone(); } if (this.maxdate && calendar[row][col].format('yyyy-mm-dd') == this.maxdate.format('yyyy-mm-dd') && calendar[row][col].isafter(this.maxdate) && side == 'right') { calendar[row][col] = this.maxdate.clone(); } } //make the calendar object available to hoverdate/clickdate if (side == 'left') { this.leftcalendar.calendar = calendar; } else { this.rightcalendar.calendar = calendar; } // // display the calendar // var mindate = side == 'left' ? this.mindate : this.startdate; var maxdate = this.maxdate; var selected = side == 'left' ? this.startdate : this.enddate; var arrow = this.locale.direction == 'ltr' ? {left: 'chevron-left', right: 'chevron-right'} : {left: 'chevron-right', right: 'chevron-left'}; var html = ''; html += ''; html += ''; // add empty cell for week number if (this.showweeknumbers || this.showisoweeknumbers) html += ''; if ((!mindate || mindate.isbefore(calendar.firstday)) && (!this.linkedcalendars || side == 'left')) { html += ''; } else { html += ''; } var datehtml = this.locale.monthnames[calendar[1][1].month()] + calendar[1][1].format(" yyyy"); if (this.showdropdowns) { var currentmonth = calendar[1][1].month(); var currentyear = calendar[1][1].year(); var maxyear = (maxdate && maxdate.year()) || (this.maxyear); var minyear = (mindate && mindate.year()) || (this.minyear); var inminyear = currentyear == minyear; var inmaxyear = currentyear == maxyear; var monthhtml = '"; var yearhtml = ''; datehtml = monthhtml + yearhtml; } html += ''; if ((!maxdate || maxdate.isafter(calendar.lastday)) && (!this.linkedcalendars || side == 'right' || this.singledatepicker)) { html += ''; } else { html += ''; } html += ''; html += ''; // add week number label if (this.showweeknumbers || this.showisoweeknumbers) html += ''; $.each(this.locale.daysofweek, function(index, dayofweek) { html += ''; }); html += ''; html += ''; html += ''; //adjust maxdate to reflect the maxspan setting in order to //grey out end dates beyond the maxspan if (this.enddate == null && this.maxspan) { var maxlimit = this.startdate.clone().add(this.maxspan).endof('day'); if (!maxdate || maxlimit.isbefore(maxdate)) { maxdate = maxlimit; } } for (var row = 0; row < 6; row++) { html += ''; // add week number if (this.showweeknumbers) html += ''; else if (this.showisoweeknumbers) html += ''; for (var col = 0; col < 7; col++) { var classes = []; //highlight today's date if (calendar[row][col].issame(new date(), "day")) classes.push('today'); //highlight weekends if (calendar[row][col].isoweekday() > 5) classes.push('weekend'); //grey out the dates in other months displayed at beginning and end of this calendar if (calendar[row][col].month() != calendar[1][1].month()) classes.push('off', 'ends'); //don't allow selection of dates before the minimum date if (this.mindate && calendar[row][col].isbefore(this.mindate, 'day')) classes.push('off', 'disabled'); //don't allow selection of dates after the maximum date if (maxdate && calendar[row][col].isafter(maxdate, 'day')) classes.push('off', 'disabled'); //don't allow selection of date if a custom function decides it's invalid if (this.isinvaliddate(calendar[row][col])) classes.push('off', 'disabled'); //highlight the currently selected start date if (calendar[row][col].format('yyyy-mm-dd') == this.startdate.format('yyyy-mm-dd')) classes.push('active', 'start-date'); //highlight the currently selected end date if (this.enddate != null && calendar[row][col].format('yyyy-mm-dd') == this.enddate.format('yyyy-mm-dd')) classes.push('active', 'end-date'); //highlight dates in-between the selected dates if (this.enddate != null && calendar[row][col] > this.startdate && calendar[row][col] < this.enddate) classes.push('in-range'); //apply custom classes for this date var iscustom = this.iscustomdate(calendar[row][col]); if (iscustom !== false) { if (typeof iscustom === 'string') classes.push(iscustom); else array.prototype.push.apply(classes, iscustom); } var cname = '', disabled = false; for (var i = 0; i < classes.length; i++) { cname += classes[i] + ' '; if (classes[i] == 'disabled') disabled = true; } if (!disabled) cname += 'available'; html += ''; } html += ''; } html += ''; html += '
' + datehtml + '
' + this.locale.weeklabel + '' + dayofweek + '
' + calendar[row][0].week() + '' + calendar[row][0].isoweek() + '' + calendar[row][col].date() + '
'; this.container.find('.drp-calendar.' + side + ' .calendar-table').html(html); }, rendertimepicker: function(side) { // don't bother updating the time picker if it's currently disabled // because an end date hasn't been clicked yet if (side == 'right' && !this.enddate) return; var html, selected, mindate, maxdate = this.maxdate; if (this.maxspan && (!this.maxdate || this.startdate.clone().add(this.maxspan).isbefore(this.maxdate))) maxdate = this.startdate.clone().add(this.maxspan); if (side == 'left') { selected = this.startdate.clone(); mindate = this.mindate; } else if (side == 'right') { selected = this.enddate.clone(); mindate = this.startdate; //preserve the time already selected var timeselector = this.container.find('.drp-calendar.right .calendar-time'); if (timeselector.html() != '') { selected.hour(!isnan(selected.hour()) ? selected.hour() : timeselector.find('.hourselect option:selected').val()); selected.minute(!isnan(selected.minute()) ? selected.minute() : timeselector.find('.minuteselect option:selected').val()); selected.second(!isnan(selected.second()) ? selected.second() : timeselector.find('.secondselect option:selected').val()); if (!this.timepicker24hour) { var ampm = timeselector.find('.ampmselect option:selected').val(); if (ampm === 'pm' && selected.hour() < 12) selected.hour(selected.hour() + 12); if (ampm === 'am' && selected.hour() === 12) selected.hour(0); } } if (selected.isbefore(this.startdate)) selected = this.startdate.clone(); if (maxdate && selected.isafter(maxdate)) selected = maxdate.clone(); } // // hours // html = ' '; // // minutes // html += ': '; // // seconds // if (this.timepickerseconds) { html += ': '; } // // am/pm // if (!this.timepicker24hour) { html += ''; } this.container.find('.drp-calendar.' + side + ' .calendar-time').html(html); }, updateforminputs: function() { if (this.singledatepicker || (this.enddate && (this.startdate.isbefore(this.enddate) || this.startdate.issame(this.enddate)))) { this.container.find('button.applybtn').prop('disabled', false); } else { this.container.find('button.applybtn').prop('disabled', true); } }, move: function() { var parentoffset = { top: 0, left: 0 }, containertop; var parentrightedge = $(window).width(); if (!this.parentel.is('body')) { parentoffset = { top: this.parentel.offset().top - this.parentel.scrolltop(), left: this.parentel.offset().left - this.parentel.scrollleft() }; parentrightedge = this.parentel[0].clientwidth + this.parentel.offset().left; } if (this.drops == 'up') containertop = this.element.offset().top - this.container.outerheight() - parentoffset.top; else containertop = this.element.offset().top + this.element.outerheight() - parentoffset.top; // force the container to it's actual width this.container.css({ top: 0, left: 0, right: 'auto' }); var containerwidth = this.container.outerwidth(); this.container[this.drops == 'up' ? 'addclass' : 'removeclass']('drop-up'); if (this.opens == 'left') { var containerright = parentrightedge - this.element.offset().left - this.element.outerwidth(); if (containerwidth + containerright > $(window).width()) { this.container.css({ top: containertop, right: 'auto', left: 9 }); } else { this.container.css({ top: containertop, right: containerright, left: 'auto' }); } } else if (this.opens == 'center') { var containerleft = this.element.offset().left - parentoffset.left + this.element.outerwidth() / 2 - containerwidth / 2; if (containerleft < 0) { this.container.css({ top: containertop, right: 'auto', left: 9 }); } else if (containerleft + containerwidth > $(window).width()) { this.container.css({ top: containertop, left: 'auto', right: 0 }); } else { this.container.css({ top: containertop, left: containerleft, right: 'auto' }); } } else { var containerleft = this.element.offset().left - parentoffset.left; if (containerleft + containerwidth > $(window).width()) { this.container.css({ top: containertop, left: 'auto', right: 0 }); } else { this.container.css({ top: containertop, left: containerleft, right: 'auto' }); } } }, show: function(e) { if (this.isshowing) return; // create a click proxy that is private to this instance of datepicker, for unbinding this._outsideclickproxy = $.proxy(function(e) { this.outsideclick(e); }, this); // bind global datepicker mousedown for hiding and $(document) .on('mousedown.daterangepicker', this._outsideclickproxy) // also support mobile devices .on('touchend.daterangepicker', this._outsideclickproxy) // also explicitly play nice with bootstrap dropdowns, which stoppropagation when clicking them .on('click.daterangepicker', '[data-toggle=dropdown]', this._outsideclickproxy) // and also close when focus changes to outside the picker (eg. tabbing between controls) .on('focusin.daterangepicker', this._outsideclickproxy); // reposition the picker if the window is resized while it's open $(window).on('resize.daterangepicker', $.proxy(function(e) { this.move(e); }, this)); this.oldstartdate = this.startdate.clone(); this.oldenddate = this.enddate.clone(); this.previousrighttime = this.enddate.clone(); this.updateview(); this.container.show(); this.move(); this.element.trigger('show.daterangepicker', this); this.isshowing = true; }, hide: function(e) { if (!this.isshowing) return; //incomplete date selection, revert to last values if (!this.enddate) { this.startdate = this.oldstartdate.clone(); this.enddate = this.oldenddate.clone(); } //if a new date range was selected, invoke the user callback function if (!this.startdate.issame(this.oldstartdate) || !this.enddate.issame(this.oldenddate)) this.callback(this.startdate.clone(), this.enddate.clone(), this.chosenlabel); //if picker is attached to a text input, update it this.updateelement(); $(document).off('.daterangepicker'); $(window).off('.daterangepicker'); this.container.hide(); this.element.trigger('hide.daterangepicker', this); this.isshowing = false; }, toggle: function(e) { if (this.isshowing) { this.hide(); } else { this.show(); } }, outsideclick: function(e) { var target = $(e.target); // if the page is clicked anywhere except within the daterangerpicker/button // itself then call this.hide() if ( // ie modal dialog fix e.type == "focusin" || target.closest(this.element).length || target.closest(this.container).length || target.closest('.calendar-table').length ) return; this.hide(); this.element.trigger('outsideclick.daterangepicker', this); }, showcalendars: function() { this.container.addclass('show-calendar'); this.move(); this.element.trigger('showcalendar.daterangepicker', this); }, hidecalendars: function() { this.container.removeclass('show-calendar'); this.element.trigger('hidecalendar.daterangepicker', this); }, clickrange: function(e) { var label = e.target.getattribute('data-range-key'); this.chosenlabel = label; if (label == this.locale.customrangelabel) { this.showcalendars(); } else { var dates = this.ranges[label]; this.startdate = dates[0]; this.enddate = dates[1]; if (!this.timepicker) { this.startdate.startof('day'); this.enddate.endof('day'); } if (!this.alwaysshowcalendars) this.hidecalendars(); this.clickapply(); } }, clickprev: function(e) { var cal = $(e.target).parents('.drp-calendar'); if (cal.hasclass('left')) { this.leftcalendar.month.subtract(1, 'month'); if (this.linkedcalendars) this.rightcalendar.month.subtract(1, 'month'); } else { this.rightcalendar.month.subtract(1, 'month'); } this.updatecalendars(); }, clicknext: function(e) { var cal = $(e.target).parents('.drp-calendar'); if (cal.hasclass('left')) { this.leftcalendar.month.add(1, 'month'); } else { this.rightcalendar.month.add(1, 'month'); if (this.linkedcalendars) this.leftcalendar.month.add(1, 'month'); } this.updatecalendars(); }, hoverdate: function(e) { //ignore dates that can't be selected if (!$(e.target).hasclass('available')) return; var title = $(e.target).attr('data-title'); var row = title.substr(1, 1); var col = title.substr(3, 1); var cal = $(e.target).parents('.drp-calendar'); var date = cal.hasclass('left') ? this.leftcalendar.calendar[row][col] : this.rightcalendar.calendar[row][col]; //highlight the dates between the start date and the date being hovered as a potential end date var leftcalendar = this.leftcalendar; var rightcalendar = this.rightcalendar; var startdate = this.startdate; if (!this.enddate) { this.container.find('.drp-calendar tbody td').each(function(index, el) { //skip week numbers, only look at dates if ($(el).hasclass('week')) return; var title = $(el).attr('data-title'); var row = title.substr(1, 1); var col = title.substr(3, 1); var cal = $(el).parents('.drp-calendar'); var dt = cal.hasclass('left') ? leftcalendar.calendar[row][col] : rightcalendar.calendar[row][col]; if ((dt.isafter(startdate) && dt.isbefore(date)) || dt.issame(date, 'day')) { $(el).addclass('in-range'); } else { $(el).removeclass('in-range'); } }); } }, clickdate: function(e) { if (!$(e.target).hasclass('available')) return; var title = $(e.target).attr('data-title'); var row = title.substr(1, 1); var col = title.substr(3, 1); var cal = $(e.target).parents('.drp-calendar'); var date = cal.hasclass('left') ? this.leftcalendar.calendar[row][col] : this.rightcalendar.calendar[row][col]; // // this function needs to do a few things: // * alternate between selecting a start and end date for the range, // * if the time picker is enabled, apply the hour/minute/second from the select boxes to the clicked date // * if autoapply is enabled, and an end date was chosen, apply the selection // * if single date picker mode, and time picker isn't enabled, apply the selection immediately // * if one of the inputs above the calendars was focused, cancel that manual input // if (this.enddate || date.isbefore(this.startdate, 'day')) { //picking start if (this.timepicker) { var hour = parseint(this.container.find('.left .hourselect').val(), 10); if (!this.timepicker24hour) { var ampm = this.container.find('.left .ampmselect').val(); if (ampm === 'pm' && hour < 12) hour += 12; if (ampm === 'am' && hour === 12) hour = 0; } var minute = parseint(this.container.find('.left .minuteselect').val(), 10); if (isnan(minute)) { minute = parseint(this.container.find('.left .minuteselect option:last').val(), 10); } var second = this.timepickerseconds ? parseint(this.container.find('.left .secondselect').val(), 10) : 0; date = date.clone().hour(hour).minute(minute).second(second); } this.enddate = null; this.setstartdate(date.clone()); } else if (!this.enddate && date.isbefore(this.startdate)) { //special case: clicking the same date for start/end, //but the time of the end date is before the start date this.setenddate(this.startdate.clone()); } else { // picking end if (this.timepicker) { var hour = parseint(this.container.find('.right .hourselect').val(), 10); if (!this.timepicker24hour) { var ampm = this.container.find('.right .ampmselect').val(); if (ampm === 'pm' && hour < 12) hour += 12; if (ampm === 'am' && hour === 12) hour = 0; } var minute = parseint(this.container.find('.right .minuteselect').val(), 10); if (isnan(minute)) { minute = parseint(this.container.find('.right .minuteselect option:last').val(), 10); } var second = this.timepickerseconds ? parseint(this.container.find('.right .secondselect').val(), 10) : 0; date = date.clone().hour(hour).minute(minute).second(second); } this.setenddate(date.clone()); if (this.autoapply) { this.calculatechosenlabel(); this.clickapply(); } } if (this.singledatepicker) { this.setenddate(this.startdate); if (!this.timepicker) this.clickapply(); } this.updateview(); //this is to cancel the blur event handler if the mouse was in one of the inputs e.stoppropagation(); }, calculatechosenlabel: function () { var customrange = true; var i = 0; for (var range in this.ranges) { if (this.timepicker) { var format = this.timepickerseconds ? "yyyy-mm-dd hh:mm:ss" : "yyyy-mm-dd hh:mm"; //ignore times when comparing dates if time picker seconds is not enabled if (this.startdate.format(format) == this.ranges[range][0].format(format) && this.enddate.format(format) == this.ranges[range][1].format(format)) { customrange = false; this.chosenlabel = this.container.find('.ranges li:eq(' + i + ')').addclass('active').attr('data-range-key'); break; } } else { //ignore times when comparing dates if time picker is not enabled if (this.startdate.format('yyyy-mm-dd') == this.ranges[range][0].format('yyyy-mm-dd') && this.enddate.format('yyyy-mm-dd') == this.ranges[range][1].format('yyyy-mm-dd')) { customrange = false; this.chosenlabel = this.container.find('.ranges li:eq(' + i + ')').addclass('active').attr('data-range-key'); break; } } i++; } if (customrange) { if (this.showcustomrangelabel) { this.chosenlabel = this.container.find('.ranges li:last').addclass('active').attr('data-range-key'); } else { this.chosenlabel = null; } this.showcalendars(); } }, clickapply: function(e) { this.hide(); this.element.trigger('apply.daterangepicker', this); }, clickcancel: function(e) { this.startdate = this.oldstartdate; this.enddate = this.oldenddate; this.hide(); this.element.trigger('cancel.daterangepicker', this); }, monthoryearchanged: function(e) { var isleft = $(e.target).closest('.drp-calendar').hasclass('left'), leftorright = isleft ? 'left' : 'right', cal = this.container.find('.drp-calendar.'+leftorright); // month must be number for new moment versions var month = parseint(cal.find('.monthselect').val(), 10); var year = cal.find('.yearselect').val(); if (!isleft) { if (year < this.startdate.year() || (year == this.startdate.year() && month < this.startdate.month())) { month = this.startdate.month(); year = this.startdate.year(); } } if (this.mindate) { if (year < this.mindate.year() || (year == this.mindate.year() && month < this.mindate.month())) { month = this.mindate.month(); year = this.mindate.year(); } } if (this.maxdate) { if (year > this.maxdate.year() || (year == this.maxdate.year() && month > this.maxdate.month())) { month = this.maxdate.month(); year = this.maxdate.year(); } } if (isleft) { this.leftcalendar.month.month(month).year(year); if (this.linkedcalendars) this.rightcalendar.month = this.leftcalendar.month.clone().add(1, 'month'); } else { this.rightcalendar.month.month(month).year(year); if (this.linkedcalendars) this.leftcalendar.month = this.rightcalendar.month.clone().subtract(1, 'month'); } this.updatecalendars(); }, timechanged: function(e) { var cal = $(e.target).closest('.drp-calendar'), isleft = cal.hasclass('left'); var hour = parseint(cal.find('.hourselect').val(), 10); var minute = parseint(cal.find('.minuteselect').val(), 10); if (isnan(minute)) { minute = parseint(cal.find('.minuteselect option:last').val(), 10); } var second = this.timepickerseconds ? parseint(cal.find('.secondselect').val(), 10) : 0; if (!this.timepicker24hour) { var ampm = cal.find('.ampmselect').val(); if (ampm === 'pm' && hour < 12) hour += 12; if (ampm === 'am' && hour === 12) hour = 0; } if (isleft) { var start = this.startdate.clone(); start.hour(hour); start.minute(minute); start.second(second); this.setstartdate(start); if (this.singledatepicker) { this.enddate = this.startdate.clone(); } else if (this.enddate && this.enddate.format('yyyy-mm-dd') == start.format('yyyy-mm-dd') && this.enddate.isbefore(start)) { this.setenddate(start.clone()); } } else if (this.enddate) { var end = this.enddate.clone(); end.hour(hour); end.minute(minute); end.second(second); this.setenddate(end); } //update the calendars so all clickable dates reflect the new time component this.updatecalendars(); //update the form inputs above the calendars with the new time this.updateforminputs(); //re-render the time pickers because changing one selection can affect what's enabled in another this.rendertimepicker('left'); this.rendertimepicker('right'); }, elementchanged: function() { if (!this.element.is('input')) return; if (!this.element.val().length) return; var datestring = this.element.val().split(this.locale.separator), start = null, end = null; if (datestring.length === 2) { start = moment(datestring[0], this.locale.format); end = moment(datestring[1], this.locale.format); } if (this.singledatepicker || start === null || end === null) { start = moment(this.element.val(), this.locale.format); end = start; } if (!start.isvalid() || !end.isvalid()) return; this.setstartdate(start); this.setenddate(end); this.updateview(); }, keydown: function(e) { //hide on tab or enter if ((e.keycode === 9) || (e.keycode === 13)) { this.hide(); } //hide on esc and prevent propagation if (e.keycode === 27) { e.preventdefault(); e.stoppropagation(); this.hide(); } }, updateelement: function() { if (this.element.is('input') && this.autoupdateinput) { var newvalue = this.startdate.format(this.locale.format); if (!this.singledatepicker) { newvalue += this.locale.separator + this.enddate.format(this.locale.format); } if (newvalue !== this.element.val()) { this.element.val(newvalue).trigger('change'); } } }, remove: function() { this.container.remove(); this.element.off('.daterangepicker'); this.element.removedata(); } }; $.fn.daterangepicker = function(options, callback) { var implementoptions = $.extend(true, {}, $.fn.daterangepicker.defaultoptions, options); this.each(function() { var el = $(this); if (el.data('daterangepicker')) el.data('daterangepicker').remove(); el.data('daterangepicker', new daterangepicker(el, implementoptions, callback)); }); return this; }; return daterangepicker; }));