361 lines
11 KiB
JavaScript
361 lines
11 KiB
JavaScript
/*!
|
|
* datepair.js v0.4.15 - A javascript plugin for intelligently selecting date and time ranges inspired by Google Calendar.
|
|
* Copyright (c) 2016 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) {
|
|
if (startTime) {
|
|
var newEnd = new Date(startTime.getTime() + this.settings.defaultTimeDelta);
|
|
this.settings.updateTime(this.endTimeInput, newEnd);
|
|
} else if (endTime) {
|
|
var newStart = new Date(endTime.getTime() - this.settings.defaultTimeDelta);
|
|
this.settings.updateTime(this.startTimeInput, newStart);
|
|
}
|
|
|
|
this.timeDelta = this.settings.defaultTimeDelta;
|
|
} else {
|
|
this.timeDelta = null;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (this.settings.anchor == 'start' && hasClass(target, this.settings.startClass)) {
|
|
var newTime = new Date(startTime.getTime() + this.timeDelta);
|
|
this.settings.updateTime(this.endTimeInput, newTime);
|
|
endTime = this.settings.parseTime(this.endTimeInput);
|
|
|
|
this._doMidnightRollover(startTime, endTime);
|
|
} else if (this.settings.anchor == 'end' && hasClass(target, this.settings.endClass)) {
|
|
var newTime = new Date(endTime.getTime() - this.timeDelta);
|
|
this.settings.updateTime(this.startTimeInput, newTime);
|
|
startTime = this.settings.parseTime(this.startTimeInput);
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
|
|
},
|
|
|
|
_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)); |