/*! * datepair.js v0.4.16 - A javascript plugin for intelligently selecting date and time ranges inspired by Google Calendar. * Copyright (c) 2018 Jon Thornton - http://jonthornton.github.com/Datepair.js * License: MIT */ (function(window, document) { 'use strict'; var _ONE_DAY = 86400000; var jq = window.Zepto || window.jQuery; function simpleExtend(obj1, obj2) { var out = obj2 || {}; for (var i in obj1) { if (!(i in out)) { out[i] = obj1[i]; } } return out; } // IE's custom event support is totally borked. // Use jQuery if possible function triggerSimpleCustomEvent(el, eventName) { if (jq) { jq(el).trigger(eventName); } else { var event = document.createEvent('CustomEvent'); event.initCustomEvent(eventName, true, true, {}); el.dispatchEvent(event); } } // el.classList not supported by < IE10 // use jQuery if available function hasClass(el, className) { if (jq) { return jq(el).hasClass(className); } else { return el.classList.contains(className); } } function Datepair(container, options) { this.dateDelta = null; this.timeDelta = null; this._defaults = { startClass: 'start', endClass: 'end', timeClass: 'time', dateClass: 'date', defaultDateDelta: 0, defaultTimeDelta: 3600000, anchor: 'start', // defaults for jquery-timepicker; override when using other input widgets parseTime: function(input){ return jq(input).timepicker('getTime'); }, updateTime: function(input, dateObj){ jq(input).timepicker('setTime', dateObj); }, setMinTime: function(input, dateObj){ jq(input).timepicker('option', 'minTime', dateObj); }, // defaults for bootstrap datepicker; override when using other input widgets parseDate: function(input){ return input.value && jq(input).datepicker('getDate'); }, updateDate: function(input, dateObj){ jq(input).datepicker('update', dateObj); } }; this.container = container; this.settings = simpleExtend(this._defaults, options); this.startDateInput = this.container.querySelector('.'+this.settings.startClass+'.'+this.settings.dateClass); this.endDateInput = this.container.querySelector('.'+this.settings.endClass+'.'+this.settings.dateClass); this.startTimeInput = this.container.querySelector('.'+this.settings.startClass+'.'+this.settings.timeClass); this.endTimeInput = this.container.querySelector('.'+this.settings.endClass+'.'+this.settings.timeClass); // initialize date and time deltas this.refresh(); // init starts here this._bindChangeHandler(); } Datepair.prototype = { constructor: Datepair, option: function(key, value) { if (typeof key == 'object') { this.settings = simpleExtend(this.settings, key); } else if (typeof key == 'string' && typeof value != 'undefined') { this.settings[key] = value; } else if (typeof key == 'string') { return this.settings[key]; } this._updateEndMintime(); }, getTimeDiff: function() { // due to the fact that times can wrap around, timeDiff for any // time-only pair will always be >= 0 var delta = this.dateDelta + this.timeDelta; if (delta < 0 && (!this.startDateInput || !this.endDateInput) ) { delta += _ONE_DAY; } return delta; }, refresh: function() { if (this.startDateInput && this.startDateInput.value && this.endDateInput && this.endDateInput.value) { var startDate = this.settings.parseDate(this.startDateInput); var endDate = this.settings.parseDate(this.endDateInput); if (startDate && endDate) { this.dateDelta = endDate.getTime() - startDate.getTime(); } } if (this.startTimeInput && this.startTimeInput.value && this.endTimeInput && this.endTimeInput.value) { var startTime = this.settings.parseTime(this.startTimeInput); var endTime = this.settings.parseTime(this.endTimeInput); if (startTime && endTime) { this.timeDelta = endTime.getTime() - startTime.getTime(); this._updateEndMintime(); } } }, remove: function() { this._unbindChangeHandler(); }, _bindChangeHandler: function(){ // addEventListener doesn't work with synthetic "change" events // fired by jQuery's trigger() functioin. If jQuery is present, // use that for event binding if (jq) { jq(this.container).on('change.datepair', jq.proxy(this.handleEvent, this)); } else { this.container.addEventListener('change', this, false); } }, _unbindChangeHandler: function(){ if (jq) { jq(this.container).off('change.datepair'); } else { this.container.removeEventListener('change', this, false); } }, // This function will be called when passing 'this' to addEventListener handleEvent: function(e){ // temporarily unbind the change handler to prevent triggering this // if we update other inputs this._unbindChangeHandler(); if (hasClass(e.target, this.settings.dateClass)) { if (e.target.value != '') { this._dateChanged(e.target); this._timeChanged(e.target); } else { this.dateDelta = null; } } else if (hasClass(e.target, this.settings.timeClass)) { if (e.target.value != '') { this._timeChanged(e.target); } else { this.timeDelta = null; } } this._validateRanges(); this._updateEndMintime(); this._bindChangeHandler(); }, _dateChanged: function(target){ if (!this.startDateInput || !this.endDateInput) { return; } var startDate = this.settings.parseDate(this.startDateInput); var endDate = this.settings.parseDate(this.endDateInput); if (!startDate || !endDate) { if (this.settings.defaultDateDelta !== null) { if (startDate) { var newEnd = new Date(startDate.getTime() + this.settings.defaultDateDelta * _ONE_DAY); this.settings.updateDate(this.endDateInput, newEnd); } else if (endDate) { var newStart = new Date(endDate.getTime() - this.settings.defaultDateDelta * _ONE_DAY); this.settings.updateDate(this.startDateInput, newStart); } this.dateDelta = this.settings.defaultDateDelta * _ONE_DAY; } else { this.dateDelta = null; } return; } if (this.settings.anchor == 'start' && hasClass(target, this.settings.startClass)) { var newDate = new Date(startDate.getTime() + this.dateDelta); this.settings.updateDate(this.endDateInput, newDate); } else if (this.settings.anchor == 'end' && hasClass(target, this.settings.endClass)) { var newDate = new Date(endDate.getTime() - this.dateDelta); this.settings.updateDate(this.startDateInput, newDate); } else { if (endDate < startDate) { var otherInput = hasClass(target, this.settings.startClass) ? this.endDateInput : this.startDateInput; var selectedDate = this.settings.parseDate(target); this.dateDelta = 0; this.settings.updateDate(otherInput, selectedDate); } else { this.dateDelta = endDate.getTime() - startDate.getTime(); } } }, _timeChanged: function(target){ if (!this.startTimeInput || !this.endTimeInput) { return; } var startTime = this.settings.parseTime(this.startTimeInput); var endTime = this.settings.parseTime(this.endTimeInput); if (!startTime || !endTime) { if (this.settings.defaultTimeDelta !== null) { this.timeDelta = this.settings.defaultTimeDelta; if (startTime) { endTime = this._setTimeAndReturn(this.endTimeInput, new Date(startTime.getTime() + this.settings.defaultTimeDelta)); this._doMidnightRollover(startTime, endTime); } else if (endTime) { startTime = this._setTimeAndReturn(this.startTimeInput, new Date(endTime.getTime() - this.settings.defaultTimeDelta)); this._doMidnightRollover(startTime, endTime); } } else { this.timeDelta = null; } return; } if (this.settings.anchor == 'start' && hasClass(target, this.settings.startClass)) { endTime = this._setTimeAndReturn(this.endTimeInput, new Date(startTime.getTime() + this.timeDelta)); this._doMidnightRollover(startTime, endTime); } else if (this.settings.anchor == 'end' && hasClass(target, this.settings.endClass)) { startTime = this._setTimeAndReturn(this.startTimeInput, new Date(endTime.getTime() - this.timeDelta)); this._doMidnightRollover(startTime, endTime); } else { this._doMidnightRollover(startTime, endTime); var startDate, endDate; if (this.startDateInput && this.endDateInput) { startDate = this.settings.parseDate(this.startDateInput); endDate = this.settings.parseDate(this.endDateInput); } if ((+startDate == +endDate) && (endTime < startTime)) { var thisInput = hasClass(target, this.settings.endClass) ? this.endTimeInput : this.startTimeInput; var otherInput = hasClass(target, this.settings.startClass) ? this.endTimeInput : this.startTimeInput; var selectedTime = this.settings.parseTime(thisInput); this.timeDelta = 0; this.settings.updateTime(otherInput, selectedTime); } else { this.timeDelta = endTime.getTime() - startTime.getTime(); } } }, _setTimeAndReturn: function(input, newTime) { this.settings.updateTime(input, newTime); return this.settings.parseTime(input); }, _doMidnightRollover: function(startTime, endTime) { if (!this.startDateInput || !this.endDateInput) { return; } var endDate = this.settings.parseDate(this.endDateInput); var startDate = this.settings.parseDate(this.startDateInput); var newDelta = endTime.getTime() - startTime.getTime(); var offset = (endTime < startTime) ? _ONE_DAY : -1 * _ONE_DAY; if (this.dateDelta !== null && this.dateDelta + this.timeDelta <= _ONE_DAY && this.dateDelta + newDelta != 0 && (offset > 0 || this.dateDelta != 0) && ((newDelta >= 0 && this.timeDelta < 0) || (newDelta < 0 && this.timeDelta >= 0))) { if (this.settings.anchor == 'start') { this.settings.updateDate(this.endDateInput, new Date(endDate.getTime() + offset)); this._dateChanged(this.endDateInput); } else if (this.settings.anchor == 'end') { this.settings.updateDate(this.startDateInput, new Date(startDate.getTime() - offset)); this._dateChanged(this.startDateInput); } } this.timeDelta = newDelta; }, _updateEndMintime: function(){ if (typeof this.settings.setMinTime != 'function') return; var baseTime = null; if (this.settings.anchor == 'start' && (!this.dateDelta || this.dateDelta < _ONE_DAY || (this.timeDelta && this.dateDelta + this.timeDelta < _ONE_DAY))) { baseTime = this.settings.parseTime(this.startTimeInput); } this.settings.setMinTime(this.endTimeInput, baseTime); }, _validateRanges: function(){ if (this.startTimeInput && this.endTimeInput && this.timeDelta === null) { triggerSimpleCustomEvent(this.container, 'rangeIncomplete'); return; } if (this.startDateInput && this.endDateInput && this.dateDelta === null) { triggerSimpleCustomEvent(this.container, 'rangeIncomplete'); return; } // due to the fact that times can wrap around, any time-only pair will be considered valid if (!this.startDateInput || !this.endDateInput || this.dateDelta + this.timeDelta >= 0) { triggerSimpleCustomEvent(this.container, 'rangeSelected'); } else { triggerSimpleCustomEvent(this.container, 'rangeError'); } } }; window.Datepair = Datepair; }(window, document));