3 * @author: Dan Grossman http://www.dangrossman.info/
\r
5 * @copyright: Copyright (c) 2012-2014 Dan Grossman. All rights reserved.
\r
6 * @license: Licensed under the MIT license. See http://www.opensource.org/licenses/mit-license.php
\r
7 * @website: http://www.improvely.com/
\r
10 (function(root, factory) {
\r
12 if (typeof define === 'function' && define.amd) {
\r
13 define(['moment', 'jquery', 'exports'], function(momentjs, $, exports) {
\r
14 root.daterangepicker = factory(root, exports, momentjs, $);
\r
17 } else if (typeof exports !== 'undefined') {
\r
18 var momentjs = require('moment');
\r
21 jQuery = require('jquery');
\r
23 jQuery = window.jQuery;
\r
24 if (!jQuery) throw new Error('jQuery dependency not found');
\r
27 factory(root, exports, momentjs, jQuery);
\r
29 // Finally, as a browser global.
\r
31 root.daterangepicker = factory(root, {}, root.moment, (root.jQuery || root.Zepto || root.ender || root.$));
\r
34 }(this, function(root, daterangepicker, moment, $) {
\r
36 var DateRangePicker = function (element, options, cb) {
\r
38 // by default, the daterangepicker element is placed at the bottom of HTML body
\r
39 this.parentEl = 'body';
\r
41 //element that triggered the date range picker
\r
42 this.element = $(element);
\r
44 //tracks visible state
\r
45 this.isShowing = false;
\r
47 //create the picker HTML object
\r
48 var DRPTemplate = '<div class="daterangepicker dropdown-menu">' +
\r
49 '<div class="calendar first left"></div>' +
\r
50 '<div class="calendar second right"></div>' +
\r
51 '<div class="ranges">' +
\r
52 '<div class="range_inputs">' +
\r
53 '<div class="daterangepicker_start_input">' +
\r
54 '<label for="daterangepicker_start"></label>' +
\r
55 '<input class="input-mini" type="text" name="daterangepicker_start" value="" />' +
\r
57 '<div class="daterangepicker_end_input">' +
\r
58 '<label for="daterangepicker_end"></label>' +
\r
59 '<input class="input-mini" type="text" name="daterangepicker_end" value="" />' +
\r
61 '<button class="applyBtn" disabled="disabled"></button> ' +
\r
62 '<button class="cancelBtn"></button>' +
\r
68 if (typeof options !== 'object' || options === null)
\r
71 this.parentEl = (typeof options === 'object' && options.parentEl && $(options.parentEl).length) ? $(options.parentEl) : $(this.parentEl);
\r
72 this.container = $(DRPTemplate).appendTo(this.parentEl);
\r
74 this.setOptions(options, cb);
\r
76 //apply CSS classes and labels to buttons
\r
77 var c = this.container;
\r
78 $.each(this.buttonClasses, function (idx, val) {
\r
79 c.find('button').addClass(val);
\r
81 this.container.find('.daterangepicker_start_input label').html(this.locale.fromLabel);
\r
82 this.container.find('.daterangepicker_end_input label').html(this.locale.toLabel);
\r
83 if (this.applyClass.length)
\r
84 this.container.find('.applyBtn').addClass(this.applyClass);
\r
85 if (this.cancelClass.length)
\r
86 this.container.find('.cancelBtn').addClass(this.cancelClass);
\r
87 this.container.find('.applyBtn').html(this.locale.applyLabel);
\r
88 this.container.find('.cancelBtn').html(this.locale.cancelLabel);
\r
92 this.container.find('.calendar')
\r
93 .on('click.daterangepicker', '.prev', $.proxy(this.clickPrev, this))
\r
94 .on('click.daterangepicker', '.next', $.proxy(this.clickNext, this))
\r
95 .on('click.daterangepicker', 'td.available', $.proxy(this.clickDate, this))
\r
96 .on('mouseenter.daterangepicker', 'td.available', $.proxy(this.hoverDate, this))
\r
97 .on('mouseleave.daterangepicker', 'td.available', $.proxy(this.updateFormInputs, this))
\r
98 .on('change.daterangepicker', 'select.yearselect', $.proxy(this.updateMonthYear, this))
\r
99 .on('change.daterangepicker', 'select.monthselect', $.proxy(this.updateMonthYear, this))
\r
100 .on('change.daterangepicker', 'select.hourselect,select.minuteselect,select.secondselect,select.ampmselect', $.proxy(this.updateTime, this));
\r
102 this.container.find('.ranges')
\r
103 .on('click.daterangepicker', 'button.applyBtn', $.proxy(this.clickApply, this))
\r
104 .on('click.daterangepicker', 'button.cancelBtn', $.proxy(this.clickCancel, this))
\r
105 .on('click.daterangepicker', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.showCalendars, this))
\r
106 .on('change.daterangepicker', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.inputsChanged, this))
\r
107 .on('keydown.daterangepicker', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.inputsKeydown, this))
\r
108 .on('click.daterangepicker', 'li', $.proxy(this.clickRange, this))
\r
109 .on('mouseenter.daterangepicker', 'li', $.proxy(this.enterRange, this))
\r
110 .on('mouseleave.daterangepicker', 'li', $.proxy(this.updateFormInputs, this));
\r
112 if (this.element.is('input')) {
\r
114 'click.daterangepicker': $.proxy(this.show, this),
\r
115 'focus.daterangepicker': $.proxy(this.show, this),
\r
116 'keyup.daterangepicker': $.proxy(this.updateFromControl, this)
\r
119 this.element.on('click.daterangepicker', $.proxy(this.toggle, this));
\r
124 DateRangePicker.prototype = {
\r
126 constructor: DateRangePicker,
\r
128 setOptions: function(options, callback) {
\r
130 this.startDate = moment().startOf('day');
\r
131 this.endDate = moment().endOf('day');
\r
132 this.timeZone = moment().zone();
\r
133 this.minDate = false;
\r
134 this.maxDate = false;
\r
135 this.dateLimit = false;
\r
137 this.showDropdowns = false;
\r
138 this.showWeekNumbers = false;
\r
139 this.timePicker = false;
\r
140 this.timePickerSeconds = false;
\r
141 this.timePickerIncrement = 30;
\r
142 this.timePicker12Hour = true;
\r
143 this.singleDatePicker = false;
\r
146 this.opens = 'right';
\r
147 if (this.element.hasClass('pull-right'))
\r
148 this.opens = 'left';
\r
150 this.buttonClasses = ['btn', 'btn-small btn-sm'];
\r
151 this.applyClass = 'btn-success';
\r
152 this.cancelClass = 'btn-default';
\r
154 this.format = 'MM/DD/YYYY';
\r
155 this.separator = ' - ';
\r
158 applyLabel: 'Apply',
\r
159 cancelLabel: 'Cancel',
\r
163 customRangeLabel: 'Custom Range',
\r
164 daysOfWeek: moment.weekdaysMin(),
\r
165 monthNames: moment.monthsShort(),
\r
166 firstDay: moment.localeData()._week.dow
\r
169 this.cb = function () { };
\r
171 if (typeof options.format === 'string')
\r
172 this.format = options.format;
\r
174 if (typeof options.separator === 'string')
\r
175 this.separator = options.separator;
\r
177 if (typeof options.startDate === 'string')
\r
178 this.startDate = moment(options.startDate, this.format);
\r
180 if (typeof options.endDate === 'string')
\r
181 this.endDate = moment(options.endDate, this.format);
\r
183 if (typeof options.minDate === 'string')
\r
184 this.minDate = moment(options.minDate, this.format);
\r
186 if (typeof options.maxDate === 'string')
\r
187 this.maxDate = moment(options.maxDate, this.format);
\r
189 if (typeof options.startDate === 'object')
\r
190 this.startDate = moment(options.startDate);
\r
192 if (typeof options.endDate === 'object')
\r
193 this.endDate = moment(options.endDate);
\r
195 if (typeof options.minDate === 'object')
\r
196 this.minDate = moment(options.minDate);
\r
198 if (typeof options.maxDate === 'object')
\r
199 this.maxDate = moment(options.maxDate);
\r
201 if (typeof options.applyClass === 'string')
\r
202 this.applyClass = options.applyClass;
\r
204 if (typeof options.cancelClass === 'string')
\r
205 this.cancelClass = options.cancelClass;
\r
207 if (typeof options.dateLimit === 'object')
\r
208 this.dateLimit = options.dateLimit;
\r
210 if (typeof options.locale === 'object') {
\r
212 if (typeof options.locale.daysOfWeek === 'object') {
\r
213 // Create a copy of daysOfWeek to avoid modification of original
\r
214 // options object for reusability in multiple daterangepicker instances
\r
215 this.locale.daysOfWeek = options.locale.daysOfWeek.slice();
\r
218 if (typeof options.locale.monthNames === 'object') {
\r
219 this.locale.monthNames = options.locale.monthNames.slice();
\r
222 if (typeof options.locale.firstDay === 'number') {
\r
223 this.locale.firstDay = options.locale.firstDay;
\r
226 if (typeof options.locale.applyLabel === 'string') {
\r
227 this.locale.applyLabel = options.locale.applyLabel;
\r
230 if (typeof options.locale.cancelLabel === 'string') {
\r
231 this.locale.cancelLabel = options.locale.cancelLabel;
\r
234 if (typeof options.locale.fromLabel === 'string') {
\r
235 this.locale.fromLabel = options.locale.fromLabel;
\r
238 if (typeof options.locale.toLabel === 'string') {
\r
239 this.locale.toLabel = options.locale.toLabel;
\r
242 if (typeof options.locale.weekLabel === 'string') {
\r
243 this.locale.weekLabel = options.locale.weekLabel;
\r
246 if (typeof options.locale.customRangeLabel === 'string') {
\r
247 this.locale.customRangeLabel = options.locale.customRangeLabel;
\r
251 if (typeof options.opens === 'string')
\r
252 this.opens = options.opens;
\r
254 if (typeof options.showWeekNumbers === 'boolean') {
\r
255 this.showWeekNumbers = options.showWeekNumbers;
\r
258 if (typeof options.buttonClasses === 'string') {
\r
259 this.buttonClasses = [options.buttonClasses];
\r
262 if (typeof options.buttonClasses === 'object') {
\r
263 this.buttonClasses = options.buttonClasses;
\r
266 if (typeof options.showDropdowns === 'boolean') {
\r
267 this.showDropdowns = options.showDropdowns;
\r
270 if (typeof options.singleDatePicker === 'boolean') {
\r
271 this.singleDatePicker = options.singleDatePicker;
\r
272 if (this.singleDatePicker) {
\r
273 this.endDate = this.startDate.clone();
\r
277 if (typeof options.timePicker === 'boolean') {
\r
278 this.timePicker = options.timePicker;
\r
281 if (typeof options.timePickerSeconds === 'boolean') {
\r
282 this.timePickerSeconds = options.timePickerSeconds;
\r
285 if (typeof options.timePickerIncrement === 'number') {
\r
286 this.timePickerIncrement = options.timePickerIncrement;
\r
289 if (typeof options.timePicker12Hour === 'boolean') {
\r
290 this.timePicker12Hour = options.timePicker12Hour;
\r
293 // update day names order to firstDay
\r
294 if (this.locale.firstDay != 0) {
\r
295 var iterator = this.locale.firstDay;
\r
296 while (iterator > 0) {
\r
297 this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift());
\r
302 var start, end, range;
\r
304 //if no start/end dates set, check if an input element contains initial values
\r
305 if (typeof options.startDate === 'undefined' && typeof options.endDate === 'undefined') {
\r
306 if ($(this.element).is('input[type=text]')) {
\r
307 var val = $(this.element).val(),
\r
308 split = val.split(this.separator);
\r
310 start = end = null;
\r
312 if (split.length == 2) {
\r
313 start = moment(split[0], this.format);
\r
314 end = moment(split[1], this.format);
\r
315 } else if (this.singleDatePicker && val !== "") {
\r
316 start = moment(val, this.format);
\r
317 end = moment(val, this.format);
\r
319 if (start !== null && end !== null) {
\r
320 this.startDate = start;
\r
321 this.endDate = end;
\r
326 // bind the time zone used to build the calendar to either the timeZone passed in through the options or the zone of the startDate (which will be the local time zone by default)
\r
327 if (typeof options.timeZone === 'string' || typeof options.timeZone === 'number') {
\r
328 this.timeZone = options.timeZone;
\r
329 this.startDate.zone(this.timeZone);
\r
330 this.endDate.zone(this.timeZone);
\r
332 this.timeZone = moment(this.startDate).zone();
\r
335 if (typeof options.ranges === 'object') {
\r
336 for (range in options.ranges) {
\r
338 if (typeof options.ranges[range][0] === 'string')
\r
339 start = moment(options.ranges[range][0], this.format);
\r
341 start = moment(options.ranges[range][0]);
\r
343 if (typeof options.ranges[range][1] === 'string')
\r
344 end = moment(options.ranges[range][1], this.format);
\r
346 end = moment(options.ranges[range][1]);
\r
348 // If we have a min/max date set, bound this range
\r
349 // to it, but only if it would otherwise fall
\r
350 // outside of the min/max.
\r
351 if (this.minDate && start.isBefore(this.minDate))
\r
352 start = moment(this.minDate);
\r
354 if (this.maxDate && end.isAfter(this.maxDate))
\r
355 end = moment(this.maxDate);
\r
357 // If the end of the range is before the minimum (if min is set) OR
\r
358 // the start of the range is after the max (also if set) don't display this
\r
360 if ((this.minDate && end.isBefore(this.minDate)) || (this.maxDate && start.isAfter(this.maxDate))) {
\r
364 this.ranges[range] = [start, end];
\r
368 for (range in this.ranges) {
\r
369 list += '<li>' + range + '</li>';
\r
371 list += '<li>' + this.locale.customRangeLabel + '</li>';
\r
373 this.container.find('.ranges ul').remove();
\r
374 this.container.find('.ranges').prepend(list);
\r
377 if (typeof callback === 'function') {
\r
378 this.cb = callback;
\r
381 if (!this.timePicker) {
\r
382 this.startDate = this.startDate.startOf('day');
\r
383 this.endDate = this.endDate.endOf('day');
\r
386 if (this.singleDatePicker) {
\r
387 this.opens = 'right';
\r
388 this.container.addClass('single');
\r
389 this.container.find('.calendar.right').show();
\r
390 this.container.find('.calendar.left').hide();
\r
391 if (!this.timePicker) {
\r
392 this.container.find('.ranges').hide();
\r
394 this.container.find('.ranges .daterangepicker_start_input, .ranges .daterangepicker_end_input').hide();
\r
396 if (!this.container.find('.calendar.right').hasClass('single'))
\r
397 this.container.find('.calendar.right').addClass('single');
\r
399 this.container.removeClass('single');
\r
400 this.container.find('.calendar.right').removeClass('single');
\r
401 this.container.find('.ranges').show();
\r
404 this.oldStartDate = this.startDate.clone();
\r
405 this.oldEndDate = this.endDate.clone();
\r
406 this.oldChosenLabel = this.chosenLabel;
\r
408 this.leftCalendar = {
\r
409 month: moment([this.startDate.year(), this.startDate.month(), 1, this.startDate.hour(), this.startDate.minute(), this.startDate.second()]),
\r
413 this.rightCalendar = {
\r
414 month: moment([this.endDate.year(), this.endDate.month(), 1, this.endDate.hour(), this.endDate.minute(), this.endDate.second()]),
\r
418 if (this.opens == 'right' || this.opens == 'center') {
\r
419 //swap calendar positions
\r
420 var first = this.container.find('.calendar.first');
\r
421 var second = this.container.find('.calendar.second');
\r
423 if (second.hasClass('single')) {
\r
424 second.removeClass('single');
\r
425 first.addClass('single');
\r
428 first.removeClass('left').addClass('right');
\r
429 second.removeClass('right').addClass('left');
\r
431 if (this.singleDatePicker) {
\r
437 if (typeof options.ranges === 'undefined' && !this.singleDatePicker) {
\r
438 this.container.addClass('show-calendar');
\r
441 this.container.addClass('opens' + this.opens);
\r
444 this.updateCalendars();
\r
448 setStartDate: function(startDate) {
\r
449 if (typeof startDate === 'string')
\r
450 this.startDate = moment(startDate, this.format).zone(this.timeZone);
\r
452 if (typeof startDate === 'object')
\r
453 this.startDate = moment(startDate);
\r
455 if (!this.timePicker)
\r
456 this.startDate = this.startDate.startOf('day');
\r
458 this.oldStartDate = this.startDate.clone();
\r
461 this.updateCalendars();
\r
462 this.updateInputText();
\r
465 setEndDate: function(endDate) {
\r
466 if (typeof endDate === 'string')
\r
467 this.endDate = moment(endDate, this.format).zone(this.timeZone);
\r
469 if (typeof endDate === 'object')
\r
470 this.endDate = moment(endDate);
\r
472 if (!this.timePicker)
\r
473 this.endDate = this.endDate.endOf('day');
\r
475 this.oldEndDate = this.endDate.clone();
\r
478 this.updateCalendars();
\r
479 this.updateInputText();
\r
482 updateView: function () {
\r
483 this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()).hour(this.startDate.hour()).minute(this.startDate.minute());
\r
484 this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()).hour(this.endDate.hour()).minute(this.endDate.minute());
\r
485 this.updateFormInputs();
\r
488 updateFormInputs: function () {
\r
489 this.container.find('input[name=daterangepicker_start]').val(this.startDate.format(this.format));
\r
490 this.container.find('input[name=daterangepicker_end]').val(this.endDate.format(this.format));
\r
492 if (this.startDate.isSame(this.endDate) || this.startDate.isBefore(this.endDate)) {
\r
493 this.container.find('button.applyBtn').removeAttr('disabled');
\r
495 this.container.find('button.applyBtn').attr('disabled', 'disabled');
\r
499 updateFromControl: function () {
\r
500 if (!this.element.is('input')) return;
\r
501 if (!this.element.val().length) return;
\r
503 var dateString = this.element.val().split(this.separator),
\r
507 if(dateString.length === 2) {
\r
508 start = moment(dateString[0], this.format).zone(this.timeZone);
\r
509 end = moment(dateString[1], this.format).zone(this.timeZone);
\r
512 if (this.singleDatePicker || start === null || end === null) {
\r
513 start = moment(this.element.val(), this.format).zone(this.timeZone);
\r
517 if (end.isBefore(start)) return;
\r
519 this.oldStartDate = this.startDate.clone();
\r
520 this.oldEndDate = this.endDate.clone();
\r
522 this.startDate = start;
\r
523 this.endDate = end;
\r
525 if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate))
\r
528 this.updateCalendars();
\r
531 notify: function () {
\r
533 this.cb(this.startDate, this.endDate, this.chosenLabel);
\r
536 move: function () {
\r
537 var parentOffset = { top: 0, left: 0 };
\r
538 var parentRightEdge = $(window).width();
\r
539 if (!this.parentEl.is('body')) {
\r
541 top: this.parentEl.offset().top - this.parentEl.scrollTop(),
\r
542 left: this.parentEl.offset().left - this.parentEl.scrollLeft()
\r
544 parentRightEdge = this.parentEl[0].clientWidth + this.parentEl.offset().left;
\r
547 if (this.opens == 'left') {
\r
548 this.container.css({
\r
549 top: this.element.offset().top + this.element.outerHeight() - parentOffset.top,
\r
550 right: parentRightEdge - this.element.offset().left - this.element.outerWidth(),
\r
553 if (this.container.offset().left < 0) {
\r
554 this.container.css({
\r
559 } else if (this.opens == 'center') {
\r
560 this.container.css({
\r
561 top: this.element.offset().top + this.element.outerHeight() - parentOffset.top,
\r
562 left: this.element.offset().left - parentOffset.left + this.element.outerWidth() / 2
\r
563 - this.container.outerWidth() / 2,
\r
566 if (this.container.offset().left < 0) {
\r
567 this.container.css({
\r
573 this.container.css({
\r
574 top: this.element.offset().top + this.element.outerHeight() - parentOffset.top,
\r
575 left: this.element.offset().left - parentOffset.left,
\r
578 if (this.container.offset().left + this.container.outerWidth() > $(window).width()) {
\r
579 this.container.css({
\r
587 toggle: function (e) {
\r
588 if (this.element.hasClass('active')) {
\r
595 show: function (e) {
\r
596 if (this.isShowing) return;
\r
598 this.element.addClass('active');
\r
599 this.container.show();
\r
602 // Create a click proxy that is private to this instance of datepicker, for unbinding
\r
603 this._outsideClickProxy = $.proxy(function (e) { this.outsideClick(e); }, this);
\r
604 // Bind global datepicker mousedown for hiding and
\r
606 .on('mousedown.daterangepicker', this._outsideClickProxy)
\r
607 // also support mobile devices
\r
608 .on('touchend.daterangepicker', this._outsideClickProxy)
\r
609 // also explicitly play nice with Bootstrap dropdowns, which stopPropagation when clicking them
\r
610 .on('click.daterangepicker', '[data-toggle=dropdown]', this._outsideClickProxy)
\r
611 // and also close when focus changes to outside the picker (eg. tabbing between controls)
\r
612 .on('focusin.daterangepicker', this._outsideClickProxy);
\r
614 this.isShowing = true;
\r
615 this.element.trigger('show.daterangepicker', this);
\r
618 outsideClick: function (e) {
\r
619 var target = $(e.target);
\r
620 // if the page is clicked anywhere except within the daterangerpicker/button
\r
621 // itself then call this.hide()
\r
623 // ie modal dialog fix
\r
624 e.type == "focusin" ||
\r
625 target.closest(this.element).length ||
\r
626 target.closest(this.container).length ||
\r
627 target.closest('.calendar-date').length
\r
632 hide: function (e) {
\r
633 if (!this.isShowing) return;
\r
636 .off('.daterangepicker');
\r
638 this.element.removeClass('active');
\r
639 this.container.hide();
\r
641 if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate))
\r
644 this.oldStartDate = this.startDate.clone();
\r
645 this.oldEndDate = this.endDate.clone();
\r
647 this.isShowing = false;
\r
648 this.element.trigger('hide.daterangepicker', this);
\r
651 enterRange: function (e) {
\r
652 // mouse pointer has entered a range label
\r
653 var label = e.target.innerHTML;
\r
654 if (label == this.locale.customRangeLabel) {
\r
657 var dates = this.ranges[label];
\r
658 this.container.find('input[name=daterangepicker_start]').val(dates[0].format(this.format));
\r
659 this.container.find('input[name=daterangepicker_end]').val(dates[1].format(this.format));
\r
663 showCalendars: function() {
\r
664 this.container.addClass('show-calendar');
\r
666 this.element.trigger('showCalendar.daterangepicker', this);
\r
669 hideCalendars: function() {
\r
670 this.container.removeClass('show-calendar');
\r
671 this.element.trigger('hideCalendar.daterangepicker', this);
\r
674 // when a date is typed into the start to end date textboxes
\r
675 inputsChanged: function (e) {
\r
676 var el = $(e.target);
\r
677 var date = moment(el.val(), this.format);
\r
678 if (!date.isValid()) return;
\r
680 var startDate, endDate;
\r
681 if (el.attr('name') === 'daterangepicker_start') {
\r
683 endDate = this.endDate;
\r
685 startDate = this.startDate;
\r
688 this.setCustomDates(startDate, endDate);
\r
691 inputsKeydown: function(e) {
\r
692 if (e.keyCode === 13) {
\r
693 this.inputsChanged(e);
\r
698 updateInputText: function() {
\r
699 if (this.element.is('input') && !this.singleDatePicker) {
\r
700 this.element.val(this.startDate.format(this.format) + this.separator + this.endDate.format(this.format));
\r
701 } else if (this.element.is('input')) {
\r
702 this.element.val(this.endDate.format(this.format));
\r
706 clickRange: function (e) {
\r
707 var label = e.target.innerHTML;
\r
708 this.chosenLabel = label;
\r
709 if (label == this.locale.customRangeLabel) {
\r
710 this.showCalendars();
\r
712 var dates = this.ranges[label];
\r
714 this.startDate = dates[0];
\r
715 this.endDate = dates[1];
\r
717 if (!this.timePicker) {
\r
718 this.startDate.startOf('day');
\r
719 this.endDate.endOf('day');
\r
722 this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()).hour(this.startDate.hour()).minute(this.startDate.minute());
\r
723 this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()).hour(this.endDate.hour()).minute(this.endDate.minute());
\r
724 this.updateCalendars();
\r
726 this.updateInputText();
\r
728 this.hideCalendars();
\r
730 this.element.trigger('apply.daterangepicker', this);
\r
734 clickPrev: function (e) {
\r
735 var cal = $(e.target).parents('.calendar');
\r
736 if (cal.hasClass('left')) {
\r
737 this.leftCalendar.month.subtract(1, 'month');
\r
739 this.rightCalendar.month.subtract(1, 'month');
\r
741 this.updateCalendars();
\r
744 clickNext: function (e) {
\r
745 var cal = $(e.target).parents('.calendar');
\r
746 if (cal.hasClass('left')) {
\r
747 this.leftCalendar.month.add(1, 'month');
\r
749 this.rightCalendar.month.add(1, 'month');
\r
751 this.updateCalendars();
\r
754 hoverDate: function (e) {
\r
755 var title = $(e.target).attr('data-title');
\r
756 var row = title.substr(1, 1);
\r
757 var col = title.substr(3, 1);
\r
758 var cal = $(e.target).parents('.calendar');
\r
760 if (cal.hasClass('left')) {
\r
761 this.container.find('input[name=daterangepicker_start]').val(this.leftCalendar.calendar[row][col].format(this.format));
\r
763 this.container.find('input[name=daterangepicker_end]').val(this.rightCalendar.calendar[row][col].format(this.format));
\r
767 setCustomDates: function(startDate, endDate) {
\r
768 this.chosenLabel = this.locale.customRangeLabel;
\r
769 if (startDate.isAfter(endDate)) {
\r
770 var difference = this.endDate.diff(this.startDate);
\r
771 endDate = moment(startDate).add(difference, 'ms');
\r
772 if (this.maxDate && endDate.isAfter(this.maxDate)) {
\r
773 endDate = this.maxDate;
\r
776 this.startDate = startDate;
\r
777 this.endDate = endDate;
\r
780 this.updateCalendars();
\r
783 clickDate: function (e) {
\r
784 var title = $(e.target).attr('data-title');
\r
785 var row = title.substr(1, 1);
\r
786 var col = title.substr(3, 1);
\r
787 var cal = $(e.target).parents('.calendar');
\r
789 var startDate, endDate;
\r
790 if (cal.hasClass('left')) {
\r
791 startDate = this.leftCalendar.calendar[row][col];
\r
792 endDate = this.endDate;
\r
793 if (typeof this.dateLimit === 'object') {
\r
794 var maxDate = moment(startDate).add(this.dateLimit).startOf('day');
\r
795 if (endDate.isAfter(maxDate)) {
\r
800 startDate = this.startDate;
\r
801 endDate = this.rightCalendar.calendar[row][col];
\r
802 if (typeof this.dateLimit === 'object') {
\r
803 var minDate = moment(endDate).subtract(this.dateLimit).startOf('day');
\r
804 if (startDate.isBefore(minDate)) {
\r
805 startDate = minDate;
\r
810 if (this.singleDatePicker && cal.hasClass('left')) {
\r
811 endDate = startDate.clone();
\r
812 } else if (this.singleDatePicker && cal.hasClass('right')) {
\r
813 startDate = endDate.clone();
\r
816 cal.find('td').removeClass('active');
\r
818 $(e.target).addClass('active');
\r
820 this.setCustomDates(startDate, endDate);
\r
822 if (!this.timePicker)
\r
823 endDate.endOf('day');
\r
825 if (this.singleDatePicker && !this.timePicker)
\r
829 clickApply: function (e) {
\r
830 this.updateInputText();
\r
832 this.element.trigger('apply.daterangepicker', this);
\r
835 clickCancel: function (e) {
\r
836 this.startDate = this.oldStartDate;
\r
837 this.endDate = this.oldEndDate;
\r
838 this.chosenLabel = this.oldChosenLabel;
\r
840 this.updateCalendars();
\r
842 this.element.trigger('cancel.daterangepicker', this);
\r
845 updateMonthYear: function (e) {
\r
846 var isLeft = $(e.target).closest('.calendar').hasClass('left'),
\r
847 leftOrRight = isLeft ? 'left' : 'right',
\r
848 cal = this.container.find('.calendar.'+leftOrRight);
\r
850 // Month must be Number for new moment versions
\r
851 var month = parseInt(cal.find('.monthselect').val(), 10);
\r
852 var year = cal.find('.yearselect').val();
\r
854 this[leftOrRight+'Calendar'].month.month(month).year(year);
\r
855 this.updateCalendars();
\r
858 updateTime: function(e) {
\r
860 var cal = $(e.target).closest('.calendar'),
\r
861 isLeft = cal.hasClass('left');
\r
863 var hour = parseInt(cal.find('.hourselect').val(), 10);
\r
864 var minute = parseInt(cal.find('.minuteselect').val(), 10);
\r
867 if (this.timePickerSeconds) {
\r
868 second = parseInt(cal.find('.secondselect').val(), 10);
\r
871 if (this.timePicker12Hour) {
\r
872 var ampm = cal.find('.ampmselect').val();
\r
873 if (ampm === 'PM' && hour < 12)
\r
875 if (ampm === 'AM' && hour === 12)
\r
880 var start = this.startDate.clone();
\r
882 start.minute(minute);
\r
883 start.second(second);
\r
884 this.startDate = start;
\r
885 this.leftCalendar.month.hour(hour).minute(minute).second(second);
\r
886 if (this.singleDatePicker)
\r
887 this.endDate = start.clone();
\r
889 var end = this.endDate.clone();
\r
891 end.minute(minute);
\r
892 end.second(second);
\r
893 this.endDate = end;
\r
894 if (this.singleDatePicker)
\r
895 this.startDate = end.clone();
\r
896 this.rightCalendar.month.hour(hour).minute(minute).second(second);
\r
900 this.updateCalendars();
\r
903 updateCalendars: function () {
\r
904 this.leftCalendar.calendar = this.buildCalendar(this.leftCalendar.month.month(), this.leftCalendar.month.year(), this.leftCalendar.month.hour(), this.leftCalendar.month.minute(), this.leftCalendar.month.second(), 'left');
\r
905 this.rightCalendar.calendar = this.buildCalendar(this.rightCalendar.month.month(), this.rightCalendar.month.year(), this.rightCalendar.month.hour(), this.rightCalendar.month.minute(), this.rightCalendar.month.second(), 'right');
\r
906 this.container.find('.calendar.left').empty().html(this.renderCalendar(this.leftCalendar.calendar, this.startDate, this.minDate, this.maxDate, 'left'));
\r
907 this.container.find('.calendar.right').empty().html(this.renderCalendar(this.rightCalendar.calendar, this.endDate, this.singleDatePicker ? this.minDate : this.startDate, this.maxDate, 'right'));
\r
909 this.container.find('.ranges li').removeClass('active');
\r
910 var customRange = true;
\r
912 for (var range in this.ranges) {
\r
913 if (this.timePicker) {
\r
914 if (this.startDate.isSame(this.ranges[range][0]) && this.endDate.isSame(this.ranges[range][1])) {
\r
915 customRange = false;
\r
916 this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')')
\r
917 .addClass('active').html();
\r
920 //ignore times when comparing dates if time picker is not enabled
\r
921 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')) {
\r
922 customRange = false;
\r
923 this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')')
\r
924 .addClass('active').html();
\r
930 this.chosenLabel = this.container.find('.ranges li:last').addClass('active').html();
\r
931 this.showCalendars();
\r
935 buildCalendar: function (month, year, hour, minute, second, side) {
\r
936 var daysInMonth = moment([year, month]).daysInMonth();
\r
937 var firstDay = moment([year, month, 1]);
\r
938 var lastDay = moment([year, month, daysInMonth]);
\r
939 var lastMonth = moment(firstDay).subtract(1, 'month').month();
\r
940 var lastYear = moment(firstDay).subtract(1, 'month').year();
\r
942 var daysInLastMonth = moment([lastYear, lastMonth]).daysInMonth();
\r
944 var dayOfWeek = firstDay.day();
\r
948 //initialize a 6 rows x 7 columns array for the calendar
\r
950 calendar.firstDay = firstDay;
\r
951 calendar.lastDay = lastDay;
\r
953 for (i = 0; i < 6; i++) {
\r
957 //populate the calendar with date objects
\r
958 var startDay = daysInLastMonth - dayOfWeek + this.locale.firstDay + 1;
\r
959 if (startDay > daysInLastMonth)
\r
962 if (dayOfWeek == this.locale.firstDay)
\r
963 startDay = daysInLastMonth - 6;
\r
965 var curDate = moment([lastYear, lastMonth, startDay, 12, minute, second]).zone(this.timeZone);
\r
968 for (i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add(24, 'hour')) {
\r
969 if (i > 0 && col % 7 === 0) {
\r
973 calendar[row][col] = curDate.clone().hour(hour);
\r
976 if (this.minDate && calendar[row][col].format('YYYY-MM-DD') == this.minDate.format('YYYY-MM-DD') && calendar[row][col].isBefore(this.minDate) && side == 'left') {
\r
977 calendar[row][col] = this.minDate.clone();
\r
980 if (this.maxDate && calendar[row][col].format('YYYY-MM-DD') == this.maxDate.format('YYYY-MM-DD') && calendar[row][col].isAfter(this.maxDate) && side == 'right') {
\r
981 calendar[row][col] = this.maxDate.clone();
\r
989 renderDropdowns: function (selected, minDate, maxDate) {
\r
990 var currentMonth = selected.month();
\r
991 var currentYear = selected.year();
\r
992 var maxYear = (maxDate && maxDate.year()) || (currentYear + 5);
\r
993 var minYear = (minDate && minDate.year()) || (currentYear - 50);
\r
995 var monthHtml = '<select class="monthselect">';
\r
996 var inMinYear = currentYear == minYear;
\r
997 var inMaxYear = currentYear == maxYear;
\r
999 for (var m = 0; m < 12; m++) {
\r
1000 if ((!inMinYear || m >= minDate.month()) && (!inMaxYear || m <= maxDate.month())) {
\r
1001 monthHtml += "<option value='" + m + "'" +
\r
1002 (m === currentMonth ? " selected='selected'" : "") +
\r
1003 ">" + this.locale.monthNames[m] + "</option>";
\r
1006 monthHtml += "</select>";
\r
1008 var yearHtml = '<select class="yearselect">';
\r
1010 for (var y = minYear; y <= maxYear; y++) {
\r
1011 yearHtml += '<option value="' + y + '"' +
\r
1012 (y === currentYear ? ' selected="selected"' : '') +
\r
1013 '>' + y + '</option>';
\r
1016 yearHtml += '</select>';
\r
1018 return monthHtml + yearHtml;
\r
1021 renderCalendar: function (calendar, selected, minDate, maxDate, side) {
\r
1023 var html = '<div class="calendar-date">';
\r
1024 html += '<table class="table-condensed">';
\r
1025 html += '<thead>';
\r
1028 // add empty cell for week number
\r
1029 if (this.showWeekNumbers)
\r
1030 html += '<th></th>';
\r
1032 if (!minDate || minDate.isBefore(calendar.firstDay)) {
\r
1033 html += '<th class="prev available"><i class="fa fa-arrow-left icon icon-arrow-left glyphicon glyphicon-arrow-left"></i></th>';
\r
1035 html += '<th></th>';
\r
1038 var dateHtml = this.locale.monthNames[calendar[1][1].month()] + calendar[1][1].format(" YYYY");
\r
1040 if (this.showDropdowns) {
\r
1041 dateHtml = this.renderDropdowns(calendar[1][1], minDate, maxDate);
\r
1044 html += '<th colspan="5" class="month">' + dateHtml + '</th>';
\r
1045 if (!maxDate || maxDate.isAfter(calendar.lastDay)) {
\r
1046 html += '<th class="next available"><i class="fa fa-arrow-right icon icon-arrow-right glyphicon glyphicon-arrow-right"></i></th>';
\r
1048 html += '<th></th>';
\r
1054 // add week number label
\r
1055 if (this.showWeekNumbers)
\r
1056 html += '<th class="week">' + this.locale.weekLabel + '</th>';
\r
1058 $.each(this.locale.daysOfWeek, function (index, dayOfWeek) {
\r
1059 html += '<th>' + dayOfWeek + '</th>';
\r
1063 html += '</thead>';
\r
1064 html += '<tbody>';
\r
1066 for (var row = 0; row < 6; row++) {
\r
1069 // add week number
\r
1070 if (this.showWeekNumbers)
\r
1071 html += '<td class="week">' + calendar[row][0].week() + '</td>';
\r
1073 for (var col = 0; col < 7; col++) {
\r
1074 var cname = 'available ';
\r
1075 cname += (calendar[row][col].month() == calendar[1][1].month()) ? '' : 'off';
\r
1077 if ((minDate && calendar[row][col].isBefore(minDate, 'day')) || (maxDate && calendar[row][col].isAfter(maxDate, 'day'))) {
\r
1078 cname = ' off disabled ';
\r
1079 } else if (calendar[row][col].format('YYYY-MM-DD') == selected.format('YYYY-MM-DD')) {
\r
1080 cname += ' active ';
\r
1081 if (calendar[row][col].format('YYYY-MM-DD') == this.startDate.format('YYYY-MM-DD')) {
\r
1082 cname += ' start-date ';
\r
1084 if (calendar[row][col].format('YYYY-MM-DD') == this.endDate.format('YYYY-MM-DD')) {
\r
1085 cname += ' end-date ';
\r
1087 } else if (calendar[row][col] >= this.startDate && calendar[row][col] <= this.endDate) {
\r
1088 cname += ' in-range ';
\r
1089 if (calendar[row][col].isSame(this.startDate)) { cname += ' start-date '; }
\r
1090 if (calendar[row][col].isSame(this.endDate)) { cname += ' end-date '; }
\r
1093 var title = 'r' + row + 'c' + col;
\r
1094 html += '<td class="' + cname.replace(/\s+/g, ' ').replace(/^\s?(.*?)\s?$/, '$1') + '" data-title="' + title + '">' + calendar[row][col].date() + '</td>';
\r
1099 html += '</tbody>';
\r
1100 html += '</table>';
\r
1104 if (this.timePicker) {
\r
1106 html += '<div class="calendar-time">';
\r
1107 html += '<select class="hourselect">';
\r
1109 // Disallow selections before the minDate or after the maxDate
\r
1111 var max_hour = 23;
\r
1113 if (minDate && (side == 'left' || this.singleDatePicker) && selected.format('YYYY-MM-DD') == minDate.format('YYYY-MM-DD')) {
\r
1114 min_hour = minDate.hour();
\r
1115 if (selected.hour() < min_hour)
\r
1116 selected.hour(min_hour);
\r
1117 if (this.timePicker12Hour && min_hour >= 12 && selected.hour() >= 12)
\r
1119 if (this.timePicker12Hour && min_hour == 12)
\r
1123 if (maxDate && (side == 'right' || this.singleDatePicker) && selected.format('YYYY-MM-DD') == maxDate.format('YYYY-MM-DD')) {
\r
1124 max_hour = maxDate.hour();
\r
1125 if (selected.hour() > max_hour)
\r
1126 selected.hour(max_hour);
\r
1127 if (this.timePicker12Hour && max_hour >= 12 && selected.hour() >= 12)
\r
1133 var selected_hour = selected.hour();
\r
1134 if (this.timePicker12Hour) {
\r
1137 if (selected_hour >= 12)
\r
1138 selected_hour -= 12;
\r
1139 if (selected_hour === 0)
\r
1140 selected_hour = 12;
\r
1143 for (i = start; i <= end; i++) {
\r
1145 if (i == selected_hour) {
\r
1146 html += '<option value="' + i + '" selected="selected">' + i + '</option>';
\r
1147 } else if (i < min_hour || i > max_hour) {
\r
1148 html += '<option value="' + i + '" disabled="disabled" class="disabled">' + i + '</option>';
\r
1150 html += '<option value="' + i + '">' + i + '</option>';
\r
1154 html += '</select> : ';
\r
1156 html += '<select class="minuteselect">';
\r
1158 // Disallow selections before the minDate or after the maxDate
\r
1159 var min_minute = 0;
\r
1160 var max_minute = 59;
\r
1162 if (minDate && (side == 'left' || this.singleDatePicker) && selected.format('YYYY-MM-DD h A') == minDate.format('YYYY-MM-DD h A')) {
\r
1163 min_minute = minDate.minute();
\r
1164 if (selected.minute() < min_minute)
\r
1165 selected.minute(min_minute);
\r
1168 if (maxDate && (side == 'right' || this.singleDatePicker) && selected.format('YYYY-MM-DD h A') == maxDate.format('YYYY-MM-DD h A')) {
\r
1169 max_minute = maxDate.minute();
\r
1170 if (selected.minute() > max_minute)
\r
1171 selected.minute(max_minute);
\r
1174 for (i = 0; i < 60; i += this.timePickerIncrement) {
\r
1178 if (i == selected.minute()) {
\r
1179 html += '<option value="' + i + '" selected="selected">' + num + '</option>';
\r
1180 } else if (i < min_minute || i > max_minute) {
\r
1181 html += '<option value="' + i + '" disabled="disabled" class="disabled">' + num + '</option>';
\r
1183 html += '<option value="' + i + '">' + num + '</option>';
\r
1187 html += '</select> ';
\r
1189 if (this.timePickerSeconds) {
\r
1190 html += ': <select class="secondselect">';
\r
1192 for (i = 0; i < 60; i += this.timePickerIncrement) {
\r
1196 if (i == selected.second()) {
\r
1197 html += '<option value="' + i + '" selected="selected">' + num + '</option>';
\r
1199 html += '<option value="' + i + '">' + num + '</option>';
\r
1203 html += '</select>';
\r
1206 if (this.timePicker12Hour) {
\r
1207 html += '<select class="ampmselect">';
\r
1209 // Disallow selection before the minDate or after the maxDate
\r
1213 if (minDate && (side == 'left' || this.singleDatePicker) && selected.format('YYYY-MM-DD') == minDate.format('YYYY-MM-DD') && minDate.hour() >= 12) {
\r
1214 am_html = ' disabled="disabled" class="disabled"';
\r
1217 if (maxDate && (side == 'right' || this.singleDatePicker) && selected.format('YYYY-MM-DD') == maxDate.format('YYYY-MM-DD') && maxDate.hour() < 12) {
\r
1218 pm_html = ' disabled="disabled" class="disabled"';
\r
1221 if (selected.hour() >= 12) {
\r
1222 html += '<option value="AM"' + am_html + '>AM</option><option value="PM" selected="selected"' + pm_html + '>PM</option>';
\r
1224 html += '<option value="AM" selected="selected"' + am_html + '>AM</option><option value="PM"' + pm_html + '>PM</option>';
\r
1226 html += '</select>';
\r
1237 remove: function() {
\r
1239 this.container.remove();
\r
1240 this.element.off('.daterangepicker');
\r
1241 this.element.removeData('daterangepicker');
\r
1247 $.fn.daterangepicker = function (options, cb) {
\r
1248 this.each(function () {
\r
1250 if (el.data('daterangepicker'))
\r
1251 el.data('daterangepicker').remove();
\r
1252 el.data('daterangepicker', new DateRangePicker(el, options, cb));
\r