1/*! 2 * datepair.js v0.4.15 - A javascript plugin for intelligently selecting date and time ranges inspired by Google Calendar. 3 * Copyright (c) 2016 Jon Thornton - http://jonthornton.github.com/Datepair.js 4 * License: MIT 5 */ 6 7(function(window, document) { 8 9 'use strict'; 10 11 var _ONE_DAY = 86400000; 12 var jq = window.Zepto || window.jQuery; 13 14 function simpleExtend(obj1, obj2) { 15 var out = obj2 || {}; 16 17 for (var i in obj1) { 18 if (!(i in out)) { 19 out[i] = obj1[i]; 20 } 21 } 22 23 return out; 24 } 25 26 // IE's custom event support is totally borked. 27 // Use jQuery if possible 28 function triggerSimpleCustomEvent(el, eventName) { 29 if (jq) { 30 jq(el).trigger(eventName); 31 } else { 32 var event = document.createEvent('CustomEvent'); 33 event.initCustomEvent(eventName, true, true, {}); 34 el.dispatchEvent(event); 35 } 36 } 37 38 // el.classList not supported by < IE10 39 // use jQuery if available 40 function hasClass(el, className) { 41 if (jq) { 42 return jq(el).hasClass(className); 43 } else { 44 return el.classList.contains(className); 45 } 46 } 47 48 function Datepair(container, options) { 49 this.dateDelta = null; 50 this.timeDelta = null; 51 this._defaults = { 52 startClass: 'start', 53 endClass: 'end', 54 timeClass: 'time', 55 dateClass: 'date', 56 defaultDateDelta: 0, 57 defaultTimeDelta: 3600000, 58 anchor: 'start', 59 60 // defaults for jquery-timepicker; override when using other input widgets 61 parseTime: function(input){ 62 return jq(input).timepicker('getTime'); 63 }, 64 updateTime: function(input, dateObj){ 65 jq(input).timepicker('setTime', dateObj); 66 }, 67 setMinTime: function(input, dateObj){ 68 jq(input).timepicker('option', 'minTime', dateObj); 69 }, 70 71 // defaults for bootstrap datepicker; override when using other input widgets 72 parseDate: function(input){ 73 return input.value && jq(input).datepicker('getDate'); 74 }, 75 updateDate: function(input, dateObj){ 76 jq(input).datepicker('update', dateObj); 77 } 78 }; 79 80 this.container = container; 81 this.settings = simpleExtend(this._defaults, options); 82 83 this.startDateInput = this.container.querySelector('.'+this.settings.startClass+'.'+this.settings.dateClass); 84 this.endDateInput = this.container.querySelector('.'+this.settings.endClass+'.'+this.settings.dateClass); 85 this.startTimeInput = this.container.querySelector('.'+this.settings.startClass+'.'+this.settings.timeClass); 86 this.endTimeInput = this.container.querySelector('.'+this.settings.endClass+'.'+this.settings.timeClass); 87 88 // initialize date and time deltas 89 this.refresh(); 90 91 // init starts here 92 this._bindChangeHandler(); 93 } 94 95 Datepair.prototype = { 96 constructor: Datepair, 97 98 option: function(key, value) 99 { 100 if (typeof key == 'object') { 101 this.settings = simpleExtend(this.settings, key); 102 103 } else if (typeof key == 'string' && typeof value != 'undefined') { 104 this.settings[key] = value; 105 106 } else if (typeof key == 'string') { 107 return this.settings[key]; 108 } 109 110 this._updateEndMintime(); 111 }, 112 113 getTimeDiff: function() 114 { 115 // due to the fact that times can wrap around, timeDiff for any 116 // time-only pair will always be >= 0 117 var delta = this.dateDelta + this.timeDelta; 118 if (delta < 0 && (!this.startDateInput || !this.endDateInput) ) { 119 delta += _ONE_DAY; 120 } 121 122 return delta; 123 }, 124 125 refresh: function() 126 { 127 if (this.startDateInput && this.startDateInput.value && this.endDateInput && this.endDateInput.value) { 128 var startDate = this.settings.parseDate(this.startDateInput); 129 var endDate = this.settings.parseDate(this.endDateInput); 130 if (startDate && endDate) { 131 this.dateDelta = endDate.getTime() - startDate.getTime(); 132 } 133 } 134 if (this.startTimeInput && this.startTimeInput.value && this.endTimeInput && this.endTimeInput.value) { 135 var startTime = this.settings.parseTime(this.startTimeInput); 136 var endTime = this.settings.parseTime(this.endTimeInput); 137 if (startTime && endTime) { 138 this.timeDelta = endTime.getTime() - startTime.getTime(); 139 this._updateEndMintime(); 140 } 141 } 142 }, 143 144 remove: function() 145 { 146 this._unbindChangeHandler(); 147 }, 148 149 _bindChangeHandler: function(){ 150 // addEventListener doesn't work with synthetic "change" events 151 // fired by jQuery's trigger() functioin. If jQuery is present, 152 // use that for event binding 153 if (jq) { 154 jq(this.container).on('change.datepair', jq.proxy(this.handleEvent, this)); 155 } else { 156 this.container.addEventListener('change', this, false); 157 } 158 }, 159 160 _unbindChangeHandler: function(){ 161 if (jq) { 162 jq(this.container).off('change.datepair'); 163 } else { 164 this.container.removeEventListener('change', this, false); 165 } 166 }, 167 168 // This function will be called when passing 'this' to addEventListener 169 handleEvent: function(e){ 170 // temporarily unbind the change handler to prevent triggering this 171 // if we update other inputs 172 this._unbindChangeHandler(); 173 174 if (hasClass(e.target, this.settings.dateClass)) { 175 if (e.target.value != '') { 176 this._dateChanged(e.target); 177 this._timeChanged(e.target); 178 } else { 179 this.dateDelta = null; 180 } 181 182 } else if (hasClass(e.target, this.settings.timeClass)) { 183 if (e.target.value != '') { 184 this._timeChanged(e.target); 185 } else { 186 this.timeDelta = null; 187 } 188 } 189 190 this._validateRanges(); 191 this._updateEndMintime(); 192 this._bindChangeHandler(); 193 }, 194 195 _dateChanged: function(target){ 196 if (!this.startDateInput || !this.endDateInput) { 197 return; 198 } 199 200 var startDate = this.settings.parseDate(this.startDateInput); 201 var endDate = this.settings.parseDate(this.endDateInput); 202 203 if (!startDate || !endDate) { 204 if (this.settings.defaultDateDelta !== null) { 205 if (startDate) { 206 var newEnd = new Date(startDate.getTime() + this.settings.defaultDateDelta * _ONE_DAY); 207 this.settings.updateDate(this.endDateInput, newEnd); 208 209 } else if (endDate) { 210 var newStart = new Date(endDate.getTime() - this.settings.defaultDateDelta * _ONE_DAY); 211 this.settings.updateDate(this.startDateInput, newStart); 212 } 213 214 this.dateDelta = this.settings.defaultDateDelta * _ONE_DAY; 215 } else { 216 this.dateDelta = null; 217 } 218 219 return; 220 } 221 222 if (this.settings.anchor == 'start' && hasClass(target, this.settings.startClass)) { 223 var newDate = new Date(startDate.getTime() + this.dateDelta); 224 this.settings.updateDate(this.endDateInput, newDate); 225 } else if (this.settings.anchor == 'end' && hasClass(target, this.settings.endClass)) { 226 var newDate = new Date(endDate.getTime() - this.dateDelta); 227 this.settings.updateDate(this.startDateInput, newDate); 228 } else { 229 if (endDate < startDate) { 230 var otherInput = hasClass(target, this.settings.startClass) ? this.endDateInput : this.startDateInput; 231 var selectedDate = this.settings.parseDate(target); 232 this.dateDelta = 0; 233 this.settings.updateDate(otherInput, selectedDate); 234 } else { 235 this.dateDelta = endDate.getTime() - startDate.getTime(); 236 } 237 } 238 }, 239 240 _timeChanged: function(target){ 241 if (!this.startTimeInput || !this.endTimeInput) { 242 return; 243 } 244 245 var startTime = this.settings.parseTime(this.startTimeInput); 246 var endTime = this.settings.parseTime(this.endTimeInput); 247 248 if (!startTime || !endTime) { 249 if (this.settings.defaultTimeDelta !== null) { 250 if (startTime) { 251 var newEnd = new Date(startTime.getTime() + this.settings.defaultTimeDelta); 252 this.settings.updateTime(this.endTimeInput, newEnd); 253 } else if (endTime) { 254 var newStart = new Date(endTime.getTime() - this.settings.defaultTimeDelta); 255 this.settings.updateTime(this.startTimeInput, newStart); 256 } 257 258 this.timeDelta = this.settings.defaultTimeDelta; 259 } else { 260 this.timeDelta = null; 261 } 262 263 return; 264 } 265 266 if (this.settings.anchor == 'start' && hasClass(target, this.settings.startClass)) { 267 var newTime = new Date(startTime.getTime() + this.timeDelta); 268 this.settings.updateTime(this.endTimeInput, newTime); 269 endTime = this.settings.parseTime(this.endTimeInput); 270 271 this._doMidnightRollover(startTime, endTime); 272 } else if (this.settings.anchor == 'end' && hasClass(target, this.settings.endClass)) { 273 var newTime = new Date(endTime.getTime() - this.timeDelta); 274 this.settings.updateTime(this.startTimeInput, newTime); 275 startTime = this.settings.parseTime(this.startTimeInput); 276 277 this._doMidnightRollover(startTime, endTime); 278 } else { 279 this._doMidnightRollover(startTime, endTime); 280 281 var startDate, endDate; 282 if (this.startDateInput && this.endDateInput) { 283 startDate = this.settings.parseDate(this.startDateInput); 284 endDate = this.settings.parseDate(this.endDateInput); 285 } 286 287 if ((+startDate == +endDate) && (endTime < startTime)) { 288 var thisInput = hasClass(target, this.settings.endClass) ? this.endTimeInput : this.startTimeInput; 289 var otherInput = hasClass(target, this.settings.startClass) ? this.endTimeInput : this.startTimeInput; 290 var selectedTime = this.settings.parseTime(thisInput); 291 this.timeDelta = 0; 292 this.settings.updateTime(otherInput, selectedTime); 293 } else { 294 this.timeDelta = endTime.getTime() - startTime.getTime(); 295 } 296 } 297 298 299 }, 300 301 _doMidnightRollover: function(startTime, endTime) { 302 if (!this.startDateInput || !this.endDateInput) { 303 return; 304 } 305 306 var endDate = this.settings.parseDate(this.endDateInput); 307 var startDate = this.settings.parseDate(this.startDateInput); 308 var newDelta = endTime.getTime() - startTime.getTime(); 309 var offset = (endTime < startTime) ? _ONE_DAY : -1 * _ONE_DAY; 310 311 if (this.dateDelta !== null 312 && this.dateDelta + this.timeDelta <= _ONE_DAY 313 && this.dateDelta + newDelta != 0 314 && (offset > 0 || this.dateDelta != 0) 315 && ((newDelta >= 0 && this.timeDelta < 0) || (newDelta < 0 && this.timeDelta >= 0))) { 316 317 if (this.settings.anchor == 'start') { 318 this.settings.updateDate(this.endDateInput, new Date(endDate.getTime() + offset)); 319 this._dateChanged(this.endDateInput); 320 } else if (this.settings.anchor == 'end') { 321 this.settings.updateDate(this.startDateInput, new Date(startDate.getTime() - offset)); 322 this._dateChanged(this.startDateInput); 323 } 324 } 325 this.timeDelta = newDelta; 326 }, 327 328 _updateEndMintime: function(){ 329 if (typeof this.settings.setMinTime != 'function') return; 330 331 var baseTime = null; 332 if (this.settings.anchor == 'start' && (!this.dateDelta || this.dateDelta < _ONE_DAY || (this.timeDelta && this.dateDelta + this.timeDelta < _ONE_DAY))) { 333 baseTime = this.settings.parseTime(this.startTimeInput); 334 } 335 336 this.settings.setMinTime(this.endTimeInput, baseTime); 337 }, 338 339 _validateRanges: function(){ 340 if (this.startTimeInput && this.endTimeInput && this.timeDelta === null) { 341 triggerSimpleCustomEvent(this.container, 'rangeIncomplete'); 342 return; 343 } 344 345 if (this.startDateInput && this.endDateInput && this.dateDelta === null) { 346 triggerSimpleCustomEvent(this.container, 'rangeIncomplete'); 347 return; 348 } 349 350 // due to the fact that times can wrap around, any time-only pair will be considered valid 351 if (!this.startDateInput || !this.endDateInput || this.dateDelta + this.timeDelta >= 0) { 352 triggerSimpleCustomEvent(this.container, 'rangeSelected'); 353 } else { 354 triggerSimpleCustomEvent(this.container, 'rangeError'); 355 } 356 } 357 }; 358 359 window.Datepair = Datepair; 360 361}(window, document));