]>
Commit | Line | Data |
---|---|---|
31f18b77 FG |
1 | /** |
2 | * @version: 2.1.19 | |
3 | * @author: Dan Grossman http://www.dangrossman.info/ | |
4 | * @copyright: Copyright (c) 2012-2015 Dan Grossman. All rights reserved. | |
5 | * @license: Licensed under the MIT license. See http://www.opensource.org/licenses/mit-license.php | |
6 | * @website: https://www.improvely.com/ | |
7 | */ | |
8 | ||
9 | (function(root, factory) { | |
10 | ||
11 | if (typeof define === 'function' && define.amd) { | |
12 | define(['moment', 'jquery', 'exports'], function(momentjs, $, exports) { | |
13 | root.daterangepicker = factory(root, exports, momentjs, $); | |
14 | }); | |
15 | ||
16 | } else if (typeof exports !== 'undefined') { | |
17 | var momentjs = require('moment'); | |
18 | var jQuery = (typeof window != 'undefined') ? window.jQuery : undefined; //isomorphic issue | |
19 | if (!jQuery) { | |
20 | try { | |
21 | jQuery = require('jquery'); | |
22 | if (!jQuery.fn) jQuery.fn = {}; //isomorphic issue | |
23 | } catch (err) { | |
24 | if (!jQuery) throw new Error('jQuery dependency not found'); | |
25 | } | |
26 | } | |
27 | ||
28 | factory(root, exports, momentjs, jQuery); | |
29 | ||
30 | // Finally, as a browser global. | |
31 | } else { | |
32 | root.daterangepicker = factory(root, {}, root.moment || moment, (root.jQuery || root.Zepto || root.ender || root.$)); | |
33 | } | |
34 | ||
35 | }(this || {}, function(root, daterangepicker, moment, $) { // 'this' doesn't exist on a server | |
36 | ||
37 | var DateRangePicker = function(element, options, cb) { | |
38 | ||
39 | //default settings for options | |
40 | this.parentEl = 'body'; | |
41 | this.element = $(element); | |
42 | this.startDate = moment().startOf('day'); | |
43 | this.endDate = moment().endOf('day'); | |
44 | this.minDate = false; | |
45 | this.maxDate = false; | |
46 | this.dateLimit = false; | |
47 | this.autoApply = false; | |
48 | this.singleDatePicker = false; | |
49 | this.showDropdowns = false; | |
50 | this.showWeekNumbers = false; | |
51 | this.showISOWeekNumbers = false; | |
52 | this.timePicker = false; | |
53 | this.timePicker24Hour = false; | |
54 | this.timePickerIncrement = 1; | |
55 | this.timePickerSeconds = false; | |
56 | this.linkedCalendars = true; | |
57 | this.autoUpdateInput = true; | |
58 | this.alwaysShowCalendars = false; | |
59 | this.ranges = {}; | |
60 | ||
61 | this.opens = 'right'; | |
62 | if (this.element.hasClass('pull-right')) | |
63 | this.opens = 'left'; | |
64 | ||
65 | this.drops = 'down'; | |
66 | if (this.element.hasClass('dropup')) | |
67 | this.drops = 'up'; | |
68 | ||
69 | this.buttonClasses = 'btn btn-sm'; | |
70 | this.applyClass = 'btn-success'; | |
71 | this.cancelClass = 'btn-default'; | |
72 | ||
73 | this.locale = { | |
74 | format: 'MM/DD/YYYY', | |
75 | separator: ' - ', | |
76 | applyLabel: 'Apply', | |
77 | cancelLabel: 'Cancel', | |
78 | weekLabel: 'W', | |
79 | customRangeLabel: 'Custom Range', | |
80 | daysOfWeek: moment.weekdaysMin(), | |
81 | monthNames: moment.monthsShort(), | |
82 | firstDay: moment.localeData().firstDayOfWeek() | |
83 | }; | |
84 | ||
85 | this.callback = function() { }; | |
86 | ||
87 | //some state information | |
88 | this.isShowing = false; | |
89 | this.leftCalendar = {}; | |
90 | this.rightCalendar = {}; | |
91 | ||
92 | //custom options from user | |
93 | if (typeof options !== 'object' || options === null) | |
94 | options = {}; | |
95 | ||
96 | //allow setting options with data attributes | |
97 | //data-api options will be overwritten with custom javascript options | |
98 | options = $.extend(this.element.data(), options); | |
99 | ||
100 | //html template for the picker UI | |
101 | if (typeof options.template !== 'string' && !(options.template instanceof $)) | |
102 | options.template = '<div class="daterangepicker dropdown-menu">' + | |
103 | '<div class="calendar left">' + | |
104 | '<div class="daterangepicker_input">' + | |
105 | '<input class="input-mini" type="text" name="daterangepicker_start" value="" />' + | |
106 | '<i class="fa fa-calendar glyphicon glyphicon-calendar"></i>' + | |
107 | '<div class="calendar-time">' + | |
108 | '<div></div>' + | |
109 | '<i class="fa fa-clock-o glyphicon glyphicon-time"></i>' + | |
110 | '</div>' + | |
111 | '</div>' + | |
112 | '<div class="calendar-table"></div>' + | |
113 | '</div>' + | |
114 | '<div class="calendar right">' + | |
115 | '<div class="daterangepicker_input">' + | |
116 | '<input class="input-mini" type="text" name="daterangepicker_end" value="" />' + | |
117 | '<i class="fa fa-calendar glyphicon glyphicon-calendar"></i>' + | |
118 | '<div class="calendar-time">' + | |
119 | '<div></div>' + | |
120 | '<i class="fa fa-clock-o glyphicon glyphicon-time"></i>' + | |
121 | '</div>' + | |
122 | '</div>' + | |
123 | '<div class="calendar-table"></div>' + | |
124 | '</div>' + | |
125 | '<div class="ranges">' + | |
126 | '<div class="range_inputs">' + | |
127 | '<button class="applyBtn" disabled="disabled" type="button"></button> ' + | |
128 | '<button class="cancelBtn" type="button"></button>' + | |
129 | '</div>' + | |
130 | '</div>' + | |
131 | '</div>'; | |
132 | ||
133 | this.parentEl = (options.parentEl && $(options.parentEl).length) ? $(options.parentEl) : $(this.parentEl); | |
134 | this.container = $(options.template).appendTo(this.parentEl); | |
135 | ||
136 | // | |
137 | // handle all the possible options overriding defaults | |
138 | // | |
139 | ||
140 | if (typeof options.locale === 'object') { | |
141 | ||
142 | if (typeof options.locale.format === 'string') | |
143 | this.locale.format = options.locale.format; | |
144 | ||
145 | if (typeof options.locale.separator === 'string') | |
146 | this.locale.separator = options.locale.separator; | |
147 | ||
148 | if (typeof options.locale.daysOfWeek === 'object') | |
149 | this.locale.daysOfWeek = options.locale.daysOfWeek.slice(); | |
150 | ||
151 | if (typeof options.locale.monthNames === 'object') | |
152 | this.locale.monthNames = options.locale.monthNames.slice(); | |
153 | ||
154 | if (typeof options.locale.firstDay === 'number') | |
155 | this.locale.firstDay = options.locale.firstDay; | |
156 | ||
157 | if (typeof options.locale.applyLabel === 'string') | |
158 | this.locale.applyLabel = options.locale.applyLabel; | |
159 | ||
160 | if (typeof options.locale.cancelLabel === 'string') | |
161 | this.locale.cancelLabel = options.locale.cancelLabel; | |
162 | ||
163 | if (typeof options.locale.weekLabel === 'string') | |
164 | this.locale.weekLabel = options.locale.weekLabel; | |
165 | ||
166 | if (typeof options.locale.customRangeLabel === 'string') | |
167 | this.locale.customRangeLabel = options.locale.customRangeLabel; | |
168 | ||
169 | } | |
170 | ||
171 | if (typeof options.startDate === 'string') | |
172 | this.startDate = moment(options.startDate, this.locale.format); | |
173 | ||
174 | if (typeof options.endDate === 'string') | |
175 | this.endDate = moment(options.endDate, this.locale.format); | |
176 | ||
177 | if (typeof options.minDate === 'string') | |
178 | this.minDate = moment(options.minDate, this.locale.format); | |
179 | ||
180 | if (typeof options.maxDate === 'string') | |
181 | this.maxDate = moment(options.maxDate, this.locale.format); | |
182 | ||
183 | if (typeof options.startDate === 'object') | |
184 | this.startDate = moment(options.startDate); | |
185 | ||
186 | if (typeof options.endDate === 'object') | |
187 | this.endDate = moment(options.endDate); | |
188 | ||
189 | if (typeof options.minDate === 'object') | |
190 | this.minDate = moment(options.minDate); | |
191 | ||
192 | if (typeof options.maxDate === 'object') | |
193 | this.maxDate = moment(options.maxDate); | |
194 | ||
195 | // sanity check for bad options | |
196 | if (this.minDate && this.startDate.isBefore(this.minDate)) | |
197 | this.startDate = this.minDate.clone(); | |
198 | ||
199 | // sanity check for bad options | |
200 | if (this.maxDate && this.endDate.isAfter(this.maxDate)) | |
201 | this.endDate = this.maxDate.clone(); | |
202 | ||
203 | if (typeof options.applyClass === 'string') | |
204 | this.applyClass = options.applyClass; | |
205 | ||
206 | if (typeof options.cancelClass === 'string') | |
207 | this.cancelClass = options.cancelClass; | |
208 | ||
209 | if (typeof options.dateLimit === 'object') | |
210 | this.dateLimit = options.dateLimit; | |
211 | ||
212 | if (typeof options.opens === 'string') | |
213 | this.opens = options.opens; | |
214 | ||
215 | if (typeof options.drops === 'string') | |
216 | this.drops = options.drops; | |
217 | ||
218 | if (typeof options.showWeekNumbers === 'boolean') | |
219 | this.showWeekNumbers = options.showWeekNumbers; | |
220 | ||
221 | if (typeof options.showISOWeekNumbers === 'boolean') | |
222 | this.showISOWeekNumbers = options.showISOWeekNumbers; | |
223 | ||
224 | if (typeof options.buttonClasses === 'string') | |
225 | this.buttonClasses = options.buttonClasses; | |
226 | ||
227 | if (typeof options.buttonClasses === 'object') | |
228 | this.buttonClasses = options.buttonClasses.join(' '); | |
229 | ||
230 | if (typeof options.showDropdowns === 'boolean') | |
231 | this.showDropdowns = options.showDropdowns; | |
232 | ||
233 | if (typeof options.singleDatePicker === 'boolean') { | |
234 | this.singleDatePicker = options.singleDatePicker; | |
235 | if (this.singleDatePicker) | |
236 | this.endDate = this.startDate.clone(); | |
237 | } | |
238 | ||
239 | if (typeof options.timePicker === 'boolean') | |
240 | this.timePicker = options.timePicker; | |
241 | ||
242 | if (typeof options.timePickerSeconds === 'boolean') | |
243 | this.timePickerSeconds = options.timePickerSeconds; | |
244 | ||
245 | if (typeof options.timePickerIncrement === 'number') | |
246 | this.timePickerIncrement = options.timePickerIncrement; | |
247 | ||
248 | if (typeof options.timePicker24Hour === 'boolean') | |
249 | this.timePicker24Hour = options.timePicker24Hour; | |
250 | ||
251 | if (typeof options.autoApply === 'boolean') | |
252 | this.autoApply = options.autoApply; | |
253 | ||
254 | if (typeof options.autoUpdateInput === 'boolean') | |
255 | this.autoUpdateInput = options.autoUpdateInput; | |
256 | ||
257 | if (typeof options.linkedCalendars === 'boolean') | |
258 | this.linkedCalendars = options.linkedCalendars; | |
259 | ||
260 | if (typeof options.isInvalidDate === 'function') | |
261 | this.isInvalidDate = options.isInvalidDate; | |
262 | ||
263 | if (typeof options.alwaysShowCalendars === 'boolean') | |
264 | this.alwaysShowCalendars = options.alwaysShowCalendars; | |
265 | ||
266 | // update day names order to firstDay | |
267 | if (this.locale.firstDay != 0) { | |
268 | var iterator = this.locale.firstDay; | |
269 | while (iterator > 0) { | |
270 | this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift()); | |
271 | iterator--; | |
272 | } | |
273 | } | |
274 | ||
275 | var start, end, range; | |
276 | ||
277 | //if no start/end dates set, check if an input element contains initial values | |
278 | if (typeof options.startDate === 'undefined' && typeof options.endDate === 'undefined') { | |
279 | if ($(this.element).is('input[type=text]')) { | |
280 | var val = $(this.element).val(), | |
281 | split = val.split(this.locale.separator); | |
282 | ||
283 | start = end = null; | |
284 | ||
285 | if (split.length == 2) { | |
286 | start = moment(split[0], this.locale.format); | |
287 | end = moment(split[1], this.locale.format); | |
288 | } else if (this.singleDatePicker && val !== "") { | |
289 | start = moment(val, this.locale.format); | |
290 | end = moment(val, this.locale.format); | |
291 | } | |
292 | if (start !== null && end !== null) { | |
293 | this.setStartDate(start); | |
294 | this.setEndDate(end); | |
295 | } | |
296 | } | |
297 | } | |
298 | ||
299 | if (typeof options.ranges === 'object') { | |
300 | for (range in options.ranges) { | |
301 | ||
302 | if (typeof options.ranges[range][0] === 'string') | |
303 | start = moment(options.ranges[range][0], this.locale.format); | |
304 | else | |
305 | start = moment(options.ranges[range][0]); | |
306 | ||
307 | if (typeof options.ranges[range][1] === 'string') | |
308 | end = moment(options.ranges[range][1], this.locale.format); | |
309 | else | |
310 | end = moment(options.ranges[range][1]); | |
311 | ||
312 | // If the start or end date exceed those allowed by the minDate or dateLimit | |
313 | // options, shorten the range to the allowable period. | |
314 | if (this.minDate && start.isBefore(this.minDate)) | |
315 | start = this.minDate.clone(); | |
316 | ||
317 | var maxDate = this.maxDate; | |
318 | if (this.dateLimit && start.clone().add(this.dateLimit).isAfter(maxDate)) | |
319 | maxDate = start.clone().add(this.dateLimit); | |
320 | if (maxDate && end.isAfter(maxDate)) | |
321 | end = maxDate.clone(); | |
322 | ||
323 | // If the end of the range is before the minimum or the start of the range is | |
324 | // after the maximum, don't display this range option at all. | |
325 | if ((this.minDate && end.isBefore(this.minDate)) || (maxDate && start.isAfter(maxDate))) | |
326 | continue; | |
327 | ||
328 | //Support unicode chars in the range names. | |
329 | var elem = document.createElement('textarea'); | |
330 | elem.innerHTML = range; | |
331 | var rangeHtml = elem.value; | |
332 | ||
333 | this.ranges[rangeHtml] = [start, end]; | |
334 | } | |
335 | ||
336 | var list = '<ul>'; | |
337 | for (range in this.ranges) { | |
338 | list += '<li>' + range + '</li>'; | |
339 | } | |
340 | list += '<li>' + this.locale.customRangeLabel + '</li>'; | |
341 | list += '</ul>'; | |
342 | this.container.find('.ranges').prepend(list); | |
343 | } | |
344 | ||
345 | if (typeof cb === 'function') { | |
346 | this.callback = cb; | |
347 | } | |
348 | ||
349 | if (!this.timePicker) { | |
350 | this.startDate = this.startDate.startOf('day'); | |
351 | this.endDate = this.endDate.endOf('day'); | |
352 | this.container.find('.calendar-time').hide(); | |
353 | } | |
354 | ||
355 | //can't be used together for now | |
356 | if (this.timePicker && this.autoApply) | |
357 | this.autoApply = false; | |
358 | ||
359 | if (this.autoApply && typeof options.ranges !== 'object') { | |
360 | this.container.find('.ranges').hide(); | |
361 | } else if (this.autoApply) { | |
362 | this.container.find('.applyBtn, .cancelBtn').addClass('hide'); | |
363 | } | |
364 | ||
365 | if (this.singleDatePicker) { | |
366 | this.container.addClass('single'); | |
367 | this.container.find('.calendar.left').addClass('single'); | |
368 | this.container.find('.calendar.left').show(); | |
369 | this.container.find('.calendar.right').hide(); | |
370 | this.container.find('.daterangepicker_input input, .daterangepicker_input i').hide(); | |
371 | if (!this.timePicker) { | |
372 | this.container.find('.ranges').hide(); | |
373 | } | |
374 | } | |
375 | ||
376 | if ((typeof options.ranges === 'undefined' && !this.singleDatePicker) || this.alwaysShowCalendars) { | |
377 | this.container.addClass('show-calendar'); | |
378 | } | |
379 | ||
380 | this.container.addClass('opens' + this.opens); | |
381 | ||
382 | //swap the position of the predefined ranges if opens right | |
383 | if (typeof options.ranges !== 'undefined' && this.opens == 'right') { | |
384 | var ranges = this.container.find('.ranges'); | |
385 | var html = ranges.clone(); | |
386 | ranges.remove(); | |
387 | this.container.find('.calendar.left').parent().prepend(html); | |
388 | } | |
389 | ||
390 | //apply CSS classes and labels to buttons | |
391 | this.container.find('.applyBtn, .cancelBtn').addClass(this.buttonClasses); | |
392 | if (this.applyClass.length) | |
393 | this.container.find('.applyBtn').addClass(this.applyClass); | |
394 | if (this.cancelClass.length) | |
395 | this.container.find('.cancelBtn').addClass(this.cancelClass); | |
396 | this.container.find('.applyBtn').html(this.locale.applyLabel); | |
397 | this.container.find('.cancelBtn').html(this.locale.cancelLabel); | |
398 | ||
399 | // | |
400 | // event listeners | |
401 | // | |
402 | ||
403 | this.container.find('.calendar') | |
404 | .on('click.daterangepicker', '.prev', $.proxy(this.clickPrev, this)) | |
405 | .on('click.daterangepicker', '.next', $.proxy(this.clickNext, this)) | |
406 | .on('click.daterangepicker', 'td.available', $.proxy(this.clickDate, this)) | |
407 | .on('mouseenter.daterangepicker', 'td.available', $.proxy(this.hoverDate, this)) | |
408 | .on('mouseleave.daterangepicker', 'td.available', $.proxy(this.updateFormInputs, this)) | |
409 | .on('change.daterangepicker', 'select.yearselect', $.proxy(this.monthOrYearChanged, this)) | |
410 | .on('change.daterangepicker', 'select.monthselect', $.proxy(this.monthOrYearChanged, this)) | |
411 | .on('change.daterangepicker', 'select.hourselect,select.minuteselect,select.secondselect,select.ampmselect', $.proxy(this.timeChanged, this)) | |
412 | .on('click.daterangepicker', '.daterangepicker_input input', $.proxy(this.showCalendars, this)) | |
413 | //.on('keyup.daterangepicker', '.daterangepicker_input input', $.proxy(this.formInputsChanged, this)) | |
414 | .on('change.daterangepicker', '.daterangepicker_input input', $.proxy(this.formInputsChanged, this)); | |
415 | ||
416 | this.container.find('.ranges') | |
417 | .on('click.daterangepicker', 'button.applyBtn', $.proxy(this.clickApply, this)) | |
418 | .on('click.daterangepicker', 'button.cancelBtn', $.proxy(this.clickCancel, this)) | |
419 | .on('click.daterangepicker', 'li', $.proxy(this.clickRange, this)) | |
420 | .on('mouseenter.daterangepicker', 'li', $.proxy(this.hoverRange, this)) | |
421 | .on('mouseleave.daterangepicker', 'li', $.proxy(this.updateFormInputs, this)); | |
422 | ||
423 | if (this.element.is('input')) { | |
424 | this.element.on({ | |
425 | 'click.daterangepicker': $.proxy(this.show, this), | |
426 | 'focus.daterangepicker': $.proxy(this.show, this), | |
427 | 'keyup.daterangepicker': $.proxy(this.elementChanged, this), | |
428 | 'keydown.daterangepicker': $.proxy(this.keydown, this) | |
429 | }); | |
430 | } else { | |
431 | this.element.on('click.daterangepicker', $.proxy(this.toggle, this)); | |
432 | } | |
433 | ||
434 | // | |
435 | // if attached to a text input, set the initial value | |
436 | // | |
437 | ||
438 | if (this.element.is('input') && !this.singleDatePicker && this.autoUpdateInput) { | |
439 | this.element.val(this.startDate.format(this.locale.format) + this.locale.separator + this.endDate.format(this.locale.format)); | |
440 | this.element.trigger('change'); | |
441 | } else if (this.element.is('input') && this.autoUpdateInput) { | |
442 | this.element.val(this.startDate.format(this.locale.format)); | |
443 | this.element.trigger('change'); | |
444 | } | |
445 | ||
446 | }; | |
447 | ||
448 | DateRangePicker.prototype = { | |
449 | ||
450 | constructor: DateRangePicker, | |
451 | ||
452 | setStartDate: function(startDate) { | |
453 | if (typeof startDate === 'string') | |
454 | this.startDate = moment(startDate, this.locale.format); | |
455 | ||
456 | if (typeof startDate === 'object') | |
457 | this.startDate = moment(startDate); | |
458 | ||
459 | if (!this.timePicker) | |
460 | this.startDate = this.startDate.startOf('day'); | |
461 | ||
462 | if (this.timePicker && this.timePickerIncrement) | |
463 | this.startDate.minute(Math.round(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement); | |
464 | ||
465 | if (this.minDate && this.startDate.isBefore(this.minDate)) | |
466 | this.startDate = this.minDate; | |
467 | ||
468 | if (this.maxDate && this.startDate.isAfter(this.maxDate)) | |
469 | this.startDate = this.maxDate; | |
470 | ||
471 | if (!this.isShowing) | |
472 | this.updateElement(); | |
473 | ||
474 | this.updateMonthsInView(); | |
475 | }, | |
476 | ||
477 | setEndDate: function(endDate) { | |
478 | if (typeof endDate === 'string') | |
479 | this.endDate = moment(endDate, this.locale.format); | |
480 | ||
481 | if (typeof endDate === 'object') | |
482 | this.endDate = moment(endDate); | |
483 | ||
484 | if (!this.timePicker) | |
485 | this.endDate = this.endDate.endOf('day'); | |
486 | ||
487 | if (this.timePicker && this.timePickerIncrement) | |
488 | this.endDate.minute(Math.round(this.endDate.minute() / this.timePickerIncrement) * this.timePickerIncrement); | |
489 | ||
490 | if (this.endDate.isBefore(this.startDate)) | |
491 | this.endDate = this.startDate.clone(); | |
492 | ||
493 | if (this.maxDate && this.endDate.isAfter(this.maxDate)) | |
494 | this.endDate = this.maxDate; | |
495 | ||
496 | if (this.dateLimit && this.startDate.clone().add(this.dateLimit).isBefore(this.endDate)) | |
497 | this.endDate = this.startDate.clone().add(this.dateLimit); | |
498 | ||
499 | this.previousRightTime = this.endDate.clone(); | |
500 | ||
501 | if (!this.isShowing) | |
502 | this.updateElement(); | |
503 | ||
504 | this.updateMonthsInView(); | |
505 | }, | |
506 | ||
507 | isInvalidDate: function() { | |
508 | return false; | |
509 | }, | |
510 | ||
511 | updateView: function() { | |
512 | if (this.timePicker) { | |
513 | this.renderTimePicker('left'); | |
514 | this.renderTimePicker('right'); | |
515 | if (!this.endDate) { | |
516 | this.container.find('.right .calendar-time select').attr('disabled', 'disabled').addClass('disabled'); | |
517 | } else { | |
518 | this.container.find('.right .calendar-time select').removeAttr('disabled').removeClass('disabled'); | |
519 | } | |
520 | } | |
521 | if (this.endDate) { | |
522 | this.container.find('input[name="daterangepicker_end"]').removeClass('active'); | |
523 | this.container.find('input[name="daterangepicker_start"]').addClass('active'); | |
524 | } else { | |
525 | this.container.find('input[name="daterangepicker_end"]').addClass('active'); | |
526 | this.container.find('input[name="daterangepicker_start"]').removeClass('active'); | |
527 | } | |
528 | this.updateMonthsInView(); | |
529 | this.updateCalendars(); | |
530 | this.updateFormInputs(); | |
531 | }, | |
532 | ||
533 | updateMonthsInView: function() { | |
534 | if (this.endDate) { | |
535 | ||
536 | //if both dates are visible already, do nothing | |
537 | if (!this.singleDatePicker && this.leftCalendar.month && this.rightCalendar.month && | |
538 | (this.startDate.format('YYYY-MM') == this.leftCalendar.month.format('YYYY-MM') || this.startDate.format('YYYY-MM') == this.rightCalendar.month.format('YYYY-MM')) | |
539 | && | |
540 | (this.endDate.format('YYYY-MM') == this.leftCalendar.month.format('YYYY-MM') || this.endDate.format('YYYY-MM') == this.rightCalendar.month.format('YYYY-MM')) | |
541 | ) { | |
542 | return; | |
543 | } | |
544 | ||
545 | this.leftCalendar.month = this.startDate.clone().date(2); | |
546 | if (!this.linkedCalendars && (this.endDate.month() != this.startDate.month() || this.endDate.year() != this.startDate.year())) { | |
547 | this.rightCalendar.month = this.endDate.clone().date(2); | |
548 | } else { | |
549 | this.rightCalendar.month = this.startDate.clone().date(2).add(1, 'month'); | |
550 | } | |
551 | ||
552 | } else { | |
553 | if (this.leftCalendar.month.format('YYYY-MM') != this.startDate.format('YYYY-MM') && this.rightCalendar.month.format('YYYY-MM') != this.startDate.format('YYYY-MM')) { | |
554 | this.leftCalendar.month = this.startDate.clone().date(2); | |
555 | this.rightCalendar.month = this.startDate.clone().date(2).add(1, 'month'); | |
556 | } | |
557 | } | |
558 | }, | |
559 | ||
560 | updateCalendars: function() { | |
561 | ||
562 | if (this.timePicker) { | |
563 | var hour, minute, second; | |
564 | if (this.endDate) { | |
565 | hour = parseInt(this.container.find('.left .hourselect').val(), 10); | |
566 | minute = parseInt(this.container.find('.left .minuteselect').val(), 10); | |
567 | second = this.timePickerSeconds ? parseInt(this.container.find('.left .secondselect').val(), 10) : 0; | |
568 | if (!this.timePicker24Hour) { | |
569 | var ampm = this.container.find('.left .ampmselect').val(); | |
570 | if (ampm === 'PM' && hour < 12) | |
571 | hour += 12; | |
572 | if (ampm === 'AM' && hour === 12) | |
573 | hour = 0; | |
574 | } | |
575 | } else { | |
576 | hour = parseInt(this.container.find('.right .hourselect').val(), 10); | |
577 | minute = parseInt(this.container.find('.right .minuteselect').val(), 10); | |
578 | second = this.timePickerSeconds ? parseInt(this.container.find('.right .secondselect').val(), 10) : 0; | |
579 | if (!this.timePicker24Hour) { | |
580 | var ampm = this.container.find('.right .ampmselect').val(); | |
581 | if (ampm === 'PM' && hour < 12) | |
582 | hour += 12; | |
583 | if (ampm === 'AM' && hour === 12) | |
584 | hour = 0; | |
585 | } | |
586 | } | |
587 | this.leftCalendar.month.hour(hour).minute(minute).second(second); | |
588 | this.rightCalendar.month.hour(hour).minute(minute).second(second); | |
589 | } | |
590 | ||
591 | this.renderCalendar('left'); | |
592 | this.renderCalendar('right'); | |
593 | ||
594 | //highlight any predefined range matching the current start and end dates | |
595 | this.container.find('.ranges li').removeClass('active'); | |
596 | if (this.endDate == null) return; | |
597 | ||
598 | this.calculateChosenLabel(); | |
599 | }, | |
600 | ||
601 | renderCalendar: function(side) { | |
602 | ||
603 | // | |
604 | // Build the matrix of dates that will populate the calendar | |
605 | // | |
606 | ||
607 | var calendar = side == 'left' ? this.leftCalendar : this.rightCalendar; | |
608 | var month = calendar.month.month(); | |
609 | var year = calendar.month.year(); | |
610 | var hour = calendar.month.hour(); | |
611 | var minute = calendar.month.minute(); | |
612 | var second = calendar.month.second(); | |
613 | var daysInMonth = moment([year, month]).daysInMonth(); | |
614 | var firstDay = moment([year, month, 1]); | |
615 | var lastDay = moment([year, month, daysInMonth]); | |
616 | var lastMonth = moment(firstDay).subtract(1, 'month').month(); | |
617 | var lastYear = moment(firstDay).subtract(1, 'month').year(); | |
618 | var daysInLastMonth = moment([lastYear, lastMonth]).daysInMonth(); | |
619 | var dayOfWeek = firstDay.day(); | |
620 | ||
621 | //initialize a 6 rows x 7 columns array for the calendar | |
622 | var calendar = []; | |
623 | calendar.firstDay = firstDay; | |
624 | calendar.lastDay = lastDay; | |
625 | ||
626 | for (var i = 0; i < 6; i++) { | |
627 | calendar[i] = []; | |
628 | } | |
629 | ||
630 | //populate the calendar with date objects | |
631 | var startDay = daysInLastMonth - dayOfWeek + this.locale.firstDay + 1; | |
632 | if (startDay > daysInLastMonth) | |
633 | startDay -= 7; | |
634 | ||
635 | if (dayOfWeek == this.locale.firstDay) | |
636 | startDay = daysInLastMonth - 6; | |
637 | ||
638 | var curDate = moment([lastYear, lastMonth, startDay, 12, minute, second]); | |
639 | ||
640 | var col, row; | |
641 | for (var i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add(24, 'hour')) { | |
642 | if (i > 0 && col % 7 === 0) { | |
643 | col = 0; | |
644 | row++; | |
645 | } | |
646 | calendar[row][col] = curDate.clone().hour(hour).minute(minute).second(second); | |
647 | curDate.hour(12); | |
648 | ||
649 | if (this.minDate && calendar[row][col].format('YYYY-MM-DD') == this.minDate.format('YYYY-MM-DD') && calendar[row][col].isBefore(this.minDate) && side == 'left') { | |
650 | calendar[row][col] = this.minDate.clone(); | |
651 | } | |
652 | ||
653 | if (this.maxDate && calendar[row][col].format('YYYY-MM-DD') == this.maxDate.format('YYYY-MM-DD') && calendar[row][col].isAfter(this.maxDate) && side == 'right') { | |
654 | calendar[row][col] = this.maxDate.clone(); | |
655 | } | |
656 | ||
657 | } | |
658 | ||
659 | //make the calendar object available to hoverDate/clickDate | |
660 | if (side == 'left') { | |
661 | this.leftCalendar.calendar = calendar; | |
662 | } else { | |
663 | this.rightCalendar.calendar = calendar; | |
664 | } | |
665 | ||
666 | // | |
667 | // Display the calendar | |
668 | // | |
669 | ||
670 | var minDate = side == 'left' ? this.minDate : this.startDate; | |
671 | var maxDate = this.maxDate; | |
672 | var selected = side == 'left' ? this.startDate : this.endDate; | |
673 | ||
674 | var html = '<table class="table-condensed">'; | |
675 | html += '<thead>'; | |
676 | html += '<tr>'; | |
677 | ||
678 | // add empty cell for week number | |
679 | if (this.showWeekNumbers || this.showISOWeekNumbers) | |
680 | html += '<th></th>'; | |
681 | ||
682 | if ((!minDate || minDate.isBefore(calendar.firstDay)) && (!this.linkedCalendars || side == 'left')) { | |
683 | html += '<th class="prev available"><i class="fa fa-chevron-left glyphicon glyphicon-chevron-left"></i></th>'; | |
684 | } else { | |
685 | html += '<th></th>'; | |
686 | } | |
687 | ||
688 | var dateHtml = this.locale.monthNames[calendar[1][1].month()] + calendar[1][1].format(" YYYY"); | |
689 | ||
690 | if (this.showDropdowns) { | |
691 | var currentMonth = calendar[1][1].month(); | |
692 | var currentYear = calendar[1][1].year(); | |
693 | var maxYear = (maxDate && maxDate.year()) || (currentYear + 5); | |
694 | var minYear = (minDate && minDate.year()) || (currentYear - 50); | |
695 | var inMinYear = currentYear == minYear; | |
696 | var inMaxYear = currentYear == maxYear; | |
697 | ||
698 | var monthHtml = '<select class="monthselect">'; | |
699 | for (var m = 0; m < 12; m++) { | |
700 | if ((!inMinYear || m >= minDate.month()) && (!inMaxYear || m <= maxDate.month())) { | |
701 | monthHtml += "<option value='" + m + "'" + | |
702 | (m === currentMonth ? " selected='selected'" : "") + | |
703 | ">" + this.locale.monthNames[m] + "</option>"; | |
704 | } else { | |
705 | monthHtml += "<option value='" + m + "'" + | |
706 | (m === currentMonth ? " selected='selected'" : "") + | |
707 | " disabled='disabled'>" + this.locale.monthNames[m] + "</option>"; | |
708 | } | |
709 | } | |
710 | monthHtml += "</select>"; | |
711 | ||
712 | var yearHtml = '<select class="yearselect">'; | |
713 | for (var y = minYear; y <= maxYear; y++) { | |
714 | yearHtml += '<option value="' + y + '"' + | |
715 | (y === currentYear ? ' selected="selected"' : '') + | |
716 | '>' + y + '</option>'; | |
717 | } | |
718 | yearHtml += '</select>'; | |
719 | ||
720 | dateHtml = monthHtml + yearHtml; | |
721 | } | |
722 | ||
723 | html += '<th colspan="5" class="month">' + dateHtml + '</th>'; | |
724 | if ((!maxDate || maxDate.isAfter(calendar.lastDay)) && (!this.linkedCalendars || side == 'right' || this.singleDatePicker)) { | |
725 | html += '<th class="next available"><i class="fa fa-chevron-right glyphicon glyphicon-chevron-right"></i></th>'; | |
726 | } else { | |
727 | html += '<th></th>'; | |
728 | } | |
729 | ||
730 | html += '</tr>'; | |
731 | html += '<tr>'; | |
732 | ||
733 | // add week number label | |
734 | if (this.showWeekNumbers || this.showISOWeekNumbers) | |
735 | html += '<th class="week">' + this.locale.weekLabel + '</th>'; | |
736 | ||
737 | $.each(this.locale.daysOfWeek, function(index, dayOfWeek) { | |
738 | html += '<th>' + dayOfWeek + '</th>'; | |
739 | }); | |
740 | ||
741 | html += '</tr>'; | |
742 | html += '</thead>'; | |
743 | html += '<tbody>'; | |
744 | ||
745 | //adjust maxDate to reflect the dateLimit setting in order to | |
746 | //grey out end dates beyond the dateLimit | |
747 | if (this.endDate == null && this.dateLimit) { | |
748 | var maxLimit = this.startDate.clone().add(this.dateLimit).endOf('day'); | |
749 | if (!maxDate || maxLimit.isBefore(maxDate)) { | |
750 | maxDate = maxLimit; | |
751 | } | |
752 | } | |
753 | ||
754 | for (var row = 0; row < 6; row++) { | |
755 | html += '<tr>'; | |
756 | ||
757 | // add week number | |
758 | if (this.showWeekNumbers) | |
759 | html += '<td class="week">' + calendar[row][0].week() + '</td>'; | |
760 | else if (this.showISOWeekNumbers) | |
761 | html += '<td class="week">' + calendar[row][0].isoWeek() + '</td>'; | |
762 | ||
763 | for (var col = 0; col < 7; col++) { | |
764 | ||
765 | var classes = []; | |
766 | ||
767 | //highlight today's date | |
768 | if (calendar[row][col].isSame(new Date(), "day")) | |
769 | classes.push('today'); | |
770 | ||
771 | //highlight weekends | |
772 | if (calendar[row][col].isoWeekday() > 5) | |
773 | classes.push('weekend'); | |
774 | ||
775 | //grey out the dates in other months displayed at beginning and end of this calendar | |
776 | if (calendar[row][col].month() != calendar[1][1].month()) | |
777 | classes.push('off'); | |
778 | ||
779 | //don't allow selection of dates before the minimum date | |
780 | if (this.minDate && calendar[row][col].isBefore(this.minDate, 'day')) | |
781 | classes.push('off', 'disabled'); | |
782 | ||
783 | //don't allow selection of dates after the maximum date | |
784 | if (maxDate && calendar[row][col].isAfter(maxDate, 'day')) | |
785 | classes.push('off', 'disabled'); | |
786 | ||
787 | //don't allow selection of date if a custom function decides it's invalid | |
788 | if (this.isInvalidDate(calendar[row][col])) | |
789 | classes.push('off', 'disabled'); | |
790 | ||
791 | //highlight the currently selected start date | |
792 | if (calendar[row][col].format('YYYY-MM-DD') == this.startDate.format('YYYY-MM-DD')) | |
793 | classes.push('active', 'start-date'); | |
794 | ||
795 | //highlight the currently selected end date | |
796 | if (this.endDate != null && calendar[row][col].format('YYYY-MM-DD') == this.endDate.format('YYYY-MM-DD')) | |
797 | classes.push('active', 'end-date'); | |
798 | ||
799 | //highlight dates in-between the selected dates | |
800 | if (this.endDate != null && calendar[row][col] > this.startDate && calendar[row][col] < this.endDate) | |
801 | classes.push('in-range'); | |
802 | ||
803 | var cname = '', disabled = false; | |
804 | for (var i = 0; i < classes.length; i++) { | |
805 | cname += classes[i] + ' '; | |
806 | if (classes[i] == 'disabled') | |
807 | disabled = true; | |
808 | } | |
809 | if (!disabled) | |
810 | cname += 'available'; | |
811 | ||
812 | html += '<td class="' + cname.replace(/^\s+|\s+$/g, '') + '" data-title="' + 'r' + row + 'c' + col + '">' + calendar[row][col].date() + '</td>'; | |
813 | ||
814 | } | |
815 | html += '</tr>'; | |
816 | } | |
817 | ||
818 | html += '</tbody>'; | |
819 | html += '</table>'; | |
820 | ||
821 | this.container.find('.calendar.' + side + ' .calendar-table').html(html); | |
822 | ||
823 | }, | |
824 | ||
825 | renderTimePicker: function(side) { | |
826 | ||
827 | var html, selected, minDate, maxDate = this.maxDate; | |
828 | ||
829 | if (this.dateLimit && (!this.maxDate || this.startDate.clone().add(this.dateLimit).isAfter(this.maxDate))) | |
830 | maxDate = this.startDate.clone().add(this.dateLimit); | |
831 | ||
832 | if (side == 'left') { | |
833 | selected = this.startDate.clone(); | |
834 | minDate = this.minDate; | |
835 | } else if (side == 'right') { | |
836 | selected = this.endDate ? this.endDate.clone() : this.previousRightTime.clone(); | |
837 | minDate = this.startDate; | |
838 | ||
839 | //Preserve the time already selected | |
840 | var timeSelector = this.container.find('.calendar.right .calendar-time div'); | |
841 | if (timeSelector.html() != '') { | |
842 | ||
843 | selected.hour(timeSelector.find('.hourselect option:selected').val() || selected.hour()); | |
844 | selected.minute(timeSelector.find('.minuteselect option:selected').val() || selected.minute()); | |
845 | selected.second(timeSelector.find('.secondselect option:selected').val() || selected.second()); | |
846 | ||
847 | if (!this.timePicker24Hour) { | |
848 | var ampm = timeSelector.find('.ampmselect option:selected').val(); | |
849 | if (ampm === 'PM' && selected.hour() < 12) | |
850 | selected.hour(selected.hour() + 12); | |
851 | if (ampm === 'AM' && selected.hour() === 12) | |
852 | selected.hour(0); | |
853 | } | |
854 | ||
855 | if (selected.isBefore(this.startDate)) | |
856 | selected = this.startDate.clone(); | |
857 | ||
858 | if (selected.isAfter(maxDate)) | |
859 | selected = maxDate.clone(); | |
860 | ||
861 | } | |
862 | } | |
863 | ||
864 | // | |
865 | // hours | |
866 | // | |
867 | ||
868 | html = '<select class="hourselect">'; | |
869 | ||
870 | var start = this.timePicker24Hour ? 0 : 1; | |
871 | var end = this.timePicker24Hour ? 23 : 12; | |
872 | ||
873 | for (var i = start; i <= end; i++) { | |
874 | var i_in_24 = i; | |
875 | if (!this.timePicker24Hour) | |
876 | i_in_24 = selected.hour() >= 12 ? (i == 12 ? 12 : i + 12) : (i == 12 ? 0 : i); | |
877 | ||
878 | var time = selected.clone().hour(i_in_24); | |
879 | var disabled = false; | |
880 | if (minDate && time.minute(59).isBefore(minDate)) | |
881 | disabled = true; | |
882 | if (maxDate && time.minute(0).isAfter(maxDate)) | |
883 | disabled = true; | |
884 | ||
885 | if (i_in_24 == selected.hour() && !disabled) { | |
886 | html += '<option value="' + i + '" selected="selected">' + i + '</option>'; | |
887 | } else if (disabled) { | |
888 | html += '<option value="' + i + '" disabled="disabled" class="disabled">' + i + '</option>'; | |
889 | } else { | |
890 | html += '<option value="' + i + '">' + i + '</option>'; | |
891 | } | |
892 | } | |
893 | ||
894 | html += '</select> '; | |
895 | ||
896 | // | |
897 | // minutes | |
898 | // | |
899 | ||
900 | html += ': <select class="minuteselect">'; | |
901 | ||
902 | for (var i = 0; i < 60; i += this.timePickerIncrement) { | |
903 | var padded = i < 10 ? '0' + i : i; | |
904 | var time = selected.clone().minute(i); | |
905 | ||
906 | var disabled = false; | |
907 | if (minDate && time.second(59).isBefore(minDate)) | |
908 | disabled = true; | |
909 | if (maxDate && time.second(0).isAfter(maxDate)) | |
910 | disabled = true; | |
911 | ||
912 | if (selected.minute() == i && !disabled) { | |
913 | html += '<option value="' + i + '" selected="selected">' + padded + '</option>'; | |
914 | } else if (disabled) { | |
915 | html += '<option value="' + i + '" disabled="disabled" class="disabled">' + padded + '</option>'; | |
916 | } else { | |
917 | html += '<option value="' + i + '">' + padded + '</option>'; | |
918 | } | |
919 | } | |
920 | ||
921 | html += '</select> '; | |
922 | ||
923 | // | |
924 | // seconds | |
925 | // | |
926 | ||
927 | if (this.timePickerSeconds) { | |
928 | html += ': <select class="secondselect">'; | |
929 | ||
930 | for (var i = 0; i < 60; i++) { | |
931 | var padded = i < 10 ? '0' + i : i; | |
932 | var time = selected.clone().second(i); | |
933 | ||
934 | var disabled = false; | |
935 | if (minDate && time.isBefore(minDate)) | |
936 | disabled = true; | |
937 | if (maxDate && time.isAfter(maxDate)) | |
938 | disabled = true; | |
939 | ||
940 | if (selected.second() == i && !disabled) { | |
941 | html += '<option value="' + i + '" selected="selected">' + padded + '</option>'; | |
942 | } else if (disabled) { | |
943 | html += '<option value="' + i + '" disabled="disabled" class="disabled">' + padded + '</option>'; | |
944 | } else { | |
945 | html += '<option value="' + i + '">' + padded + '</option>'; | |
946 | } | |
947 | } | |
948 | ||
949 | html += '</select> '; | |
950 | } | |
951 | ||
952 | // | |
953 | // AM/PM | |
954 | // | |
955 | ||
956 | if (!this.timePicker24Hour) { | |
957 | html += '<select class="ampmselect">'; | |
958 | ||
959 | var am_html = ''; | |
960 | var pm_html = ''; | |
961 | ||
962 | if (minDate && selected.clone().hour(12).minute(0).second(0).isBefore(minDate)) | |
963 | am_html = ' disabled="disabled" class="disabled"'; | |
964 | ||
965 | if (maxDate && selected.clone().hour(0).minute(0).second(0).isAfter(maxDate)) | |
966 | pm_html = ' disabled="disabled" class="disabled"'; | |
967 | ||
968 | if (selected.hour() >= 12) { | |
969 | html += '<option value="AM"' + am_html + '>AM</option><option value="PM" selected="selected"' + pm_html + '>PM</option>'; | |
970 | } else { | |
971 | html += '<option value="AM" selected="selected"' + am_html + '>AM</option><option value="PM"' + pm_html + '>PM</option>'; | |
972 | } | |
973 | ||
974 | html += '</select>'; | |
975 | } | |
976 | ||
977 | this.container.find('.calendar.' + side + ' .calendar-time div').html(html); | |
978 | ||
979 | }, | |
980 | ||
981 | updateFormInputs: function() { | |
982 | ||
983 | //ignore mouse movements while an above-calendar text input has focus | |
984 | if (this.container.find('input[name=daterangepicker_start]').is(":focus") || this.container.find('input[name=daterangepicker_end]').is(":focus")) | |
985 | return; | |
986 | ||
987 | this.container.find('input[name=daterangepicker_start]').val(this.startDate.format(this.locale.format)); | |
988 | if (this.endDate) | |
989 | this.container.find('input[name=daterangepicker_end]').val(this.endDate.format(this.locale.format)); | |
990 | ||
991 | if (this.singleDatePicker || (this.endDate && (this.startDate.isBefore(this.endDate) || this.startDate.isSame(this.endDate)))) { | |
992 | this.container.find('button.applyBtn').removeAttr('disabled'); | |
993 | } else { | |
994 | this.container.find('button.applyBtn').attr('disabled', 'disabled'); | |
995 | } | |
996 | ||
997 | }, | |
998 | ||
999 | move: function() { | |
1000 | var parentOffset = { top: 0, left: 0 }, | |
1001 | containerTop; | |
1002 | var parentRightEdge = $(window).width(); | |
1003 | if (!this.parentEl.is('body')) { | |
1004 | parentOffset = { | |
1005 | top: this.parentEl.offset().top - this.parentEl.scrollTop(), | |
1006 | left: this.parentEl.offset().left - this.parentEl.scrollLeft() | |
1007 | }; | |
1008 | parentRightEdge = this.parentEl[0].clientWidth + this.parentEl.offset().left; | |
1009 | } | |
1010 | ||
1011 | if (this.drops == 'up') | |
1012 | containerTop = this.element.offset().top - this.container.outerHeight() - parentOffset.top; | |
1013 | else | |
1014 | containerTop = this.element.offset().top + this.element.outerHeight() - parentOffset.top; | |
1015 | this.container[this.drops == 'up' ? 'addClass' : 'removeClass']('dropup'); | |
1016 | ||
1017 | if (this.opens == 'left') { | |
1018 | this.container.css({ | |
1019 | top: containerTop, | |
1020 | right: parentRightEdge - this.element.offset().left - this.element.outerWidth(), | |
1021 | left: 'auto' | |
1022 | }); | |
1023 | if (this.container.offset().left < 0) { | |
1024 | this.container.css({ | |
1025 | right: 'auto', | |
1026 | left: 9 | |
1027 | }); | |
1028 | } | |
1029 | } else if (this.opens == 'center') { | |
1030 | this.container.css({ | |
1031 | top: containerTop, | |
1032 | left: this.element.offset().left - parentOffset.left + this.element.outerWidth() / 2 | |
1033 | - this.container.outerWidth() / 2, | |
1034 | right: 'auto' | |
1035 | }); | |
1036 | if (this.container.offset().left < 0) { | |
1037 | this.container.css({ | |
1038 | right: 'auto', | |
1039 | left: 9 | |
1040 | }); | |
1041 | } | |
1042 | } else { | |
1043 | this.container.css({ | |
1044 | top: containerTop, | |
1045 | left: this.element.offset().left - parentOffset.left, | |
1046 | right: 'auto' | |
1047 | }); | |
1048 | if (this.container.offset().left + this.container.outerWidth() > $(window).width()) { | |
1049 | this.container.css({ | |
1050 | left: 'auto', | |
1051 | right: 0 | |
1052 | }); | |
1053 | } | |
1054 | } | |
1055 | }, | |
1056 | ||
1057 | show: function(e) { | |
1058 | if (this.isShowing) return; | |
1059 | ||
1060 | // Create a click proxy that is private to this instance of datepicker, for unbinding | |
1061 | this._outsideClickProxy = $.proxy(function(e) { this.outsideClick(e); }, this); | |
1062 | ||
1063 | // Bind global datepicker mousedown for hiding and | |
1064 | $(document) | |
1065 | .on('mousedown.daterangepicker', this._outsideClickProxy) | |
1066 | // also support mobile devices | |
1067 | .on('touchend.daterangepicker', this._outsideClickProxy) | |
1068 | // also explicitly play nice with Bootstrap dropdowns, which stopPropagation when clicking them | |
1069 | .on('click.daterangepicker', '[data-toggle=dropdown]', this._outsideClickProxy) | |
1070 | // and also close when focus changes to outside the picker (eg. tabbing between controls) | |
1071 | .on('focusin.daterangepicker', this._outsideClickProxy); | |
1072 | ||
1073 | // Reposition the picker if the window is resized while it's open | |
1074 | $(window).on('resize.daterangepicker', $.proxy(function(e) { this.move(e); }, this)); | |
1075 | ||
1076 | this.oldStartDate = this.startDate.clone(); | |
1077 | this.oldEndDate = this.endDate.clone(); | |
1078 | this.previousRightTime = this.endDate.clone(); | |
1079 | ||
1080 | this.updateView(); | |
1081 | this.container.show(); | |
1082 | this.move(); | |
1083 | this.element.trigger('show.daterangepicker', this); | |
1084 | this.isShowing = true; | |
1085 | }, | |
1086 | ||
1087 | hide: function(e) { | |
1088 | if (!this.isShowing) return; | |
1089 | ||
1090 | //incomplete date selection, revert to last values | |
1091 | if (!this.endDate) { | |
1092 | this.startDate = this.oldStartDate.clone(); | |
1093 | this.endDate = this.oldEndDate.clone(); | |
1094 | } | |
1095 | ||
1096 | //if a new date range was selected, invoke the user callback function | |
1097 | if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate)) | |
1098 | this.callback(this.startDate, this.endDate, this.chosenLabel); | |
1099 | ||
1100 | //if picker is attached to a text input, update it | |
1101 | this.updateElement(); | |
1102 | ||
1103 | $(document).off('.daterangepicker'); | |
1104 | $(window).off('.daterangepicker'); | |
1105 | this.container.hide(); | |
1106 | this.element.trigger('hide.daterangepicker', this); | |
1107 | this.isShowing = false; | |
1108 | }, | |
1109 | ||
1110 | toggle: function(e) { | |
1111 | if (this.isShowing) { | |
1112 | this.hide(); | |
1113 | } else { | |
1114 | this.show(); | |
1115 | } | |
1116 | }, | |
1117 | ||
1118 | outsideClick: function(e) { | |
1119 | var target = $(e.target); | |
1120 | // if the page is clicked anywhere except within the daterangerpicker/button | |
1121 | // itself then call this.hide() | |
1122 | if ( | |
1123 | // ie modal dialog fix | |
1124 | e.type == "focusin" || | |
1125 | target.closest(this.element).length || | |
1126 | target.closest(this.container).length || | |
1127 | target.closest('.calendar-table').length | |
1128 | ) return; | |
1129 | this.hide(); | |
1130 | }, | |
1131 | ||
1132 | showCalendars: function() { | |
1133 | this.container.addClass('show-calendar'); | |
1134 | this.move(); | |
1135 | this.element.trigger('showCalendar.daterangepicker', this); | |
1136 | }, | |
1137 | ||
1138 | hideCalendars: function() { | |
1139 | this.container.removeClass('show-calendar'); | |
1140 | this.element.trigger('hideCalendar.daterangepicker', this); | |
1141 | }, | |
1142 | ||
1143 | hoverRange: function(e) { | |
1144 | ||
1145 | //ignore mouse movements while an above-calendar text input has focus | |
1146 | if (this.container.find('input[name=daterangepicker_start]').is(":focus") || this.container.find('input[name=daterangepicker_end]').is(":focus")) | |
1147 | return; | |
1148 | ||
1149 | var label = e.target.innerHTML; | |
1150 | if (label == this.locale.customRangeLabel) { | |
1151 | this.updateView(); | |
1152 | } else { | |
1153 | var dates = this.ranges[label]; | |
1154 | this.container.find('input[name=daterangepicker_start]').val(dates[0].format(this.locale.format)); | |
1155 | this.container.find('input[name=daterangepicker_end]').val(dates[1].format(this.locale.format)); | |
1156 | } | |
1157 | ||
1158 | }, | |
1159 | ||
1160 | clickRange: function(e) { | |
1161 | var label = e.target.innerHTML; | |
1162 | this.chosenLabel = label; | |
1163 | if (label == this.locale.customRangeLabel) { | |
1164 | this.showCalendars(); | |
1165 | } else { | |
1166 | var dates = this.ranges[label]; | |
1167 | this.startDate = dates[0]; | |
1168 | this.endDate = dates[1]; | |
1169 | ||
1170 | if (!this.timePicker) { | |
1171 | this.startDate.startOf('day'); | |
1172 | this.endDate.endOf('day'); | |
1173 | } | |
1174 | ||
1175 | if (!this.alwaysShowCalendars) | |
1176 | this.hideCalendars(); | |
1177 | this.clickApply(); | |
1178 | } | |
1179 | }, | |
1180 | ||
1181 | clickPrev: function(e) { | |
1182 | var cal = $(e.target).parents('.calendar'); | |
1183 | if (cal.hasClass('left')) { | |
1184 | this.leftCalendar.month.subtract(1, 'month'); | |
1185 | if (this.linkedCalendars) | |
1186 | this.rightCalendar.month.subtract(1, 'month'); | |
1187 | } else { | |
1188 | this.rightCalendar.month.subtract(1, 'month'); | |
1189 | } | |
1190 | this.updateCalendars(); | |
1191 | }, | |
1192 | ||
1193 | clickNext: function(e) { | |
1194 | var cal = $(e.target).parents('.calendar'); | |
1195 | if (cal.hasClass('left')) { | |
1196 | this.leftCalendar.month.add(1, 'month'); | |
1197 | } else { | |
1198 | this.rightCalendar.month.add(1, 'month'); | |
1199 | if (this.linkedCalendars) | |
1200 | this.leftCalendar.month.add(1, 'month'); | |
1201 | } | |
1202 | this.updateCalendars(); | |
1203 | }, | |
1204 | ||
1205 | hoverDate: function(e) { | |
1206 | ||
1207 | //ignore mouse movements while an above-calendar text input has focus | |
1208 | if (this.container.find('input[name=daterangepicker_start]').is(":focus") || this.container.find('input[name=daterangepicker_end]').is(":focus")) | |
1209 | return; | |
1210 | ||
1211 | //ignore dates that can't be selected | |
1212 | if (!$(e.target).hasClass('available')) return; | |
1213 | ||
1214 | //have the text inputs above calendars reflect the date being hovered over | |
1215 | var title = $(e.target).attr('data-title'); | |
1216 | var row = title.substr(1, 1); | |
1217 | var col = title.substr(3, 1); | |
1218 | var cal = $(e.target).parents('.calendar'); | |
1219 | var date = cal.hasClass('left') ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col]; | |
1220 | ||
1221 | if (this.endDate) { | |
1222 | this.container.find('input[name=daterangepicker_start]').val(date.format(this.locale.format)); | |
1223 | } else { | |
1224 | this.container.find('input[name=daterangepicker_end]').val(date.format(this.locale.format)); | |
1225 | } | |
1226 | ||
1227 | //highlight the dates between the start date and the date being hovered as a potential end date | |
1228 | var leftCalendar = this.leftCalendar; | |
1229 | var rightCalendar = this.rightCalendar; | |
1230 | var startDate = this.startDate; | |
1231 | if (!this.endDate) { | |
1232 | this.container.find('.calendar td').each(function(index, el) { | |
1233 | ||
1234 | //skip week numbers, only look at dates | |
1235 | if ($(el).hasClass('week')) return; | |
1236 | ||
1237 | var title = $(el).attr('data-title'); | |
1238 | var row = title.substr(1, 1); | |
1239 | var col = title.substr(3, 1); | |
1240 | var cal = $(el).parents('.calendar'); | |
1241 | var dt = cal.hasClass('left') ? leftCalendar.calendar[row][col] : rightCalendar.calendar[row][col]; | |
1242 | ||
1243 | if (dt.isAfter(startDate) && dt.isBefore(date)) { | |
1244 | $(el).addClass('in-range'); | |
1245 | } else { | |
1246 | $(el).removeClass('in-range'); | |
1247 | } | |
1248 | ||
1249 | }); | |
1250 | } | |
1251 | ||
1252 | }, | |
1253 | ||
1254 | clickDate: function(e) { | |
1255 | ||
1256 | if (!$(e.target).hasClass('available')) return; | |
1257 | ||
1258 | var title = $(e.target).attr('data-title'); | |
1259 | var row = title.substr(1, 1); | |
1260 | var col = title.substr(3, 1); | |
1261 | var cal = $(e.target).parents('.calendar'); | |
1262 | var date = cal.hasClass('left') ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col]; | |
1263 | ||
1264 | // | |
1265 | // this function needs to do a few things: | |
1266 | // * alternate between selecting a start and end date for the range, | |
1267 | // * if the time picker is enabled, apply the hour/minute/second from the select boxes to the clicked date | |
1268 | // * if autoapply is enabled, and an end date was chosen, apply the selection | |
1269 | // * if single date picker mode, and time picker isn't enabled, apply the selection immediately | |
1270 | // | |
1271 | ||
1272 | if (this.endDate || date.isBefore(this.startDate, 'day')) { | |
1273 | if (this.timePicker) { | |
1274 | var hour = parseInt(this.container.find('.left .hourselect').val(), 10); | |
1275 | if (!this.timePicker24Hour) { | |
1276 | var ampm = this.container.find('.left .ampmselect').val(); | |
1277 | if (ampm === 'PM' && hour < 12) | |
1278 | hour += 12; | |
1279 | if (ampm === 'AM' && hour === 12) | |
1280 | hour = 0; | |
1281 | } | |
1282 | var minute = parseInt(this.container.find('.left .minuteselect').val(), 10); | |
1283 | var second = this.timePickerSeconds ? parseInt(this.container.find('.left .secondselect').val(), 10) : 0; | |
1284 | date = date.clone().hour(hour).minute(minute).second(second); | |
1285 | } | |
1286 | this.endDate = null; | |
1287 | this.setStartDate(date.clone()); | |
1288 | } else if (!this.endDate && date.isBefore(this.startDate)) { | |
1289 | //special case: clicking the same date for start/end, | |
1290 | //but the time of the end date is before the start date | |
1291 | this.setEndDate(this.startDate.clone()); | |
1292 | } else { | |
1293 | if (this.timePicker) { | |
1294 | var hour = parseInt(this.container.find('.right .hourselect').val(), 10); | |
1295 | if (!this.timePicker24Hour) { | |
1296 | var ampm = this.container.find('.right .ampmselect').val(); | |
1297 | if (ampm === 'PM' && hour < 12) | |
1298 | hour += 12; | |
1299 | if (ampm === 'AM' && hour === 12) | |
1300 | hour = 0; | |
1301 | } | |
1302 | var minute = parseInt(this.container.find('.right .minuteselect').val(), 10); | |
1303 | var second = this.timePickerSeconds ? parseInt(this.container.find('.right .secondselect').val(), 10) : 0; | |
1304 | date = date.clone().hour(hour).minute(minute).second(second); | |
1305 | } | |
1306 | this.setEndDate(date.clone()); | |
1307 | if (this.autoApply) { | |
1308 | this.calculateChosenLabel(); | |
1309 | this.clickApply(); | |
1310 | } | |
1311 | } | |
1312 | ||
1313 | if (this.singleDatePicker) { | |
1314 | this.setEndDate(this.startDate); | |
1315 | if (!this.timePicker) | |
1316 | this.clickApply(); | |
1317 | } | |
1318 | ||
1319 | this.updateView(); | |
1320 | ||
1321 | }, | |
1322 | ||
1323 | calculateChosenLabel: function() { | |
1324 | var customRange = true; | |
1325 | var i = 0; | |
1326 | for (var range in this.ranges) { | |
1327 | if (this.timePicker) { | |
1328 | if (this.startDate.isSame(this.ranges[range][0]) && this.endDate.isSame(this.ranges[range][1])) { | |
1329 | customRange = false; | |
1330 | this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')').addClass('active').html(); | |
1331 | break; | |
1332 | } | |
1333 | } else { | |
1334 | //ignore times when comparing dates if time picker is not enabled | |
1335 | 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')) { | |
1336 | customRange = false; | |
1337 | this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')').addClass('active').html(); | |
1338 | break; | |
1339 | } | |
1340 | } | |
1341 | i++; | |
1342 | } | |
1343 | if (customRange) { | |
1344 | this.chosenLabel = this.container.find('.ranges li:last').addClass('active').html(); | |
1345 | this.showCalendars(); | |
1346 | } | |
1347 | }, | |
1348 | ||
1349 | clickApply: function(e) { | |
1350 | this.hide(); | |
1351 | this.element.trigger('apply.daterangepicker', this); | |
1352 | }, | |
1353 | ||
1354 | clickCancel: function(e) { | |
1355 | this.startDate = this.oldStartDate; | |
1356 | this.endDate = this.oldEndDate; | |
1357 | this.hide(); | |
1358 | this.element.trigger('cancel.daterangepicker', this); | |
1359 | }, | |
1360 | ||
1361 | monthOrYearChanged: function(e) { | |
1362 | var isLeft = $(e.target).closest('.calendar').hasClass('left'), | |
1363 | leftOrRight = isLeft ? 'left' : 'right', | |
1364 | cal = this.container.find('.calendar.'+leftOrRight); | |
1365 | ||
1366 | // Month must be Number for new moment versions | |
1367 | var month = parseInt(cal.find('.monthselect').val(), 10); | |
1368 | var year = cal.find('.yearselect').val(); | |
1369 | ||
1370 | if (!isLeft) { | |
1371 | if (year < this.startDate.year() || (year == this.startDate.year() && month < this.startDate.month())) { | |
1372 | month = this.startDate.month(); | |
1373 | year = this.startDate.year(); | |
1374 | } | |
1375 | } | |
1376 | ||
1377 | if (this.minDate) { | |
1378 | if (year < this.minDate.year() || (year == this.minDate.year() && month < this.minDate.month())) { | |
1379 | month = this.minDate.month(); | |
1380 | year = this.minDate.year(); | |
1381 | } | |
1382 | } | |
1383 | ||
1384 | if (this.maxDate) { | |
1385 | if (year > this.maxDate.year() || (year == this.maxDate.year() && month > this.maxDate.month())) { | |
1386 | month = this.maxDate.month(); | |
1387 | year = this.maxDate.year(); | |
1388 | } | |
1389 | } | |
1390 | ||
1391 | if (isLeft) { | |
1392 | this.leftCalendar.month.month(month).year(year); | |
1393 | if (this.linkedCalendars) | |
1394 | this.rightCalendar.month = this.leftCalendar.month.clone().add(1, 'month'); | |
1395 | } else { | |
1396 | this.rightCalendar.month.month(month).year(year); | |
1397 | if (this.linkedCalendars) | |
1398 | this.leftCalendar.month = this.rightCalendar.month.clone().subtract(1, 'month'); | |
1399 | } | |
1400 | this.updateCalendars(); | |
1401 | }, | |
1402 | ||
1403 | timeChanged: function(e) { | |
1404 | ||
1405 | var cal = $(e.target).closest('.calendar'), | |
1406 | isLeft = cal.hasClass('left'); | |
1407 | ||
1408 | var hour = parseInt(cal.find('.hourselect').val(), 10); | |
1409 | var minute = parseInt(cal.find('.minuteselect').val(), 10); | |
1410 | var second = this.timePickerSeconds ? parseInt(cal.find('.secondselect').val(), 10) : 0; | |
1411 | ||
1412 | if (!this.timePicker24Hour) { | |
1413 | var ampm = cal.find('.ampmselect').val(); | |
1414 | if (ampm === 'PM' && hour < 12) | |
1415 | hour += 12; | |
1416 | if (ampm === 'AM' && hour === 12) | |
1417 | hour = 0; | |
1418 | } | |
1419 | ||
1420 | if (isLeft) { | |
1421 | var start = this.startDate.clone(); | |
1422 | start.hour(hour); | |
1423 | start.minute(minute); | |
1424 | start.second(second); | |
1425 | this.setStartDate(start); | |
1426 | if (this.singleDatePicker) { | |
1427 | this.endDate = this.startDate.clone(); | |
1428 | } else if (this.endDate && this.endDate.format('YYYY-MM-DD') == start.format('YYYY-MM-DD') && this.endDate.isBefore(start)) { | |
1429 | this.setEndDate(start.clone()); | |
1430 | } | |
1431 | } else if (this.endDate) { | |
1432 | var end = this.endDate.clone(); | |
1433 | end.hour(hour); | |
1434 | end.minute(minute); | |
1435 | end.second(second); | |
1436 | this.setEndDate(end); | |
1437 | } | |
1438 | ||
1439 | //update the calendars so all clickable dates reflect the new time component | |
1440 | this.updateCalendars(); | |
1441 | ||
1442 | //update the form inputs above the calendars with the new time | |
1443 | this.updateFormInputs(); | |
1444 | ||
1445 | //re-render the time pickers because changing one selection can affect what's enabled in another | |
1446 | this.renderTimePicker('left'); | |
1447 | this.renderTimePicker('right'); | |
1448 | ||
1449 | }, | |
1450 | ||
1451 | formInputsChanged: function(e) { | |
1452 | var isRight = $(e.target).closest('.calendar').hasClass('right'); | |
1453 | var start = moment(this.container.find('input[name="daterangepicker_start"]').val(), this.locale.format); | |
1454 | var end = moment(this.container.find('input[name="daterangepicker_end"]').val(), this.locale.format); | |
1455 | ||
1456 | if (start.isValid() && end.isValid()) { | |
1457 | ||
1458 | if (isRight && end.isBefore(start)) | |
1459 | start = end.clone(); | |
1460 | ||
1461 | this.setStartDate(start); | |
1462 | this.setEndDate(end); | |
1463 | ||
1464 | if (isRight) { | |
1465 | this.container.find('input[name="daterangepicker_start"]').val(this.startDate.format(this.locale.format)); | |
1466 | } else { | |
1467 | this.container.find('input[name="daterangepicker_end"]').val(this.endDate.format(this.locale.format)); | |
1468 | } | |
1469 | ||
1470 | } | |
1471 | ||
1472 | this.updateCalendars(); | |
1473 | if (this.timePicker) { | |
1474 | this.renderTimePicker('left'); | |
1475 | this.renderTimePicker('right'); | |
1476 | } | |
1477 | }, | |
1478 | ||
1479 | elementChanged: function() { | |
1480 | if (!this.element.is('input')) return; | |
1481 | if (!this.element.val().length) return; | |
1482 | if (this.element.val().length < this.locale.format.length) return; | |
1483 | ||
1484 | var dateString = this.element.val().split(this.locale.separator), | |
1485 | start = null, | |
1486 | end = null; | |
1487 | ||
1488 | if (dateString.length === 2) { | |
1489 | start = moment(dateString[0], this.locale.format); | |
1490 | end = moment(dateString[1], this.locale.format); | |
1491 | } | |
1492 | ||
1493 | if (this.singleDatePicker || start === null || end === null) { | |
1494 | start = moment(this.element.val(), this.locale.format); | |
1495 | end = start; | |
1496 | } | |
1497 | ||
1498 | if (!start.isValid() || !end.isValid()) return; | |
1499 | ||
1500 | this.setStartDate(start); | |
1501 | this.setEndDate(end); | |
1502 | this.updateView(); | |
1503 | }, | |
1504 | ||
1505 | keydown: function(e) { | |
1506 | //hide on tab or enter | |
1507 | if ((e.keyCode === 9) || (e.keyCode === 13)) { | |
1508 | this.hide(); | |
1509 | } | |
1510 | }, | |
1511 | ||
1512 | updateElement: function() { | |
1513 | if (this.element.is('input') && !this.singleDatePicker && this.autoUpdateInput) { | |
1514 | this.element.val(this.startDate.format(this.locale.format) + this.locale.separator + this.endDate.format(this.locale.format)); | |
1515 | this.element.trigger('change'); | |
1516 | } else if (this.element.is('input') && this.autoUpdateInput) { | |
1517 | this.element.val(this.startDate.format(this.locale.format)); | |
1518 | this.element.trigger('change'); | |
1519 | } | |
1520 | }, | |
1521 | ||
1522 | remove: function() { | |
1523 | this.container.remove(); | |
1524 | this.element.off('.daterangepicker'); | |
1525 | this.element.removeData(); | |
1526 | } | |
1527 | ||
1528 | }; | |
1529 | ||
1530 | $.fn.daterangepicker = function(options, callback) { | |
1531 | this.each(function() { | |
1532 | var el = $(this); | |
1533 | if (el.data('daterangepicker')) | |
1534 | el.data('daterangepicker').remove(); | |
1535 | el.data('daterangepicker', new DateRangePicker(el, options, callback)); | |
1536 | }); | |
1537 | return this; | |
1538 | }; | |
1539 | ||
1540 | return DateRangePicker; | |
1541 | ||
1542 | })); |