github.com/apremalal/vamps-core@v1.0.1-0.20161221121535-d430b56ec174/server/webapps/app/base/plugins/bootstrap-daterangepicker/daterangepicker.js (about) 1 /** 2 * @version: 1.3.13 3 * @author: Dan Grossman http://www.dangrossman.info/ 4 * @date: 2014-09-04 5 * @copyright: Copyright (c) 2012-2014 Dan Grossman. All rights reserved. 6 * @license: Licensed under Apache License v2.0. See http://www.apache.org/licenses/LICENSE-2.0 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 left"></div>' + 50 '<div class="calendar 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> ' + 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.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.minDate = false; 133 this.maxDate = false; 134 this.dateLimit = false; 135 136 this.showDropdowns = false; 137 this.showWeekNumbers = false; 138 this.timePicker = false; 139 this.timePickerIncrement = 30; 140 this.timePicker12Hour = true; 141 this.singleDatePicker = false; 142 this.ranges = {}; 143 144 this.opens = 'right'; 145 if (this.element.hasClass('pull-right')) 146 this.opens = 'left'; 147 148 this.buttonClasses = ['btn', 'btn-small btn-sm']; 149 this.applyClass = 'btn-success'; 150 this.cancelClass = 'btn-default'; 151 152 this.format = 'MM/DD/YYYY'; 153 this.separator = ' - '; 154 155 this.locale = { 156 applyLabel: 'Apply', 157 cancelLabel: 'Cancel', 158 fromLabel: 'From', 159 toLabel: 'To', 160 weekLabel: 'W', 161 customRangeLabel: 'Custom Range', 162 daysOfWeek: moment.weekdaysMin(), 163 monthNames: moment.monthsShort(), 164 firstDay: moment.localeData()._week.dow 165 }; 166 167 this.cb = function () { }; 168 169 if (typeof options.format === 'string') 170 this.format = options.format; 171 172 if (typeof options.separator === 'string') 173 this.separator = options.separator; 174 175 if (typeof options.startDate === 'string') 176 this.startDate = moment(options.startDate, this.format); 177 178 if (typeof options.endDate === 'string') 179 this.endDate = moment(options.endDate, this.format); 180 181 if (typeof options.minDate === 'string') 182 this.minDate = moment(options.minDate, this.format); 183 184 if (typeof options.maxDate === 'string') 185 this.maxDate = moment(options.maxDate, this.format); 186 187 if (typeof options.startDate === 'object') 188 this.startDate = moment(options.startDate); 189 190 if (typeof options.endDate === 'object') 191 this.endDate = moment(options.endDate); 192 193 if (typeof options.minDate === 'object') 194 this.minDate = moment(options.minDate); 195 196 if (typeof options.maxDate === 'object') 197 this.maxDate = moment(options.maxDate); 198 199 if (typeof options.applyClass === 'string') 200 this.applyClass = options.applyClass; 201 202 if (typeof options.cancelClass === 'string') 203 this.cancelClass = options.cancelClass; 204 205 if (typeof options.dateLimit === 'object') 206 this.dateLimit = options.dateLimit; 207 208 if (typeof options.locale === 'object') { 209 210 if (typeof options.locale.daysOfWeek === 'object') { 211 // Create a copy of daysOfWeek to avoid modification of original 212 // options object for reusability in multiple daterangepicker instances 213 this.locale.daysOfWeek = options.locale.daysOfWeek.slice(); 214 } 215 216 if (typeof options.locale.monthNames === 'object') { 217 this.locale.monthNames = options.locale.monthNames.slice(); 218 } 219 220 if (typeof options.locale.firstDay === 'number') { 221 this.locale.firstDay = options.locale.firstDay; 222 } 223 224 if (typeof options.locale.applyLabel === 'string') { 225 this.locale.applyLabel = options.locale.applyLabel; 226 } 227 228 if (typeof options.locale.cancelLabel === 'string') { 229 this.locale.cancelLabel = options.locale.cancelLabel; 230 } 231 232 if (typeof options.locale.fromLabel === 'string') { 233 this.locale.fromLabel = options.locale.fromLabel; 234 } 235 236 if (typeof options.locale.toLabel === 'string') { 237 this.locale.toLabel = options.locale.toLabel; 238 } 239 240 if (typeof options.locale.weekLabel === 'string') { 241 this.locale.weekLabel = options.locale.weekLabel; 242 } 243 244 if (typeof options.locale.customRangeLabel === 'string') { 245 this.locale.customRangeLabel = options.locale.customRangeLabel; 246 } 247 } 248 249 if (typeof options.opens === 'string') 250 this.opens = options.opens; 251 252 if (typeof options.showWeekNumbers === 'boolean') { 253 this.showWeekNumbers = options.showWeekNumbers; 254 } 255 256 if (typeof options.buttonClasses === 'string') { 257 this.buttonClasses = [options.buttonClasses]; 258 } 259 260 if (typeof options.buttonClasses === 'object') { 261 this.buttonClasses = options.buttonClasses; 262 } 263 264 if (typeof options.showDropdowns === 'boolean') { 265 this.showDropdowns = options.showDropdowns; 266 } 267 268 if (typeof options.singleDatePicker === 'boolean') { 269 this.singleDatePicker = options.singleDatePicker; 270 if (this.singleDatePicker) { 271 this.endDate = this.startDate.clone(); 272 } 273 } 274 275 if (typeof options.timePicker === 'boolean') { 276 this.timePicker = options.timePicker; 277 } 278 279 if (typeof options.timePickerIncrement === 'number') { 280 this.timePickerIncrement = options.timePickerIncrement; 281 } 282 283 if (typeof options.timePicker12Hour === 'boolean') { 284 this.timePicker12Hour = options.timePicker12Hour; 285 } 286 287 // update day names order to firstDay 288 if (this.locale.firstDay != 0) { 289 var iterator = this.locale.firstDay; 290 while (iterator > 0) { 291 this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift()); 292 iterator--; 293 } 294 } 295 296 var start, end, range; 297 298 //if no start/end dates set, check if an input element contains initial values 299 if (typeof options.startDate === 'undefined' && typeof options.endDate === 'undefined') { 300 if ($(this.element).is('input[type=text]')) { 301 var val = $(this.element).val(); 302 var split = val.split(this.separator); 303 start = end = null; 304 if (split.length == 2) { 305 start = moment(split[0], this.format); 306 end = moment(split[1], this.format); 307 } else if (this.singleDatePicker) { 308 start = moment(val, this.format); 309 end = moment(val, this.format); 310 } 311 if (start !== null && end !== null) { 312 this.startDate = start; 313 this.endDate = end; 314 } 315 } 316 } 317 318 if (typeof options.ranges === 'object') { 319 for (range in options.ranges) { 320 321 start = moment(options.ranges[range][0]); 322 end = moment(options.ranges[range][1]); 323 324 // If we have a min/max date set, bound this range 325 // to it, but only if it would otherwise fall 326 // outside of the min/max. 327 if (this.minDate && start.isBefore(this.minDate)) 328 start = moment(this.minDate); 329 330 if (this.maxDate && end.isAfter(this.maxDate)) 331 end = moment(this.maxDate); 332 333 // If the end of the range is before the minimum (if min is set) OR 334 // the start of the range is after the max (also if set) don't display this 335 // range option. 336 if ((this.minDate && end.isBefore(this.minDate)) || (this.maxDate && start.isAfter(this.maxDate))) { 337 continue; 338 } 339 340 this.ranges[range] = [start, end]; 341 } 342 343 var list = '<ul>'; 344 for (range in this.ranges) { 345 list += '<li>' + range + '</li>'; 346 } 347 list += '<li>' + this.locale.customRangeLabel + '</li>'; 348 list += '</ul>'; 349 this.container.find('.ranges ul').remove(); 350 this.container.find('.ranges').prepend(list); 351 } 352 353 if (typeof callback === 'function') { 354 this.cb = callback; 355 } 356 357 if (!this.timePicker) { 358 this.startDate = this.startDate.startOf('day'); 359 this.endDate = this.endDate.endOf('day'); 360 } 361 362 if (this.singleDatePicker) { 363 this.opens = 'right'; 364 this.container.addClass('single'); 365 this.container.find('.calendar.right').show(); 366 this.container.find('.calendar.left').hide(); 367 if (!this.timePicker) { 368 this.container.find('.ranges').hide(); 369 } else { 370 this.container.find('.ranges .daterangepicker_start_input, .ranges .daterangepicker_end_input').hide(); 371 } 372 if (!this.container.find('.calendar.right').hasClass('single')) 373 this.container.find('.calendar.right').addClass('single'); 374 } else { 375 this.container.removeClass('single'); 376 this.container.find('.calendar.right').removeClass('single'); 377 this.container.find('.ranges').show(); 378 } 379 380 this.oldStartDate = this.startDate.clone(); 381 this.oldEndDate = this.endDate.clone(); 382 this.oldChosenLabel = this.chosenLabel; 383 384 this.leftCalendar = { 385 month: moment([this.startDate.year(), this.startDate.month(), 1, this.startDate.hour(), this.startDate.minute()]), 386 calendar: [] 387 }; 388 389 this.rightCalendar = { 390 month: moment([this.endDate.year(), this.endDate.month(), 1, this.endDate.hour(), this.endDate.minute()]), 391 calendar: [] 392 }; 393 394 if (this.opens == 'right') { 395 //swap calendar positions 396 var left = this.container.find('.calendar.left'); 397 var right = this.container.find('.calendar.right'); 398 399 if (right.hasClass('single')) { 400 right.removeClass('single'); 401 left.addClass('single'); 402 } 403 404 left.removeClass('left').addClass('right'); 405 right.removeClass('right').addClass('left'); 406 407 if (this.singleDatePicker) { 408 left.show(); 409 right.hide(); 410 } 411 } 412 413 if (typeof options.ranges === 'undefined' && !this.singleDatePicker) { 414 this.container.addClass('show-calendar'); 415 } 416 417 this.container.addClass('opens' + this.opens); 418 419 this.updateView(); 420 this.updateCalendars(); 421 422 }, 423 424 setStartDate: function(startDate) { 425 if (typeof startDate === 'string') 426 this.startDate = moment(startDate, this.format); 427 428 if (typeof startDate === 'object') 429 this.startDate = moment(startDate); 430 431 if (!this.timePicker) 432 this.startDate = this.startDate.startOf('day'); 433 434 this.oldStartDate = this.startDate.clone(); 435 436 this.updateView(); 437 this.updateCalendars(); 438 this.updateInputText(); 439 }, 440 441 setEndDate: function(endDate) { 442 if (typeof endDate === 'string') 443 this.endDate = moment(endDate, this.format); 444 445 if (typeof endDate === 'object') 446 this.endDate = moment(endDate); 447 448 if (!this.timePicker) 449 this.endDate = this.endDate.endOf('day'); 450 451 this.oldEndDate = this.endDate.clone(); 452 453 this.updateView(); 454 this.updateCalendars(); 455 this.updateInputText(); 456 }, 457 458 updateView: function () { 459 this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()).hour(this.startDate.hour()).minute(this.startDate.minute()); 460 this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()).hour(this.endDate.hour()).minute(this.endDate.minute()); 461 this.updateFormInputs(); 462 }, 463 464 updateFormInputs: function () { 465 this.container.find('input[name=daterangepicker_start]').val(this.startDate.format(this.format)); 466 this.container.find('input[name=daterangepicker_end]').val(this.endDate.format(this.format)); 467 468 if (this.startDate.isSame(this.endDate) || this.startDate.isBefore(this.endDate)) { 469 this.container.find('button.applyBtn').removeAttr('disabled'); 470 } else { 471 this.container.find('button.applyBtn').attr('disabled', 'disabled'); 472 } 473 }, 474 475 updateFromControl: function () { 476 if (!this.element.is('input')) return; 477 if (!this.element.val().length) return; 478 479 var dateString = this.element.val().split(this.separator), 480 start = null, 481 end = null; 482 483 if(dateString.length === 2) { 484 start = moment(dateString[0], this.format); 485 end = moment(dateString[1], this.format); 486 } 487 488 if (this.singleDatePicker || start === null || end === null) { 489 start = moment(this.element.val(), this.format); 490 end = start; 491 } 492 493 if (end.isBefore(start)) return; 494 495 this.oldStartDate = this.startDate.clone(); 496 this.oldEndDate = this.endDate.clone(); 497 498 this.startDate = start; 499 this.endDate = end; 500 501 if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate)) 502 this.notify(); 503 504 this.updateCalendars(); 505 }, 506 507 notify: function () { 508 this.updateView(); 509 this.cb(this.startDate, this.endDate, this.chosenLabel); 510 }, 511 512 move: function () { 513 var parentOffset = { top: 0, left: 0 }; 514 var parentRightEdge = $(window).width(); 515 if (!this.parentEl.is('body')) { 516 parentOffset = { 517 top: this.parentEl.offset().top - this.parentEl.scrollTop(), 518 left: this.parentEl.offset().left - this.parentEl.scrollLeft() 519 }; 520 parentRightEdge = this.parentEl[0].clientWidth + this.parentEl.offset().left; 521 } 522 523 if (this.opens == 'left') { 524 this.container.css({ 525 top: this.element.offset().top + this.element.outerHeight() - parentOffset.top, 526 right: parentRightEdge - this.element.offset().left - this.element.outerWidth(), 527 left: 'auto' 528 }); 529 if (this.container.offset().left < 0) { 530 this.container.css({ 531 right: 'auto', 532 left: 9 533 }); 534 } 535 } else { 536 this.container.css({ 537 top: this.element.offset().top + this.element.outerHeight() - parentOffset.top, 538 left: this.element.offset().left - parentOffset.left, 539 right: 'auto' 540 }); 541 if (this.container.offset().left + this.container.outerWidth() > $(window).width()) { 542 this.container.css({ 543 left: 'auto', 544 right: 0 545 }); 546 } 547 } 548 }, 549 550 toggle: function (e) { 551 if (this.element.hasClass('active')) { 552 this.hide(); 553 } else { 554 this.show(); 555 } 556 }, 557 558 show: function (e) { 559 if (this.isShowing) return; 560 561 this.element.addClass('active'); 562 this.container.show(); 563 this.move(); 564 565 // Create a click proxy that is private to this instance of datepicker, for unbinding 566 this._outsideClickProxy = $.proxy(function (e) { this.outsideClick(e); }, this); 567 // Bind global datepicker mousedown for hiding and 568 $(document) 569 .on('mousedown.daterangepicker', this._outsideClickProxy) 570 // also explicitly play nice with Bootstrap dropdowns, which stopPropagation when clicking them 571 .on('click.daterangepicker', '[data-toggle=dropdown]', this._outsideClickProxy) 572 // and also close when focus changes to outside the picker (eg. tabbing between controls) 573 .on('focusin.daterangepicker', this._outsideClickProxy); 574 575 this.isShowing = true; 576 this.element.trigger('show.daterangepicker', this); 577 }, 578 579 outsideClick: function (e) { 580 var target = $(e.target); 581 // if the page is clicked anywhere except within the daterangerpicker/button 582 // itself then call this.hide() 583 if ( 584 target.closest(this.element).length || 585 target.closest(this.container).length || 586 target.closest('.calendar-date').length 587 ) return; 588 this.hide(); 589 }, 590 591 hide: function (e) { 592 if (!this.isShowing) return; 593 594 $(document) 595 .off('mousedown.daterangepicker') 596 .off('click.daterangepicker', '[data-toggle=dropdown]') 597 .off('focusin.daterangepicker'); 598 599 this.element.removeClass('active'); 600 this.container.hide(); 601 602 if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate)) 603 this.notify(); 604 605 this.oldStartDate = this.startDate.clone(); 606 this.oldEndDate = this.endDate.clone(); 607 608 this.isShowing = false; 609 this.element.trigger('hide.daterangepicker', this); 610 }, 611 612 enterRange: function (e) { 613 // mouse pointer has entered a range label 614 var label = e.target.innerHTML; 615 if (label == this.locale.customRangeLabel) { 616 this.updateView(); 617 } else { 618 var dates = this.ranges[label]; 619 this.container.find('input[name=daterangepicker_start]').val(dates[0].format(this.format)); 620 this.container.find('input[name=daterangepicker_end]').val(dates[1].format(this.format)); 621 } 622 }, 623 624 showCalendars: function() { 625 this.container.addClass('show-calendar'); 626 this.move(); 627 this.element.trigger('showCalendar.daterangepicker', this); 628 }, 629 630 hideCalendars: function() { 631 this.container.removeClass('show-calendar'); 632 this.element.trigger('hideCalendar.daterangepicker', this); 633 }, 634 635 // when a date is typed into the start to end date textboxes 636 inputsChanged: function (e) { 637 var el = $(e.target); 638 var date = moment(el.val(), this.format); 639 if (!date.isValid()) return; 640 641 var startDate, endDate; 642 if (el.attr('name') === 'daterangepicker_start') { 643 startDate = date; 644 endDate = this.endDate; 645 } else { 646 startDate = this.startDate; 647 endDate = date; 648 } 649 this.setCustomDates(startDate, endDate); 650 }, 651 652 inputsKeydown: function(e) { 653 if (e.keyCode === 13) { 654 this.inputsChanged(e); 655 this.notify(); 656 } 657 }, 658 659 updateInputText: function() { 660 if (this.element.is('input') && !this.singleDatePicker) { 661 this.element.val(this.startDate.format(this.format) + this.separator + this.endDate.format(this.format)); 662 } else if (this.element.is('input')) { 663 this.element.val(this.endDate.format(this.format)); 664 } 665 }, 666 667 clickRange: function (e) { 668 var label = e.target.innerHTML; 669 this.chosenLabel = label; 670 if (label == this.locale.customRangeLabel) { 671 this.showCalendars(); 672 } else { 673 var dates = this.ranges[label]; 674 675 this.startDate = dates[0]; 676 this.endDate = dates[1]; 677 678 if (!this.timePicker) { 679 this.startDate.startOf('day'); 680 this.endDate.endOf('day'); 681 } 682 683 this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()).hour(this.startDate.hour()).minute(this.startDate.minute()); 684 this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()).hour(this.endDate.hour()).minute(this.endDate.minute()); 685 this.updateCalendars(); 686 687 this.updateInputText(); 688 689 this.hideCalendars(); 690 this.hide(); 691 this.element.trigger('apply.daterangepicker', this); 692 } 693 }, 694 695 clickPrev: function (e) { 696 var cal = $(e.target).parents('.calendar'); 697 if (cal.hasClass('left')) { 698 this.leftCalendar.month.subtract(1, 'month'); 699 } else { 700 this.rightCalendar.month.subtract(1, 'month'); 701 } 702 this.updateCalendars(); 703 }, 704 705 clickNext: function (e) { 706 var cal = $(e.target).parents('.calendar'); 707 if (cal.hasClass('left')) { 708 this.leftCalendar.month.add(1, 'month'); 709 } else { 710 this.rightCalendar.month.add(1, 'month'); 711 } 712 this.updateCalendars(); 713 }, 714 715 hoverDate: function (e) { 716 var title = $(e.target).attr('data-title'); 717 var row = title.substr(1, 1); 718 var col = title.substr(3, 1); 719 var cal = $(e.target).parents('.calendar'); 720 721 if (cal.hasClass('left')) { 722 this.container.find('input[name=daterangepicker_start]').val(this.leftCalendar.calendar[row][col].format(this.format)); 723 } else { 724 this.container.find('input[name=daterangepicker_end]').val(this.rightCalendar.calendar[row][col].format(this.format)); 725 } 726 }, 727 728 setCustomDates: function(startDate, endDate) { 729 this.chosenLabel = this.locale.customRangeLabel; 730 if (startDate.isAfter(endDate)) { 731 var difference = this.endDate.diff(this.startDate); 732 endDate = moment(startDate).add(difference, 'ms'); 733 } 734 this.startDate = startDate; 735 this.endDate = endDate; 736 737 this.updateView(); 738 this.updateCalendars(); 739 }, 740 741 clickDate: function (e) { 742 var title = $(e.target).attr('data-title'); 743 var row = title.substr(1, 1); 744 var col = title.substr(3, 1); 745 var cal = $(e.target).parents('.calendar'); 746 747 var startDate, endDate; 748 if (cal.hasClass('left')) { 749 startDate = this.leftCalendar.calendar[row][col]; 750 endDate = this.endDate; 751 if (typeof this.dateLimit === 'object') { 752 var maxDate = moment(startDate).add(this.dateLimit).startOf('day'); 753 if (endDate.isAfter(maxDate)) { 754 endDate = maxDate; 755 } 756 } 757 } else { 758 startDate = this.startDate; 759 endDate = this.rightCalendar.calendar[row][col]; 760 if (typeof this.dateLimit === 'object') { 761 var minDate = moment(endDate).subtract(this.dateLimit).startOf('day'); 762 if (startDate.isBefore(minDate)) { 763 startDate = minDate; 764 } 765 } 766 } 767 768 if (this.singleDatePicker && cal.hasClass('left')) { 769 endDate = startDate.clone(); 770 } else if (this.singleDatePicker && cal.hasClass('right')) { 771 startDate = endDate.clone(); 772 } 773 774 cal.find('td').removeClass('active'); 775 776 $(e.target).addClass('active'); 777 778 this.setCustomDates(startDate, endDate); 779 780 if (!this.timePicker) 781 endDate.endOf('day'); 782 783 if (this.singleDatePicker && !this.timePicker) 784 this.clickApply(); 785 }, 786 787 clickApply: function (e) { 788 this.updateInputText(); 789 this.hide(); 790 this.element.trigger('apply.daterangepicker', this); 791 }, 792 793 clickCancel: function (e) { 794 this.startDate = this.oldStartDate; 795 this.endDate = this.oldEndDate; 796 this.chosenLabel = this.oldChosenLabel; 797 this.updateView(); 798 this.updateCalendars(); 799 this.hide(); 800 this.element.trigger('cancel.daterangepicker', this); 801 }, 802 803 updateMonthYear: function (e) { 804 var isLeft = $(e.target).closest('.calendar').hasClass('left'), 805 leftOrRight = isLeft ? 'left' : 'right', 806 cal = this.container.find('.calendar.'+leftOrRight); 807 808 // Month must be Number for new moment versions 809 var month = parseInt(cal.find('.monthselect').val(), 10); 810 var year = cal.find('.yearselect').val(); 811 812 this[leftOrRight+'Calendar'].month.month(month).year(year); 813 this.updateCalendars(); 814 }, 815 816 updateTime: function(e) { 817 818 var cal = $(e.target).closest('.calendar'), 819 isLeft = cal.hasClass('left'); 820 821 var hour = parseInt(cal.find('.hourselect').val(), 10); 822 var minute = parseInt(cal.find('.minuteselect').val(), 10); 823 824 if (this.timePicker12Hour) { 825 var ampm = cal.find('.ampmselect').val(); 826 if (ampm === 'PM' && hour < 12) 827 hour += 12; 828 if (ampm === 'AM' && hour === 12) 829 hour = 0; 830 } 831 832 if (isLeft) { 833 var start = this.startDate.clone(); 834 start.hour(hour); 835 start.minute(minute); 836 this.startDate = start; 837 this.leftCalendar.month.hour(hour).minute(minute); 838 } else { 839 var end = this.endDate.clone(); 840 end.hour(hour); 841 end.minute(minute); 842 this.endDate = end; 843 this.rightCalendar.month.hour(hour).minute(minute); 844 } 845 846 this.updateCalendars(); 847 }, 848 849 updateCalendars: function () { 850 this.leftCalendar.calendar = this.buildCalendar(this.leftCalendar.month.month(), this.leftCalendar.month.year(), this.leftCalendar.month.hour(), this.leftCalendar.month.minute(), 'left'); 851 this.rightCalendar.calendar = this.buildCalendar(this.rightCalendar.month.month(), this.rightCalendar.month.year(), this.rightCalendar.month.hour(), this.rightCalendar.month.minute(), 'right'); 852 this.container.find('.calendar.left').empty().html(this.renderCalendar(this.leftCalendar.calendar, this.startDate, this.minDate, this.maxDate)); 853 this.container.find('.calendar.right').empty().html(this.renderCalendar(this.rightCalendar.calendar, this.endDate, this.singleDatePicker ? this.minDate : this.startDate, this.maxDate)); 854 855 this.container.find('.ranges li').removeClass('active'); 856 var customRange = true; 857 var i = 0; 858 for (var range in this.ranges) { 859 if (this.timePicker) { 860 if (this.startDate.isSame(this.ranges[range][0]) && this.endDate.isSame(this.ranges[range][1])) { 861 customRange = false; 862 this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')') 863 .addClass('active').html(); 864 } 865 } else { 866 //ignore times when comparing dates if time picker is not enabled 867 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')) { 868 customRange = false; 869 this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')') 870 .addClass('active').html(); 871 } 872 } 873 i++; 874 } 875 if (customRange) { 876 this.chosenLabel = this.container.find('.ranges li:last').addClass('active').html(); 877 this.showCalendars(); 878 } 879 }, 880 881 buildCalendar: function (month, year, hour, minute, side) { 882 var daysInMonth = moment([year, month]).daysInMonth(); 883 var firstDay = moment([year, month, 1]); 884 var lastDay = moment([year, month, daysInMonth]); 885 var lastMonth = moment(firstDay).subtract(1, 'month').month(); 886 var lastYear = moment(firstDay).subtract(1, 'month').year(); 887 888 var daysInLastMonth = moment([lastYear, lastMonth]).daysInMonth(); 889 890 var dayOfWeek = firstDay.day(); 891 892 var i; 893 894 //initialize a 6 rows x 7 columns array for the calendar 895 var calendar = []; 896 calendar.firstDay = firstDay; 897 calendar.lastDay = lastDay; 898 899 for (i = 0; i < 6; i++) { 900 calendar[i] = []; 901 } 902 903 //populate the calendar with date objects 904 var startDay = daysInLastMonth - dayOfWeek + this.locale.firstDay + 1; 905 if (startDay > daysInLastMonth) 906 startDay -= 7; 907 908 if (dayOfWeek == this.locale.firstDay) 909 startDay = daysInLastMonth - 6; 910 911 var curDate = moment([lastYear, lastMonth, startDay, 12, minute]); 912 var col, row; 913 for (i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add(24, 'hour')) { 914 if (i > 0 && col % 7 === 0) { 915 col = 0; 916 row++; 917 } 918 calendar[row][col] = curDate.clone().hour(hour); 919 curDate.hour(12); 920 } 921 922 return calendar; 923 }, 924 925 renderDropdowns: function (selected, minDate, maxDate) { 926 var currentMonth = selected.month(); 927 var monthHtml = '<select class="monthselect">'; 928 var inMinYear = false; 929 var inMaxYear = false; 930 931 for (var m = 0; m < 12; m++) { 932 if ((!inMinYear || m >= minDate.month()) && (!inMaxYear || m <= maxDate.month())) { 933 monthHtml += "<option value='" + m + "'" + 934 (m === currentMonth ? " selected='selected'" : "") + 935 ">" + this.locale.monthNames[m] + "</option>"; 936 } 937 } 938 monthHtml += "</select>"; 939 940 var currentYear = selected.year(); 941 var maxYear = (maxDate && maxDate.year()) || (currentYear + 5); 942 var minYear = (minDate && minDate.year()) || (currentYear - 50); 943 var yearHtml = '<select class="yearselect">'; 944 945 for (var y = minYear; y <= maxYear; y++) { 946 yearHtml += '<option value="' + y + '"' + 947 (y === currentYear ? ' selected="selected"' : '') + 948 '>' + y + '</option>'; 949 } 950 951 yearHtml += '</select>'; 952 953 return monthHtml + yearHtml; 954 }, 955 956 renderCalendar: function (calendar, selected, minDate, maxDate) { 957 958 var html = '<div class="calendar-date">'; 959 html += '<table class="table-condensed">'; 960 html += '<thead>'; 961 html += '<tr>'; 962 963 // add empty cell for week number 964 if (this.showWeekNumbers) 965 html += '<th></th>'; 966 967 if (!minDate || minDate.isBefore(calendar.firstDay)) { 968 html += '<th class="prev available"><i class="fa fa-angle-left"></i></th>'; 969 } else { 970 html += '<th></th>'; 971 } 972 973 var dateHtml = this.locale.monthNames[calendar[1][1].month()] + calendar[1][1].format(" YYYY"); 974 975 if (this.showDropdowns) { 976 dateHtml = this.renderDropdowns(calendar[1][1], minDate, maxDate); 977 } 978 979 html += '<th colspan="5" class="month">' + dateHtml + '</th>'; 980 if (!maxDate || maxDate.isAfter(calendar.lastDay)) { 981 html += '<th class="next available"><i class="fa fa-angle-right"></i></th>'; 982 } else { 983 html += '<th></th>'; 984 } 985 986 html += '</tr>'; 987 html += '<tr>'; 988 989 // add week number label 990 if (this.showWeekNumbers) 991 html += '<th class="week">' + this.locale.weekLabel + '</th>'; 992 993 $.each(this.locale.daysOfWeek, function (index, dayOfWeek) { 994 html += '<th>' + dayOfWeek + '</th>'; 995 }); 996 997 html += '</tr>'; 998 html += '</thead>'; 999 html += '<tbody>'; 1000 1001 for (var row = 0; row < 6; row++) { 1002 html += '<tr>'; 1003 1004 // add week number 1005 if (this.showWeekNumbers) 1006 html += '<td class="week">' + calendar[row][0].week() + '</td>'; 1007 1008 for (var col = 0; col < 7; col++) { 1009 var cname = 'available '; 1010 cname += (calendar[row][col].month() == calendar[1][1].month()) ? '' : 'off'; 1011 1012 if ((minDate && calendar[row][col].isBefore(minDate, 'day')) || (maxDate && calendar[row][col].isAfter(maxDate, 'day'))) { 1013 cname = ' off disabled '; 1014 } else if (calendar[row][col].format('YYYY-MM-DD') == selected.format('YYYY-MM-DD')) { 1015 cname += ' active '; 1016 if (calendar[row][col].format('YYYY-MM-DD') == this.startDate.format('YYYY-MM-DD')) { 1017 cname += ' start-date '; 1018 } 1019 if (calendar[row][col].format('YYYY-MM-DD') == this.endDate.format('YYYY-MM-DD')) { 1020 cname += ' end-date '; 1021 } 1022 } else if (calendar[row][col] >= this.startDate && calendar[row][col] <= this.endDate) { 1023 cname += ' in-range '; 1024 if (calendar[row][col].isSame(this.startDate)) { cname += ' start-date '; } 1025 if (calendar[row][col].isSame(this.endDate)) { cname += ' end-date '; } 1026 } 1027 1028 var title = 'r' + row + 'c' + col; 1029 html += '<td class="' + cname.replace(/\s+/g, ' ').replace(/^\s?(.*?)\s?$/, '$1') + '" data-title="' + title + '">' + calendar[row][col].date() + '</td>'; 1030 } 1031 html += '</tr>'; 1032 } 1033 1034 html += '</tbody>'; 1035 html += '</table>'; 1036 html += '</div>'; 1037 1038 var i; 1039 if (this.timePicker) { 1040 1041 html += '<div class="calendar-time">'; 1042 html += '<select class="hourselect">'; 1043 var start = 0; 1044 var end = 23; 1045 var selected_hour = selected.hour(); 1046 if (this.timePicker12Hour) { 1047 start = 1; 1048 end = 12; 1049 if (selected_hour >= 12) 1050 selected_hour -= 12; 1051 if (selected_hour === 0) 1052 selected_hour = 12; 1053 } 1054 1055 for (i = start; i <= end; i++) { 1056 if (i == selected_hour) { 1057 html += '<option value="' + i + '" selected="selected">' + i + '</option>'; 1058 } else { 1059 html += '<option value="' + i + '">' + i + '</option>'; 1060 } 1061 } 1062 1063 html += '</select> : '; 1064 1065 html += '<select class="minuteselect">'; 1066 1067 for (i = 0; i < 60; i += this.timePickerIncrement) { 1068 var num = i; 1069 if (num < 10) 1070 num = '0' + num; 1071 if (i == selected.minute()) { 1072 html += '<option value="' + i + '" selected="selected">' + num + '</option>'; 1073 } else { 1074 html += '<option value="' + i + '">' + num + '</option>'; 1075 } 1076 } 1077 1078 html += '</select> '; 1079 1080 if (this.timePicker12Hour) { 1081 html += '<select class="ampmselect">'; 1082 if (selected.hour() >= 12) { 1083 html += '<option value="AM">AM</option><option value="PM" selected="selected">PM</option>'; 1084 } else { 1085 html += '<option value="AM" selected="selected">AM</option><option value="PM">PM</option>'; 1086 } 1087 html += '</select>'; 1088 } 1089 1090 html += '</div>'; 1091 1092 } 1093 1094 return html; 1095 1096 }, 1097 1098 remove: function() { 1099 1100 this.container.remove(); 1101 this.element.off('.daterangepicker'); 1102 this.element.removeData('daterangepicker'); 1103 1104 } 1105 1106 }; 1107 1108 $.fn.daterangepicker = function (options, cb) { 1109 this.each(function () { 1110 var el = $(this); 1111 if (el.data('daterangepicker')) 1112 el.data('daterangepicker').remove(); 1113 el.data('daterangepicker', new DateRangePicker(el, options, cb)); 1114 }); 1115 return this; 1116 }; 1117 1118 }));