redo gridftp navbar
[qcg-portal.git] / qcg / static / qcg / daterangepicker / daterangepicker.js
1 /**
2 * @version: 1.3.17
3 * @author: Dan Grossman http://www.dangrossman.info/
4 * @date: 2014-11-25
5 * @copyright: Copyright (c) 2012-2014 Dan Grossman. All rights reserved.
6 * @license: Licensed under the MIT license. See http://www.opensource.org/licenses/mit-license.php
7 * @website: http://www.improvely.com/
8 */
9
10 (function(root, factory) {
11
12   if (typeof define === 'function' && define.amd) {
13     define(['moment', 'jquery', 'exports'], function(momentjs, $, exports) {
14       root.daterangepicker = factory(root, exports, momentjs, $);
15     });
16
17   } else if (typeof exports !== 'undefined') {
18     var momentjs = require('moment');
19     var jQuery;
20     try {
21       jQuery = require('jquery');
22     } catch (err) {
23       jQuery = window.jQuery;
24       if (!jQuery) throw new Error('jQuery dependency not found');
25     }
26
27     factory(root, exports, momentjs, jQuery);
28
29   // Finally, as a browser global.
30   } else {
31     root.daterangepicker = factory(root, {}, root.moment, (root.jQuery || root.Zepto || root.ender || root.$));
32   }
33
34 }(this, function(root, daterangepicker, moment, $) {
35
36     var DateRangePicker = function (element, options, cb) {
37
38         // by default, the daterangepicker element is placed at the bottom of HTML body
39         this.parentEl = 'body';
40
41         //element that triggered the date range picker
42         this.element = $(element);
43
44         //tracks visible state
45         this.isShowing = false;
46
47         //create the picker HTML object
48         var DRPTemplate = '<div class="daterangepicker dropdown-menu">' +
49                 '<div class="calendar first left"></div>' +
50                 '<div class="calendar second right"></div>' +
51                 '<div class="ranges">' +
52                   '<div class="range_inputs">' +
53                     '<div class="daterangepicker_start_input">' +
54                       '<label for="daterangepicker_start"></label>' +
55                       '<input class="input-mini" type="text" name="daterangepicker_start" value="" />' +
56                     '</div>' +
57                     '<div class="daterangepicker_end_input">' +
58                       '<label for="daterangepicker_end"></label>' +
59                       '<input class="input-mini" type="text" name="daterangepicker_end" value="" />' +
60                     '</div>' +
61                     '<button class="applyBtn" disabled="disabled"></button>&nbsp;' +
62                     '<button class="cancelBtn"></button>' +
63                   '</div>' +
64                 '</div>' +
65               '</div>';
66
67         //custom options
68         if (typeof options !== 'object' || options === null)
69             options = {};
70
71         this.parentEl = (typeof options === 'object' && options.parentEl && $(options.parentEl).length) ? $(options.parentEl) : $(this.parentEl);
72         this.container = $(DRPTemplate).appendTo(this.parentEl);
73
74         this.setOptions(options, cb);
75
76         //apply CSS classes and labels to buttons
77         var c = this.container;
78         $.each(this.buttonClasses, function (idx, val) {
79             c.find('button').addClass(val);
80         });
81         this.container.find('.daterangepicker_start_input label').html(this.locale.fromLabel);
82         this.container.find('.daterangepicker_end_input label').html(this.locale.toLabel);
83         if (this.applyClass.length)
84             this.container.find('.applyBtn').addClass(this.applyClass);
85         if (this.cancelClass.length)
86             this.container.find('.cancelBtn').addClass(this.cancelClass);
87         this.container.find('.applyBtn').html(this.locale.applyLabel);
88         this.container.find('.cancelBtn').html(this.locale.cancelLabel);
89
90         //event listeners
91
92         this.container.find('.calendar')
93             .on('click.daterangepicker', '.prev', $.proxy(this.clickPrev, this))
94             .on('click.daterangepicker', '.next', $.proxy(this.clickNext, this))
95             .on('click.daterangepicker', 'td.available', $.proxy(this.clickDate, this))
96             .on('mouseenter.daterangepicker', 'td.available', $.proxy(this.hoverDate, this))
97             .on('mouseleave.daterangepicker', 'td.available', $.proxy(this.updateFormInputs, this))
98             .on('change.daterangepicker', 'select.yearselect', $.proxy(this.updateMonthYear, this))
99             .on('change.daterangepicker', 'select.monthselect', $.proxy(this.updateMonthYear, this))
100             .on('change.daterangepicker', 'select.hourselect,select.minuteselect,select.secondselect,select.ampmselect', $.proxy(this.updateTime, this));
101
102         this.container.find('.ranges')
103             .on('click.daterangepicker', 'button.applyBtn', $.proxy(this.clickApply, this))
104             .on('click.daterangepicker', 'button.cancelBtn', $.proxy(this.clickCancel, this))
105             .on('click.daterangepicker', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.showCalendars, this))
106             .on('change.daterangepicker', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.inputsChanged, this))
107             .on('keydown.daterangepicker', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.inputsKeydown, this))
108             .on('click.daterangepicker', 'li', $.proxy(this.clickRange, this))
109             .on('mouseenter.daterangepicker', 'li', $.proxy(this.enterRange, this))
110             .on('mouseleave.daterangepicker', 'li', $.proxy(this.updateFormInputs, this));
111
112         if (this.element.is('input')) {
113             this.element.on({
114                 'click.daterangepicker': $.proxy(this.show, this),
115                 'focus.daterangepicker': $.proxy(this.show, this),
116                 'keyup.daterangepicker': $.proxy(this.updateFromControl, this)
117             });
118         } else {
119             this.element.on('click.daterangepicker', $.proxy(this.toggle, this));
120         }
121
122     };
123
124     DateRangePicker.prototype = {
125
126         constructor: DateRangePicker,
127
128         setOptions: function(options, callback) {
129
130             this.startDate = moment().startOf('day');
131             this.endDate = moment().endOf('day');
132             this.timeZone = moment().zone();
133             this.minDate = false;
134             this.maxDate = false;
135             this.dateLimit = false;
136
137             this.showDropdowns = false;
138             this.showWeekNumbers = false;
139             this.timePicker = false;
140             this.timePickerSeconds = false;
141             this.timePickerIncrement = 30;
142             this.timePicker12Hour = true;
143             this.singleDatePicker = false;
144             this.ranges = {};
145
146             this.opens = 'right';
147             if (this.element.hasClass('pull-right'))
148                 this.opens = 'left';
149
150             this.buttonClasses = ['btn', 'btn-small btn-sm'];
151             this.applyClass = 'btn-success';
152             this.cancelClass = 'btn-default';
153
154             this.format = 'MM/DD/YYYY';
155             this.separator = ' - ';
156
157             this.locale = {
158                 applyLabel: 'Apply',
159                 cancelLabel: 'Cancel',
160                 fromLabel: 'From',
161                 toLabel: 'To',
162                 weekLabel: 'W',
163                 customRangeLabel: 'Custom Range',
164                 daysOfWeek: moment.weekdaysMin(),
165                 monthNames: moment.monthsShort(),
166                 firstDay: moment.localeData()._week.dow
167             };
168
169             this.cb = function () { };
170
171             if (typeof options.format === 'string')
172                 this.format = options.format;
173
174             if (typeof options.separator === 'string')
175                 this.separator = options.separator;
176
177             if (typeof options.startDate === 'string')
178                 this.startDate = moment(options.startDate, this.format);
179
180             if (typeof options.endDate === 'string')
181                 this.endDate = moment(options.endDate, this.format);
182
183             if (typeof options.minDate === 'string')
184                 this.minDate = moment(options.minDate, this.format);
185
186             if (typeof options.maxDate === 'string')
187                 this.maxDate = moment(options.maxDate, this.format);
188
189             if (typeof options.startDate === 'object')
190                 this.startDate = moment(options.startDate);
191
192             if (typeof options.endDate === 'object')
193                 this.endDate = moment(options.endDate);
194
195             if (typeof options.minDate === 'object')
196                 this.minDate = moment(options.minDate);
197
198             if (typeof options.maxDate === 'object')
199                 this.maxDate = moment(options.maxDate);
200
201             if (typeof options.applyClass === 'string')
202                 this.applyClass = options.applyClass;
203
204             if (typeof options.cancelClass === 'string')
205                 this.cancelClass = options.cancelClass;
206
207             if (typeof options.dateLimit === 'object')
208                 this.dateLimit = options.dateLimit;
209
210             if (typeof options.locale === 'object') {
211
212                 if (typeof options.locale.daysOfWeek === 'object') {
213                     // Create a copy of daysOfWeek to avoid modification of original
214                     // options object for reusability in multiple daterangepicker instances
215                     this.locale.daysOfWeek = options.locale.daysOfWeek.slice();
216                 }
217
218                 if (typeof options.locale.monthNames === 'object') {
219                   this.locale.monthNames = options.locale.monthNames.slice();
220                 }
221
222                 if (typeof options.locale.firstDay === 'number') {
223                   this.locale.firstDay = options.locale.firstDay;
224                 }
225
226                 if (typeof options.locale.applyLabel === 'string') {
227                   this.locale.applyLabel = options.locale.applyLabel;
228                 }
229
230                 if (typeof options.locale.cancelLabel === 'string') {
231                   this.locale.cancelLabel = options.locale.cancelLabel;
232                 }
233
234                 if (typeof options.locale.fromLabel === 'string') {
235                   this.locale.fromLabel = options.locale.fromLabel;
236                 }
237
238                 if (typeof options.locale.toLabel === 'string') {
239                   this.locale.toLabel = options.locale.toLabel;
240                 }
241
242                 if (typeof options.locale.weekLabel === 'string') {
243                   this.locale.weekLabel = options.locale.weekLabel;
244                 }
245
246                 if (typeof options.locale.customRangeLabel === 'string') {
247                   this.locale.customRangeLabel = options.locale.customRangeLabel;
248                 }
249             }
250
251             if (typeof options.opens === 'string')
252                 this.opens = options.opens;
253
254             if (typeof options.showWeekNumbers === 'boolean') {
255                 this.showWeekNumbers = options.showWeekNumbers;
256             }
257
258             if (typeof options.buttonClasses === 'string') {
259                 this.buttonClasses = [options.buttonClasses];
260             }
261
262             if (typeof options.buttonClasses === 'object') {
263                 this.buttonClasses = options.buttonClasses;
264             }
265
266             if (typeof options.showDropdowns === 'boolean') {
267                 this.showDropdowns = options.showDropdowns;
268             }
269
270             if (typeof options.singleDatePicker === 'boolean') {
271                 this.singleDatePicker = options.singleDatePicker;
272                 if (this.singleDatePicker) {
273                     this.endDate = this.startDate.clone();
274                 }
275             }
276
277             if (typeof options.timePicker === 'boolean') {
278                 this.timePicker = options.timePicker;
279             }
280
281             if (typeof options.timePickerSeconds === 'boolean') {
282                 this.timePickerSeconds = options.timePickerSeconds;
283             }
284
285             if (typeof options.timePickerIncrement === 'number') {
286                 this.timePickerIncrement = options.timePickerIncrement;
287             }
288
289             if (typeof options.timePicker12Hour === 'boolean') {
290                 this.timePicker12Hour = options.timePicker12Hour;
291             }
292
293             // update day names order to firstDay
294             if (this.locale.firstDay != 0) {
295                 var iterator = this.locale.firstDay;
296                 while (iterator > 0) {
297                     this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift());
298                     iterator--;
299                 }
300             }
301
302             var start, end, range;
303
304             //if no start/end dates set, check if an input element contains initial values
305             if (typeof options.startDate === 'undefined' && typeof options.endDate === 'undefined') {
306                 if ($(this.element).is('input[type=text]')) {
307                     var val = $(this.element).val(), 
308                         split = val.split(this.separator);
309                     
310                     start = end = null;
311                     
312                     if (split.length == 2) {
313                         start = moment(split[0], this.format);
314                         end = moment(split[1], this.format);
315                     } else if (this.singleDatePicker && val !== "") {
316                         start = moment(val, this.format);
317                         end = moment(val, this.format);
318                     }
319                     if (start !== null && end !== null) {
320                         this.startDate = start;
321                         this.endDate = end;
322                     }
323                 }
324             }
325
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)
327             if (typeof options.timeZone === 'string' || typeof options.timeZone === 'number') {
328                 this.timeZone = options.timeZone;
329                 this.startDate.zone(this.timeZone);
330                 this.endDate.zone(this.timeZone);
331             } else {
332                 this.timeZone = moment(this.startDate).zone();
333             }
334
335             if (typeof options.ranges === 'object') {
336                 for (range in options.ranges) {
337
338                     if (typeof options.ranges[range][0] === 'string')
339                         start = moment(options.ranges[range][0], this.format);
340                     else
341                         start = moment(options.ranges[range][0]);
342
343                     if (typeof options.ranges[range][1] === 'string')
344                         end = moment(options.ranges[range][1], this.format);
345                     else
346                         end = moment(options.ranges[range][1]);
347
348                     // If we have a min/max date set, bound this range
349                     // to it, but only if it would otherwise fall
350                     // outside of the min/max.
351                     if (this.minDate && start.isBefore(this.minDate))
352                         start = moment(this.minDate);
353
354                     if (this.maxDate && end.isAfter(this.maxDate))
355                         end = moment(this.maxDate);
356
357                     // If the end of the range is before the minimum (if min is set) OR
358                     // the start of the range is after the max (also if set) don't display this
359                     // range option.
360                     if ((this.minDate && end.isBefore(this.minDate)) || (this.maxDate && start.isAfter(this.maxDate))) {
361                         continue;
362                     }
363
364                     this.ranges[range] = [start, end];
365                 }
366
367                 var list = '<ul>';
368                 for (range in this.ranges) {
369                     list += '<li>' + range + '</li>';
370                 }
371                 list += '<li>' + this.locale.customRangeLabel + '</li>';
372                 list += '</ul>';
373                 this.container.find('.ranges ul').remove();
374                 this.container.find('.ranges').prepend(list);
375             }
376
377             if (typeof callback === 'function') {
378                 this.cb = callback;
379             }
380
381             if (!this.timePicker) {
382                 this.startDate = this.startDate.startOf('day');
383                 this.endDate = this.endDate.endOf('day');
384             }
385
386             if (this.singleDatePicker) {
387                 this.opens = 'right';
388                 this.container.addClass('single');
389                 this.container.find('.calendar.right').show();
390                 this.container.find('.calendar.left').hide();
391                 if (!this.timePicker) {
392                     this.container.find('.ranges').hide();
393                 } else {
394                     this.container.find('.ranges .daterangepicker_start_input, .ranges .daterangepicker_end_input').hide();
395                 }
396                 if (!this.container.find('.calendar.right').hasClass('single'))
397                     this.container.find('.calendar.right').addClass('single');
398             } else {
399                 this.container.removeClass('single');
400                 this.container.find('.calendar.right').removeClass('single');
401                 this.container.find('.ranges').show();
402             }
403
404             this.oldStartDate = this.startDate.clone();
405             this.oldEndDate = this.endDate.clone();
406             this.oldChosenLabel = this.chosenLabel;
407
408             this.leftCalendar = {
409                 month: moment([this.startDate.year(), this.startDate.month(), 1, this.startDate.hour(), this.startDate.minute(), this.startDate.second()]),
410                 calendar: []
411             };
412
413             this.rightCalendar = {
414                 month: moment([this.endDate.year(), this.endDate.month(), 1, this.endDate.hour(), this.endDate.minute(), this.endDate.second()]),
415                 calendar: []
416             };
417
418             if (this.opens == 'right' || this.opens == 'center') {
419                 //swap calendar positions
420                 var first = this.container.find('.calendar.first');
421                 var second = this.container.find('.calendar.second');
422
423                 if (second.hasClass('single')) {
424                     second.removeClass('single');
425                     first.addClass('single');
426                 }
427
428                 first.removeClass('left').addClass('right');
429                 second.removeClass('right').addClass('left');
430
431                 if (this.singleDatePicker) {
432                     first.show();
433                     second.hide();
434                 }
435             }
436
437             if (typeof options.ranges === 'undefined' && !this.singleDatePicker) {
438                 this.container.addClass('show-calendar');
439             }
440
441             this.container.addClass('opens' + this.opens);
442
443             this.updateView();
444             this.updateCalendars();
445
446         },
447
448         setStartDate: function(startDate) {
449             if (typeof startDate === 'string')
450                 this.startDate = moment(startDate, this.format).zone(this.timeZone);
451
452             if (typeof startDate === 'object')
453                 this.startDate = moment(startDate);
454
455             if (!this.timePicker)
456                 this.startDate = this.startDate.startOf('day');
457
458             this.oldStartDate = this.startDate.clone();
459
460             this.updateView();
461             this.updateCalendars();
462             this.updateInputText();
463         },
464
465         setEndDate: function(endDate) {
466             if (typeof endDate === 'string')
467                 this.endDate = moment(endDate, this.format).zone(this.timeZone);
468
469             if (typeof endDate === 'object')
470                 this.endDate = moment(endDate);
471
472             if (!this.timePicker)
473                 this.endDate = this.endDate.endOf('day');
474
475             this.oldEndDate = this.endDate.clone();
476
477             this.updateView();
478             this.updateCalendars();
479             this.updateInputText();
480         },
481
482         updateView: function () {
483             this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()).hour(this.startDate.hour()).minute(this.startDate.minute());
484             this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()).hour(this.endDate.hour()).minute(this.endDate.minute());
485             this.updateFormInputs();
486         },
487
488         updateFormInputs: function () {
489             this.container.find('input[name=daterangepicker_start]').val(this.startDate.format(this.format));
490             this.container.find('input[name=daterangepicker_end]').val(this.endDate.format(this.format));
491
492             if (this.startDate.isSame(this.endDate) || this.startDate.isBefore(this.endDate)) {
493                 this.container.find('button.applyBtn').removeAttr('disabled');
494             } else {
495                 this.container.find('button.applyBtn').attr('disabled', 'disabled');
496             }
497         },
498
499         updateFromControl: function () {
500             if (!this.element.is('input')) return;
501             if (!this.element.val().length) return;
502
503             var dateString = this.element.val().split(this.separator),
504                 start = null,
505                 end = null;
506
507             if(dateString.length === 2) {
508                 start = moment(dateString[0], this.format).zone(this.timeZone);
509                 end = moment(dateString[1], this.format).zone(this.timeZone);
510             }
511
512             if (this.singleDatePicker || start === null || end === null) {
513                 start = moment(this.element.val(), this.format).zone(this.timeZone);
514                 end = start;
515             }
516
517             if (end.isBefore(start)) return;
518
519             this.oldStartDate = this.startDate.clone();
520             this.oldEndDate = this.endDate.clone();
521
522             this.startDate = start;
523             this.endDate = end;
524
525             if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate))
526                 this.notify();
527
528             this.updateCalendars();
529         },
530
531         notify: function () {
532             this.updateView();
533             this.cb(this.startDate, this.endDate, this.chosenLabel);
534         },
535
536         move: function () {
537             var parentOffset = { top: 0, left: 0 };
538             var parentRightEdge = $(window).width();
539             if (!this.parentEl.is('body')) {
540                 parentOffset = {
541                     top: this.parentEl.offset().top - this.parentEl.scrollTop(),
542                     left: this.parentEl.offset().left - this.parentEl.scrollLeft()
543                 };
544                 parentRightEdge = this.parentEl[0].clientWidth + this.parentEl.offset().left;
545             }
546
547             if (this.opens == 'left') {
548                 this.container.css({
549                     top: this.element.offset().top + this.element.outerHeight() - parentOffset.top,
550                     right: parentRightEdge - this.element.offset().left - this.element.outerWidth(),
551                     left: 'auto'
552                 });
553                 if (this.container.offset().left < 0) {
554                     this.container.css({
555                         right: 'auto',
556                         left: 9
557                     });
558                 }
559             } else if (this.opens == 'center') {
560                 this.container.css({
561                     top: this.element.offset().top + this.element.outerHeight() - parentOffset.top,
562                     left: this.element.offset().left - parentOffset.left + this.element.outerWidth() / 2
563                             - this.container.outerWidth() / 2,
564                     right: 'auto'
565                 });
566                 if (this.container.offset().left < 0) {
567                     this.container.css({
568                         right: 'auto',
569                         left: 9
570                     });
571                 }
572             } else {
573                 this.container.css({
574                     top: this.element.offset().top + this.element.outerHeight() - parentOffset.top,
575                     left: this.element.offset().left - parentOffset.left,
576                     right: 'auto'
577                 });
578                 if (this.container.offset().left + this.container.outerWidth() > $(window).width()) {
579                     this.container.css({
580                         left: 'auto',
581                         right: 0
582                     });
583                 }
584             }
585         },
586
587         toggle: function (e) {
588             if (this.element.hasClass('active')) {
589                 this.hide();
590             } else {
591                 this.show();
592             }
593         },
594
595         show: function (e) {
596             if (this.isShowing) return;
597
598             this.element.addClass('active');
599             this.container.show();
600             this.move();
601
602             // Create a click proxy that is private to this instance of datepicker, for unbinding
603             this._outsideClickProxy = $.proxy(function (e) { this.outsideClick(e); }, this);
604             // Bind global datepicker mousedown for hiding and
605             $(document)
606               .on('mousedown.daterangepicker', this._outsideClickProxy)
607               // also support mobile devices
608               .on('touchend.daterangepicker', this._outsideClickProxy)
609               // also explicitly play nice with Bootstrap dropdowns, which stopPropagation when clicking them
610               .on('click.daterangepicker', '[data-toggle=dropdown]', this._outsideClickProxy)
611               // and also close when focus changes to outside the picker (eg. tabbing between controls)
612               .on('focusin.daterangepicker', this._outsideClickProxy);
613
614             this.isShowing = true;
615             this.element.trigger('show.daterangepicker', this);
616         },
617
618         outsideClick: function (e) {
619             var target = $(e.target);
620             // if the page is clicked anywhere except within the daterangerpicker/button
621             // itself then call this.hide()
622             if (
623                 // ie modal dialog fix
624                 e.type == "focusin" ||
625                 target.closest(this.element).length ||
626                 target.closest(this.container).length ||
627                 target.closest('.calendar-date').length
628                 ) return;
629             this.hide();
630         },
631
632         hide: function (e) {
633             if (!this.isShowing) return;
634
635             $(document)
636               .off('.daterangepicker');
637
638             this.element.removeClass('active');
639             this.container.hide();
640
641             if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate))
642                 this.notify();
643
644             this.oldStartDate = this.startDate.clone();
645             this.oldEndDate = this.endDate.clone();
646
647             this.isShowing = false;
648             this.element.trigger('hide.daterangepicker', this);
649         },
650
651         enterRange: function (e) {
652             // mouse pointer has entered a range label
653             var label = e.target.innerHTML;
654             if (label == this.locale.customRangeLabel) {
655                 this.updateView();
656             } else {
657                 var dates = this.ranges[label];
658                 this.container.find('input[name=daterangepicker_start]').val(dates[0].format(this.format));
659                 this.container.find('input[name=daterangepicker_end]').val(dates[1].format(this.format));
660             }
661         },
662
663         showCalendars: function() {
664             this.container.addClass('show-calendar');
665             this.move();
666             this.element.trigger('showCalendar.daterangepicker', this);
667         },
668
669         hideCalendars: function() {
670             this.container.removeClass('show-calendar');
671             this.element.trigger('hideCalendar.daterangepicker', this);
672         },
673
674         // when a date is typed into the start to end date textboxes
675         inputsChanged: function (e) {
676             var el = $(e.target);
677             var date = moment(el.val(), this.format);
678             if (!date.isValid()) return;
679
680             var startDate, endDate;
681             if (el.attr('name') === 'daterangepicker_start') {
682                 startDate = date;
683                 endDate = this.endDate;
684             } else {
685                 startDate = this.startDate;
686                 endDate = date;
687             }
688             this.setCustomDates(startDate, endDate);
689         },
690
691         inputsKeydown: function(e) {
692             if (e.keyCode === 13) {
693                 this.inputsChanged(e);
694                 this.notify();
695             }
696         },
697
698         updateInputText: function() {
699             if (this.element.is('input') && !this.singleDatePicker) {
700                 this.element.val(this.startDate.format(this.format) + this.separator + this.endDate.format(this.format));
701             } else if (this.element.is('input')) {
702                 this.element.val(this.endDate.format(this.format));
703             }
704         },
705
706         clickRange: function (e) {
707             var label = e.target.innerHTML;
708             this.chosenLabel = label;
709             if (label == this.locale.customRangeLabel) {
710                 this.showCalendars();
711             } else {
712                 var dates = this.ranges[label];
713
714                 this.startDate = dates[0];
715                 this.endDate = dates[1];
716
717                 if (!this.timePicker) {
718                     this.startDate.startOf('day');
719                     this.endDate.endOf('day');
720                 }
721
722                 this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()).hour(this.startDate.hour()).minute(this.startDate.minute());
723                 this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()).hour(this.endDate.hour()).minute(this.endDate.minute());
724                 this.updateCalendars();
725
726                 this.updateInputText();
727
728                 this.hideCalendars();
729                 this.hide();
730                 this.element.trigger('apply.daterangepicker', this);
731             }
732         },
733
734         clickPrev: function (e) {
735             var cal = $(e.target).parents('.calendar');
736             if (cal.hasClass('left')) {
737                 this.leftCalendar.month.subtract(1, 'month');
738             } else {
739                 this.rightCalendar.month.subtract(1, 'month');
740             }
741             this.updateCalendars();
742         },
743
744         clickNext: function (e) {
745             var cal = $(e.target).parents('.calendar');
746             if (cal.hasClass('left')) {
747                 this.leftCalendar.month.add(1, 'month');
748             } else {
749                 this.rightCalendar.month.add(1, 'month');
750             }
751             this.updateCalendars();
752         },
753
754         hoverDate: function (e) {
755             var title = $(e.target).attr('data-title');
756             var row = title.substr(1, 1);
757             var col = title.substr(3, 1);
758             var cal = $(e.target).parents('.calendar');
759
760             if (cal.hasClass('left')) {
761                 this.container.find('input[name=daterangepicker_start]').val(this.leftCalendar.calendar[row][col].format(this.format));
762             } else {
763                 this.container.find('input[name=daterangepicker_end]').val(this.rightCalendar.calendar[row][col].format(this.format));
764             }
765         },
766
767         setCustomDates: function(startDate, endDate) {
768             this.chosenLabel = this.locale.customRangeLabel;
769             if (startDate.isAfter(endDate)) {
770                 var difference = this.endDate.diff(this.startDate);
771                 endDate = moment(startDate).add(difference, 'ms');
772             }
773             this.startDate = startDate;
774             this.endDate = endDate;
775
776             this.updateView();
777             this.updateCalendars();
778         },
779
780         clickDate: function (e) {
781             var title = $(e.target).attr('data-title');
782             var row = title.substr(1, 1);
783             var col = title.substr(3, 1);
784             var cal = $(e.target).parents('.calendar');
785
786             var startDate, endDate;
787             if (cal.hasClass('left')) {
788                 startDate = this.leftCalendar.calendar[row][col];
789                 endDate = this.endDate;
790                 if (typeof this.dateLimit === 'object') {
791                     var maxDate = moment(startDate).add(this.dateLimit).startOf('day');
792                     if (endDate.isAfter(maxDate)) {
793                         endDate = maxDate;
794                     }
795                 }
796             } else {
797                 startDate = this.startDate;
798                 endDate = this.rightCalendar.calendar[row][col];
799                 if (typeof this.dateLimit === 'object') {
800                     var minDate = moment(endDate).subtract(this.dateLimit).startOf('day');
801                     if (startDate.isBefore(minDate)) {
802                         startDate = minDate;
803                     }
804                 }
805             }
806
807             if (this.singleDatePicker && cal.hasClass('left')) {
808                 endDate = startDate.clone();
809             } else if (this.singleDatePicker && cal.hasClass('right')) {
810                 startDate = endDate.clone();
811             }
812
813             cal.find('td').removeClass('active');
814
815             $(e.target).addClass('active');
816
817             this.setCustomDates(startDate, endDate);
818
819             if (!this.timePicker)
820                 endDate.endOf('day');
821
822             if (this.singleDatePicker && !this.timePicker)
823                 this.clickApply();
824         },
825
826         clickApply: function (e) {
827             this.updateInputText();
828             this.hide();
829             this.element.trigger('apply.daterangepicker', this);
830         },
831
832         clickCancel: function (e) {
833             this.startDate = this.oldStartDate;
834             this.endDate = this.oldEndDate;
835             this.chosenLabel = this.oldChosenLabel;
836             this.updateView();
837             this.updateCalendars();
838             this.hide();
839             this.element.trigger('cancel.daterangepicker', this);
840         },
841
842         updateMonthYear: function (e) {
843             var isLeft = $(e.target).closest('.calendar').hasClass('left'),
844                 leftOrRight = isLeft ? 'left' : 'right',
845                 cal = this.container.find('.calendar.'+leftOrRight);
846
847             // Month must be Number for new moment versions
848             var month = parseInt(cal.find('.monthselect').val(), 10);
849             var year = cal.find('.yearselect').val();
850
851             this[leftOrRight+'Calendar'].month.month(month).year(year);
852             this.updateCalendars();
853         },
854
855         updateTime: function(e) {
856
857             var cal = $(e.target).closest('.calendar'),
858                 isLeft = cal.hasClass('left');
859
860             var hour = parseInt(cal.find('.hourselect').val(), 10);
861             var minute = parseInt(cal.find('.minuteselect').val(), 10);
862             var second = 0;
863
864             if (this.timePickerSeconds) {
865                 second = parseInt(cal.find('.secondselect').val(), 10);
866             }
867
868             if (this.timePicker12Hour) {
869                 var ampm = cal.find('.ampmselect').val();
870                 if (ampm === 'PM' && hour < 12)
871                     hour += 12;
872                 if (ampm === 'AM' && hour === 12)
873                     hour = 0;
874             }
875
876             if (isLeft) {
877                 var start = this.startDate.clone();
878                 start.hour(hour);
879                 start.minute(minute);
880                 start.second(second);
881                 this.startDate = start;
882                 this.leftCalendar.month.hour(hour).minute(minute).second(second);
883                 if (this.singleDatePicker)
884                     this.endDate = start.clone();
885             } else {
886                 var end = this.endDate.clone();
887                 end.hour(hour);
888                 end.minute(minute);
889                 end.second(second);
890                 this.endDate = end;
891                 if (this.singleDatePicker)
892                     this.startDate = end.clone();
893                 this.rightCalendar.month.hour(hour).minute(minute).second(second);
894             }
895
896             this.updateView();
897             this.updateCalendars();
898         },
899
900         updateCalendars: function () {
901             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');
902             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');
903             this.container.find('.calendar.left').empty().html(this.renderCalendar(this.leftCalendar.calendar, this.startDate, this.minDate, this.maxDate, 'left'));
904             this.container.find('.calendar.right').empty().html(this.renderCalendar(this.rightCalendar.calendar, this.endDate, this.singleDatePicker ? this.minDate : this.startDate, this.maxDate, 'right'));
905
906             this.container.find('.ranges li').removeClass('active');
907             var customRange = true;
908             var i = 0;
909             for (var range in this.ranges) {
910                 if (this.timePicker) {
911                     if (this.startDate.isSame(this.ranges[range][0]) && this.endDate.isSame(this.ranges[range][1])) {
912                         customRange = false;
913                         this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')')
914                             .addClass('active').html();
915                     }
916                 } else {
917                     //ignore times when comparing dates if time picker is not enabled
918                     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')) {
919                         customRange = false;
920                         this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')')
921                             .addClass('active').html();
922                     }
923                 }
924                 i++;
925             }
926             if (customRange) {
927                 this.chosenLabel = this.container.find('.ranges li:last').addClass('active').html();
928                 this.showCalendars();
929             }
930         },
931
932         buildCalendar: function (month, year, hour, minute, second, side) {
933             var daysInMonth = moment([year, month]).daysInMonth();
934             var firstDay = moment([year, month, 1]);
935             var lastDay = moment([year, month, daysInMonth]);
936             var lastMonth = moment(firstDay).subtract(1, 'month').month();
937             var lastYear = moment(firstDay).subtract(1, 'month').year();
938
939             var daysInLastMonth = moment([lastYear, lastMonth]).daysInMonth();
940
941             var dayOfWeek = firstDay.day();
942
943             var i;
944
945             //initialize a 6 rows x 7 columns array for the calendar
946             var calendar = [];
947             calendar.firstDay = firstDay;
948             calendar.lastDay = lastDay;
949
950             for (i = 0; i < 6; i++) {
951                 calendar[i] = [];
952             }
953
954             //populate the calendar with date objects
955             var startDay = daysInLastMonth - dayOfWeek + this.locale.firstDay + 1;
956             if (startDay > daysInLastMonth)
957                 startDay -= 7;
958
959             if (dayOfWeek == this.locale.firstDay)
960                 startDay = daysInLastMonth - 6;
961
962             var curDate = moment([lastYear, lastMonth, startDay, 12, minute, second]).zone(this.timeZone);
963
964             var col, row;
965             for (i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add(24, 'hour')) {
966                 if (i > 0 && col % 7 === 0) {
967                     col = 0;
968                     row++;
969                 }
970                 calendar[row][col] = curDate.clone().hour(hour);
971                 curDate.hour(12);
972
973                 if (this.minDate && calendar[row][col].format('YYYY-MM-DD') == this.minDate.format('YYYY-MM-DD') && calendar[row][col].isBefore(this.minDate) && side == 'left') {
974                     calendar[row][col] = this.minDate.clone();
975                 }
976
977                 if (this.maxDate && calendar[row][col].format('YYYY-MM-DD') == this.maxDate.format('YYYY-MM-DD') && calendar[row][col].isAfter(this.maxDate) && side == 'right') {
978                     calendar[row][col] = this.maxDate.clone();
979                 }
980
981             }
982
983             return calendar;
984         },
985
986         renderDropdowns: function (selected, minDate, maxDate) {
987             var currentMonth = selected.month();
988             var currentYear = selected.year();
989             var maxYear = (maxDate && maxDate.year()) || (currentYear + 5);
990             var minYear = (minDate && minDate.year()) || (currentYear - 50);
991
992             var monthHtml = '<select class="monthselect">';
993             var inMinYear = currentYear == minYear;
994             var inMaxYear = currentYear == maxYear;
995
996             for (var m = 0; m < 12; m++) {
997                 if ((!inMinYear || m >= minDate.month()) && (!inMaxYear || m <= maxDate.month())) {
998                     monthHtml += "<option value='" + m + "'" +
999                         (m === currentMonth ? " selected='selected'" : "") +
1000                         ">" + this.locale.monthNames[m] + "</option>";
1001                 }
1002             }
1003             monthHtml += "</select>";
1004
1005             var yearHtml = '<select class="yearselect">';
1006
1007             for (var y = minYear; y <= maxYear; y++) {
1008                 yearHtml += '<option value="' + y + '"' +
1009                     (y === currentYear ? ' selected="selected"' : '') +
1010                     '>' + y + '</option>';
1011             }
1012
1013             yearHtml += '</select>';
1014
1015             return monthHtml + yearHtml;
1016         },
1017
1018         renderCalendar: function (calendar, selected, minDate, maxDate, side) {
1019
1020             var html = '<div class="calendar-date">';
1021             html += '<table class="table-condensed">';
1022             html += '<thead>';
1023             html += '<tr>';
1024
1025             // add empty cell for week number
1026             if (this.showWeekNumbers)
1027                 html += '<th></th>';
1028
1029             if (!minDate || minDate.isBefore(calendar.firstDay)) {
1030                 html += '<th class="prev available"><i class="fa fa-arrow-left icon-arrow-left glyphicon glyphicon-arrow-left"></i></th>';
1031             } else {
1032                 html += '<th></th>';
1033             }
1034
1035             var dateHtml = this.locale.monthNames[calendar[1][1].month()] + calendar[1][1].format(" YYYY");
1036
1037             if (this.showDropdowns) {
1038                 dateHtml = this.renderDropdowns(calendar[1][1], minDate, maxDate);
1039             }
1040
1041             html += '<th colspan="5" class="month">' + dateHtml + '</th>';
1042             if (!maxDate || maxDate.isAfter(calendar.lastDay)) {
1043                 html += '<th class="next available"><i class="fa fa-arrow-right icon-arrow-right glyphicon glyphicon-arrow-right"></i></th>';
1044             } else {
1045                 html += '<th></th>';
1046             }
1047
1048             html += '</tr>';
1049             html += '<tr>';
1050
1051             // add week number label
1052             if (this.showWeekNumbers)
1053                 html += '<th class="week">' + this.locale.weekLabel + '</th>';
1054
1055             $.each(this.locale.daysOfWeek, function (index, dayOfWeek) {
1056                 html += '<th>' + dayOfWeek + '</th>';
1057             });
1058
1059             html += '</tr>';
1060             html += '</thead>';
1061             html += '<tbody>';
1062
1063             for (var row = 0; row < 6; row++) {
1064                 html += '<tr>';
1065
1066                 // add week number
1067                 if (this.showWeekNumbers)
1068                     html += '<td class="week">' + calendar[row][0].week() + '</td>';
1069
1070                 for (var col = 0; col < 7; col++) {
1071                     var cname = 'available ';
1072                     cname += (calendar[row][col].month() == calendar[1][1].month()) ? '' : 'off';
1073
1074                     if ((minDate && calendar[row][col].isBefore(minDate, 'day')) || (maxDate && calendar[row][col].isAfter(maxDate, 'day'))) {
1075                         cname = ' off disabled ';
1076                     } else if (calendar[row][col].format('YYYY-MM-DD') == selected.format('YYYY-MM-DD')) {
1077                         cname += ' active ';
1078                         if (calendar[row][col].format('YYYY-MM-DD') == this.startDate.format('YYYY-MM-DD')) {
1079                             cname += ' start-date ';
1080                         }
1081                         if (calendar[row][col].format('YYYY-MM-DD') == this.endDate.format('YYYY-MM-DD')) {
1082                             cname += ' end-date ';
1083                         }
1084                     } else if (calendar[row][col] >= this.startDate && calendar[row][col] <= this.endDate) {
1085                         cname += ' in-range ';
1086                         if (calendar[row][col].isSame(this.startDate)) { cname += ' start-date '; }
1087                         if (calendar[row][col].isSame(this.endDate)) { cname += ' end-date '; }
1088                     }
1089
1090                     var title = 'r' + row + 'c' + col;
1091                     html += '<td class="' + cname.replace(/\s+/g, ' ').replace(/^\s?(.*?)\s?$/, '$1') + '" data-title="' + title + '">' + calendar[row][col].date() + '</td>';
1092                 }
1093                 html += '</tr>';
1094             }
1095
1096             html += '</tbody>';
1097             html += '</table>';
1098             html += '</div>';
1099
1100             var i;
1101             if (this.timePicker) {
1102
1103                 html += '<div class="calendar-time">';
1104                 html += '<select class="hourselect">';
1105
1106                 // Disallow selections before the minDate or after the maxDate
1107                 var min_hour = 0;
1108                 var max_hour = 23;
1109
1110                 if (minDate && (side == 'left' || this.singleDatePicker) && selected.format('YYYY-MM-DD') == minDate.format('YYYY-MM-DD')) {
1111                     min_hour = minDate.hour();
1112                     if (selected.hour() < min_hour)
1113                         selected.hour(min_hour);
1114                     if (this.timePicker12Hour && min_hour >= 12 && selected.hour() >= 12)
1115                         min_hour -= 12;
1116                     if (this.timePicker12Hour && min_hour == 12)
1117                         min_hour = 1;
1118                 }
1119
1120                 if (maxDate && (side == 'right' || this.singleDatePicker) && selected.format('YYYY-MM-DD') == maxDate.format('YYYY-MM-DD')) {
1121                     max_hour = maxDate.hour();
1122                     if (selected.hour() > max_hour)
1123                         selected.hour(max_hour);
1124                     if (this.timePicker12Hour && max_hour >= 12 && selected.hour() >= 12)
1125                         max_hour -= 12;
1126                 }
1127
1128                 var start = 0;
1129                 var end = 23;
1130                 var selected_hour = selected.hour();
1131                 if (this.timePicker12Hour) {
1132                     start = 1;
1133                     end = 12;
1134                     if (selected_hour >= 12)
1135                         selected_hour -= 12;
1136                     if (selected_hour === 0)
1137                         selected_hour = 12;
1138                 }
1139
1140                 for (i = start; i <= end; i++) {
1141
1142                     if (i == selected_hour) {
1143                         html += '<option value="' + i + '" selected="selected">' + i + '</option>';
1144                     } else if (i < min_hour || i > max_hour) {
1145                         html += '<option value="' + i + '" disabled="disabled" class="disabled">' + i + '</option>';
1146                     } else {
1147                         html += '<option value="' + i + '">' + i + '</option>';
1148                     }
1149                 }
1150
1151                 html += '</select> : ';
1152
1153                 html += '<select class="minuteselect">';
1154
1155                 // Disallow selections before the minDate or after the maxDate
1156                 var min_minute = 0;
1157                 var max_minute = 59;
1158
1159                 if (minDate && (side == 'left' || this.singleDatePicker) && selected.format('YYYY-MM-DD h A') == minDate.format('YYYY-MM-DD h A')) {
1160                     min_minute = minDate.minute();
1161                     if (selected.minute() < min_minute)
1162                         selected.minute(min_minute);
1163                 }
1164
1165                 if (maxDate && (side == 'right' || this.singleDatePicker) && selected.format('YYYY-MM-DD h A') == maxDate.format('YYYY-MM-DD h A')) {
1166                     max_minute = maxDate.minute();
1167                     if (selected.minute() > max_minute)
1168                         selected.minute(max_minute);
1169                 }
1170
1171                 for (i = 0; i < 60; i += this.timePickerIncrement) {
1172                     var num = i;
1173                     if (num < 10)
1174                         num = '0' + num;
1175                     if (i == selected.minute()) {
1176                         html += '<option value="' + i + '" selected="selected">' + num + '</option>';
1177                     } else if (i < min_minute || i > max_minute) {
1178                         html += '<option value="' + i + '" disabled="disabled" class="disabled">' + num + '</option>';
1179                     } else {
1180                         html += '<option value="' + i + '">' + num + '</option>';
1181                     }
1182                 }
1183
1184                 html += '</select> ';
1185
1186                 if (this.timePickerSeconds) {
1187                     html += ': <select class="secondselect">';
1188
1189                     for (i = 0; i < 60; i += this.timePickerIncrement) {
1190                         var num = i;
1191                         if (num < 10)
1192                             num = '0' + num;
1193                         if (i == selected.second()) {
1194                             html += '<option value="' + i + '" selected="selected">' + num + '</option>';
1195                         } else {
1196                             html += '<option value="' + i + '">' + num + '</option>';
1197                         }
1198                     }
1199
1200                     html += '</select>';
1201                 }
1202
1203                 if (this.timePicker12Hour) {
1204                     html += '<select class="ampmselect">';
1205
1206                     // Disallow selection before the minDate or after the maxDate
1207                     var am_html = '';
1208                     var pm_html = '';
1209
1210                     if (minDate && (side == 'left' || this.singleDatePicker) && selected.format('YYYY-MM-DD') == minDate.format('YYYY-MM-DD') && minDate.hour() >= 12) {
1211                         am_html = ' disabled="disabled" class="disabled"';
1212                     }
1213
1214                     if (maxDate && (side == 'right' || this.singleDatePicker) && selected.format('YYYY-MM-DD') == maxDate.format('YYYY-MM-DD') && maxDate.hour() < 12) {
1215                         pm_html = ' disabled="disabled" class="disabled"';
1216                     }
1217
1218                     if (selected.hour() >= 12) {
1219                         html += '<option value="AM"' + am_html + '>AM</option><option value="PM" selected="selected"' + pm_html + '>PM</option>';
1220                     } else {
1221                         html += '<option value="AM" selected="selected"' + am_html + '>AM</option><option value="PM"' + pm_html + '>PM</option>';
1222                     }
1223                     html += '</select>';
1224                 }
1225
1226                 html += '</div>';
1227
1228             }
1229
1230             return html;
1231
1232         },
1233
1234         remove: function() {
1235
1236             this.container.remove();
1237             this.element.off('.daterangepicker');
1238             this.element.removeData('daterangepicker');
1239
1240         }
1241
1242     };
1243
1244     $.fn.daterangepicker = function (options, cb) {
1245         this.each(function () {
1246             var el = $(this);
1247             if (el.data('daterangepicker'))
1248                 el.data('daterangepicker').remove();
1249             el.data('daterangepicker', new DateRangePicker(el, options, cb));
1250         });
1251         return this;
1252     };
1253
1254 }));