/** * @version: 1.0.1 * @author: Dan Grossman http://www.dangrossman.info/ * @date: 2012-08-20 * @copyright: Copyright (c) 2012 Dan Grossman. All rights reserved. * @license: Licensed under Apache License v2.0. See http://www.apache.org/licenses/LICENSE-2.0 * @website: http://www.improvely.com/ */ !function ($) { var DateRangePicker = function (element, options, cb) { var hasOptions = typeof options == 'object' var localeObject; //state this.startDate = Date.create('today'); this.endDate = Date.create('today'); this.minDate = false; this.maxDate = false; this.changed = false; this.cleared = false; this.ranges = {}; this.opens = 'right'; this.cb = function () { }; this.format = '{MM}/{dd}/{yyyy}'; this.separator = ' - '; this.showWeekNumbers = false; this.buttonClasses = ['btn-success']; this.locale = { applyLabel: 'Apply', clearLabel:"Clear", fromLabel: 'From', toLabel: 'To', weekLabel: 'W', customRangeLabel: 'Custom Range', daysOfWeek: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr','Sa'], monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], firstDay: 0 }; localeObject = this.locale; this.leftCalendar = { month: Date.create('today').set({ day: 1, month: this.startDate.getMonth(), year: this.startDate.getFullYear() }), calendar: Array() }; this.rightCalendar = { month: Date.create('today').set({ day: 1, month: this.endDate.getMonth(), year: this.endDate.getFullYear() }), calendar: Array() }; //element that triggered the date range picker this.element = $(element); if (this.element.hasClass('pull-right')) this.opens = 'left'; if (this.element.is('input')) { this.element.on({ click: $.proxy(this.show, this), focus: $.proxy(this.show, this) }); } else { this.element.on('click', $.proxy(this.show, this)); } if (hasOptions) { if(typeof options.locale == 'object') { $.each(localeObject, function (property, value) { localeObject[property] = options.locale[property] || value; }); } } var DRPTemplate = ''; this.container = $(DRPTemplate).appendTo('body'); if (hasOptions) { if (typeof options.format == 'string') this.format = options.format; if (typeof options.separator == 'string') this.separator = options.separator; if (typeof options.startDate == 'string') this.startDate = Date.create(options.startDate); if (typeof options.endDate == 'string') this.endDate = Date.create(options.endDate); if (typeof options.minDate == 'string') this.minDate = Date.create(options.minDate); if (typeof options.maxDate == 'string') this.maxDate = Date.create(options.maxDate); if (typeof options.startDate == 'object') this.startDate = options.startDate; if (typeof options.endDate == 'object') this.endDate = options.endDate; if (typeof options.minDate == 'object') this.minDate = options.minDate; if (typeof options.maxDate == 'object') this.maxDate = options.maxDate; if (typeof options.ranges == 'object') { for (var range in options.ranges) { var start = options.ranges[range][0]; var end = options.ranges[range][1]; if (typeof start == 'string') start = Date.create(start); if (typeof end == 'string') end = Date.create(end); // If we have a min/max date set, bound this range // to it, but only if it would otherwise fall // outside of the min/max. if (this.minDate && start < this.minDate) start = this.minDate; if (this.maxDate && end > this.maxDate) end = this.maxDate; // If the end of the range is before the minimum (if min is set) OR // the start of the range is after the max (also if set) don't display this // range option. if ((this.minDate && end < this.minDate) || (this.maxDate && start > this.maxDate)) { continue; } this.ranges[range] = [start, end]; } var list = ''; this.container.find('.ranges').prepend(list); } // update day names order to firstDay if (typeof options.locale == 'object') { if (typeof options.locale.firstDay == 'number') { this.locale.firstDay = options.locale.firstDay; var iterator = options.locale.firstDay; while (iterator > 0) { this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift()); iterator--; } } } if (typeof options.opens == 'string') this.opens = options.opens; if (typeof options.showWeekNumbers == 'boolean') { this.showWeekNumbers = options.showWeekNumbers; } if (typeof options.buttonClasses == 'string') { this.buttonClasses = [options.buttonClasses]; } if (typeof options.buttonClasses == 'object') { this.buttonClasses = options.buttonClasses; } } //apply CSS classes to buttons var c = this.container; $.each(this.buttonClasses, function (idx, val) { c.find('button').addClass(val); }); if (this.opens == 'right') { //swap calendar positions var left = this.container.find('.calendar.left'); var right = this.container.find('.calendar.right'); left.removeClass('left').addClass('right'); right.removeClass('right').addClass('left'); } if (typeof options == 'undefined' || typeof options.ranges == 'undefined') this.container.find('.calendar').show(); if (typeof cb == 'function') this.cb = cb; this.container.addClass('opens' + this.opens); //event listeners this.container.on('mousedown', $.proxy(this.mousedown, this)); this.container.find('.calendar').on('click', '.prev', $.proxy(this.clickPrev, this)); this.container.find('.calendar').on('click', '.next', $.proxy(this.clickNext, this)); this.container.find('.ranges').on('click', 'button.applyBtn', $.proxy(this.clickApply, this)); this.container.find('.ranges').on('click', 'button.clearBtn', $.proxy(this.clickClear, this)); this.container.find('.calendar').on('click', 'td.available', $.proxy(this.clickDate, this)); this.container.find('.calendar').on('mouseenter', 'td.available', $.proxy(this.enterDate, this)); this.container.find('.calendar').on('mouseleave', 'td.available', $.proxy(this.updateView, this)); this.container.find('.ranges').on('click', 'li', $.proxy(this.clickRange, this)); this.container.find('.ranges').on('mouseenter', 'li', $.proxy(this.enterRange, this)); this.container.find('.ranges').on('mouseleave', 'li', $.proxy(this.updateView, this)); this.element.on('keyup', $.proxy(this.updateFromControl, this)); this.updateView(); this.updateCalendars(); }; DateRangePicker.prototype = { constructor: DateRangePicker, mousedown: function (e) { e.stopPropagation(); e.preventDefault(); }, updateView: function () { this.leftCalendar.month.set({ month: this.startDate.getMonth(), year: this.startDate.getFullYear() }); this.rightCalendar.month.set({ month: this.endDate.getMonth(), year: this.endDate.getFullYear() }); this.container.find('input[name=daterangepicker_start]').val(this.startDate.format(this.format)); this.container.find('input[name=daterangepicker_end]').val(this.endDate.format(this.format)); if (this.startDate.is(this.endDate) || this.startDate.isBefore(this.endDate)) { this.container.find('button.applyBtn').removeAttr('disabled'); } else { this.container.find('button.applyBtn').attr('disabled', 'disabled'); } }, updateFromControl: function () { if (!this.element.is('input')) return; var dateString = this.element.val().split(this.separator); var start = Date.create(dateString[0]); var end = Date.create(dateString[1]); if (start == null || end == null) return; if (end.isBefore(start)) return; this.startDate = start; this.endDate = end; this.updateView(); this.cb(this.startDate, this.endDate); this.updateCalendars(); }, notify: function () { if (!this.cleared) { this.updateView(); } if (this.element.is('input')) { this.element.val(this.cleared ? '' : this.startDate.format(this.format) + this.separator + this.endDate.format(this.format)); } var arg1 = (this.cleared ? null : this.startDate), arg2 = (this.cleared ? null : this.endDate); this.cleared = false; this.cb(arg1,arg2); }, move: function () { if (this.opens == 'left') { this.container.css({ top: this.element.offset().top + this.element.outerHeight(), right: $(window).width() - this.element.offset().left - this.element.outerWidth(), left: 'auto' }); } else { this.container.css({ top: this.element.offset().top + this.element.outerHeight(), left: this.element.offset().left, right: 'auto' }); } }, show: function (e) { this.container.show(); this.move(); if (e) { e.stopPropagation(); e.preventDefault(); } this.changed = false; this.element.trigger('shown',{target:e.target,picker:this}); $(document).on('mousedown', $.proxy(this.hide, this)); }, hide: function (e) { this.container.hide(); $(document).off('mousedown', this.hide); if (this.changed) { this.changed = false; this.notify(); } }, enterRange: function (e) { var label = e.target.innerHTML; if (label == this.locale.customRangeLabel) { this.updateView(); } else { var dates = this.ranges[label]; this.container.find('input[name=daterangepicker_start]').val(dates[0].format(this.format)); this.container.find('input[name=daterangepicker_end]').val(dates[1].format(this.format)); } }, clickRange: function (e) { var label = e.target.innerHTML; if (label == this.locale.customRangeLabel) { this.container.find('.calendar').show(); } else { var dates = this.ranges[label]; this.startDate = dates[0]; this.endDate = dates[1]; this.leftCalendar.month.set({ month: this.startDate.getMonth(), year: this.startDate.getFullYear() }); this.rightCalendar.month.set({ month: this.endDate.getMonth(), year: this.endDate.getFullYear() }); this.updateCalendars(); this.changed = true; this.container.find('.calendar').hide(); this.hide(); } }, clickPrev: function (e) { var cal = $(e.target).parents('.calendar'); if (cal.hasClass('left')) { this.leftCalendar.month.addMonths(-1); } else { this.rightCalendar.month.addMonths(-1); } this.updateCalendars(); }, clickNext: function (e) { var cal = $(e.target).parents('.calendar'); if (cal.hasClass('left')) { this.leftCalendar.month.addMonths(1); } else { this.rightCalendar.month.addMonths(1); } this.updateCalendars(); }, enterDate: function (e) { var title = $(e.target).attr('title'); var row = title.substr(1, 1); var col = title.substr(3, 1); var cal = $(e.target).parents('.calendar'); if (cal.hasClass('left')) { this.container.find('input[name=daterangepicker_start]').val(this.leftCalendar.calendar[row][col].format(this.format)); } else { this.container.find('input[name=daterangepicker_end]').val(this.rightCalendar.calendar[row][col].format(this.format)); } }, clickDate: function (e) { var title = $(e.target).attr('title'); var row = title.substr(1, 1); var col = title.substr(3, 1); var cal = $(e.target).parents('.calendar'); if (cal.hasClass('left')) { startDate = this.leftCalendar.calendar[row][col]; endDate = this.endDate; this.element.trigger('clicked',{ dir: 'left', picker: this }); } else { startDate = this.startDate; endDate = this.rightCalendar.calendar[row][col]; this.element.trigger('clicked',{ dir: 'right', picker: this }); } cal.find('td').removeClass('active'); if (startDate.is(endDate) || startDate.isBefore(endDate)) { $(e.target).addClass('active'); if (!startDate.is(this.startDate) || !endDate.is(this.endDate)) this.changed = true; this.startDate = startDate; this.endDate = endDate; } else if (startDate.isAfter(endDate)) { $(e.target).addClass('active'); this.changed = true; this.startDate = startDate; this.endDate = startDate.clone().addDays(1); } this.leftCalendar.month.set({ month: this.startDate.getMonth(), year: this.startDate.getFullYear() }); this.rightCalendar.month.set({ month: this.endDate.getMonth(), year: this.endDate.getFullYear() }); this.updateCalendars(); }, clickApply: function (e) { this.hide(); }, clickClear: function (e) { this.changed = true; this.cleared = true; this.hide(); }, updateCalendars: function () { this.leftCalendar.calendar = this.buildCalendar(this.leftCalendar.month.getMonth(), this.leftCalendar.month.getFullYear()); this.rightCalendar.calendar = this.buildCalendar(this.rightCalendar.month.getMonth(), this.rightCalendar.month.getFullYear()); this.container.find('.calendar.left').html(this.renderCalendar(this.leftCalendar.calendar, this.startDate, this.minDate, this.maxDate)); this.container.find('.calendar.right').html(this.renderCalendar(this.rightCalendar.calendar, this.endDate, this.startDate, this.maxDate)); this.element.trigger('updated',this); }, buildCalendar: function (month, year) { var firstDay = Date.create('today').set({ day: 1, month: month, year: year }); var lastMonth = firstDay.clone().addDays(-1).getMonth(); var lastYear = firstDay.clone().addDays(-1).getFullYear(); var daysInMonth = this.getDaysInMonth(year, month); var daysInLastMonth = this.getDaysInMonth(lastYear, lastMonth); var dayOfWeek = firstDay.getDay(); //initialize a 6 rows x 7 columns array for the calendar var calendar = Array(); for (var i = 0; i < 6; i++) { calendar[i] = Array(); } //populate the calendar with date objects var startDay = daysInLastMonth - dayOfWeek + this.locale.firstDay + 1; if (startDay > daysInLastMonth) startDay -= 7; if (dayOfWeek == this.locale.firstDay) startDay = daysInLastMonth - 6; var curDate = Date.create('today').set({ day: startDay, month: lastMonth, year: lastYear }); for (var i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = curDate.clone().addDays(1)) { if (i > 0 && col % 7 == 0) { col = 0; row++; } calendar[row][col] = curDate; } return calendar; }, renderCalendar: function (calendar, selected, minDate, maxDate) { var html = ''; html += ''; html += ''; // add empty cell for week number if (this.showWeekNumbers) html += ''; if (!minDate || minDate < calendar[1][1]) { html += ''; } else { html += ''; } html += ''; if (!maxDate || maxDate > calendar[1][1]) { html += ''; } else { html += ''; } html += ''; html += ''; // add week number label if (this.showWeekNumbers) html += ''; $.each(this.locale.daysOfWeek, function (index, dayOfWeek) { html += ''; }); html += ''; html += ''; html += ''; for (var row = 0; row < 6; row++) { html += ''; // add week number if (this.showWeekNumbers) html += ''; for (var col = 0; col < 7; col++) { var cname = 'available '; cname += (calendar[row][col].getMonth() == calendar[1][1].getMonth()) ? '' : 'off'; // Normalise the time so the comparison won't fail selected.setHours(0,0,0,0); if ( (minDate && calendar[row][col] < minDate) || (maxDate && calendar[row][col] > maxDate)) { cname = ' off disabled '; } else if (calendar[row][col].is(selected)) { cname += ' active '; if (calendar[row][col].is(this.startDate)) { cname += ' start-date '; } if (calendar[row][col].is(this.endDate)) { cname += ' end-date '; } } else if (calendar[row][col] >= this.startDate && calendar[row][col] <= this.endDate) { cname += ' in-range '; if (calendar[row][col].is(this.startDate)) { cname += ' start-date '; } if (calendar[row][col].is(this.endDate)) { cname += ' end-date '; } } var title = 'r' + row + 'c' + col; html += ''; } html += ''; } html += ''; html += '
' + this.locale.monthNames[calendar[1][1].getMonth()] + calendar[1][1].format(' {yyyy}') + '
' + this.locale.weekLabel + '' + dayOfWeek + '
' + calendar[row][0].getWeek() + '' + calendar[row][col].getDate() + '
'; return html; }, getDaysInMonth: function (y, m) { return /8|3|5|10/.test(--m)?30:m==1?(!(y%4)&&y%100)||!(y%400)?29:28:31; } }; $.fn.daterangepicker = function (options, cb) { this.each(function() { var el = $(this); if (!el.data('daterangepicker')) el.data('daterangepicker', new DateRangePicker(el, options, cb)); }); return this; }; } (window.jQuery);