├── .gitattributes
├── .gitignore
├── README.md
├── bower.json
├── LICENSE.txt
├── jquery.comiseo.daterangepicker.css
└── jquery.comiseo.daterangepicker.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js text eol=lf
2 | *.json text eol=lf
3 | *.css text eol=lf
4 | *.md text eol=lf
5 | *.txt text
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS generated files #
2 | ######################
3 | .DS_Store
4 | .DS_Store?
5 | ._*
6 | .Spotlight-V100
7 | .Trashes
8 | ehthumbs.db
9 | Thumbs.db
10 | Desktop.ini
11 | *~
12 |
13 | # IDE generated files #
14 | #######################
15 | .settings/
16 | .project
17 | .buildpath
18 | .idea/
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jQuery UI DateRangePicker
2 |
3 | A jQuery UI widget similar to the date range picker used in Google Analytics;
4 | supports multiple months, custom preset ranges and smart positioning;
5 | [ThemeRoller](http://jqueryui.com/themeroller/)-ready and mobile-friendly.
6 |
7 | To get started, checkout examples and API at http://tamble.github.io/jquery-ui-daterangepicker/
8 |
9 | ## Dependencies
10 |
11 | - [jQuery](http://jquery.com/) 1.8.3+
12 | - [jQuery UI](http://jqueryui.com/) 1.9.0+ (widget factory, position utility, button, menu, datepicker)
13 | - [moment.js](http://momentjs.com) 2.3.0+
14 |
15 | ## Copyright and License
16 |
17 | Copyright (c) 2017 Tamble, Inc.
18 | Released under [the MIT license](LICENSE.txt)
19 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery-ui-daterangepicker",
3 | "homepage": "http://tamble.github.io/jquery-ui-daterangepicker/",
4 | "description": "jQuery UI widget similar to the date range picker used in Google Analytics; supports multiple months, custom preset ranges and smart positioning; ThemeRoller-ready and mobile-friendly.",
5 | "repository": {
6 | "type": "git",
7 | "url": "git://github.com/tamble/jquery-ui-daterangepicker.git"
8 | },
9 | "main": [
10 | "jquery.comiseo.daterangepicker.js",
11 | "jquery.comiseo.daterangepicker.css"
12 | ],
13 | "ignore": [
14 | "/.*"
15 | ],
16 | "keywords": [
17 | "jquery",
18 | "jquery ui",
19 | "date picker",
20 | "date range picker"
21 | ],
22 | "license": "MIT",
23 | "dependencies": {
24 | "jquery": ">=1.8.3",
25 | "jquery-ui": ">=1.9.0",
26 | "momentjs": "~2.3.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Copyright (c) 2015 Tamble, Inc
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/jquery.comiseo.daterangepicker.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Copyright (c) 2017 Tamble, Inc.
3 | * Licensed under MIT (https://github.com/tamble/jquery-ui-daterangepicker/raw/master/LICENSE.txt)
4 | */
5 |
6 | .comiseo-daterangepicker-triggerbutton.ui-button {
7 | text-align: left;
8 | min-width: 18em;
9 | }
10 |
11 | .comiseo-daterangepicker-triggerbutton .ui-button-icon { /* fix v1.12 */
12 | position: absolute;
13 | right: 0.5em;
14 | top: 50%;
15 | margin-top: -8px;
16 | }
17 |
18 | .comiseo-daterangepicker {
19 | position: absolute;
20 | padding: 5px;
21 | }
22 |
23 | .comiseo-daterangepicker-mask {
24 | margin: 0;
25 | padding: 0;
26 | position: fixed;
27 | left: 0;
28 | top: 0;
29 | height: 100%;
30 | width: 100%;
31 | /* required for IE */
32 | background-color: #fff;
33 | opacity: 0;
34 | filter: alpha(opacity = 0);
35 | }
36 |
37 | .comiseo-daterangepicker-presets,
38 | .comiseo-daterangepicker-calendar {
39 | display: table-cell;
40 | vertical-align: top;
41 | height: 230px;
42 | }
43 |
44 | .comiseo-daterangepicker-right .comiseo-daterangepicker-presets {
45 | padding: 2px 7px 7px 2px;
46 | }
47 |
48 | .comiseo-daterangepicker-left .comiseo-daterangepicker-presets {
49 | padding: 2px 2px 7px 7px;
50 | }
51 |
52 | .comiseo-daterangepicker-presets .ui-menu {
53 | padding: 2px; /* fix v1.11 */
54 | white-space: nowrap;
55 | }
56 |
57 | .comiseo-daterangepicker-presets .ui-menu-item { /* fix v1.11 */
58 | padding: 0;
59 | }
60 |
61 | .comiseo-daterangepicker-presets .ui-menu-item > * { /* fix v1.11 */
62 | text-decoration: none;
63 | display: block;
64 | padding: 2px 0.4em;
65 | line-height: 1.5;
66 | min-height: 0; /* support: IE7 */
67 | }
68 |
69 | .comiseo-daterangepicker .ui-widget-content,
70 | .comiseo-daterangepicker .ui-datepicker .ui-state-highlight {
71 | border-width: 0;
72 | }
73 |
74 | .comiseo-daterangepicker > .comiseo-daterangepicker-main.ui-widget-content {
75 | border-bottom-width: 1px;
76 | }
77 |
78 | .comiseo-daterangepicker .ui-datepicker .ui-datepicker-today .ui-state-highlight {
79 | border-width: 1px;
80 | }
81 |
82 | .comiseo-daterangepicker-right .comiseo-daterangepicker-calendar {
83 | border-left-width: 1px;
84 | padding-left: 5px;
85 | }
86 |
87 | .comiseo-daterangepicker-left .comiseo-daterangepicker-calendar {
88 | border-right-width: 1px;
89 | padding-right: 5px;
90 | }
91 |
92 | .comiseo-daterangepicker-right .comiseo-daterangepicker-buttonpanel {
93 | float: left;
94 | }
95 |
96 | .comiseo-daterangepicker-left .comiseo-daterangepicker-buttonpanel {
97 | float: right;
98 | }
99 |
100 | .comiseo-daterangepicker-buttonpanel > button {
101 | margin-top: 6px;
102 | }
103 |
104 | .comiseo-daterangepicker-right .comiseo-daterangepicker-buttonpanel > button {
105 | margin-right: 6px;
106 | }
107 |
108 | .comiseo-daterangepicker-left .comiseo-daterangepicker-buttonpanel > button {
109 | margin-left: 6px;
110 | }
111 |
112 | /* themeable styles */
113 | .comiseo-daterangepicker-calendar .ui-state-highlight a.ui-state-default {
114 | background: #b0c4de;
115 | color: #fff;
116 | }
--------------------------------------------------------------------------------
/jquery.comiseo.daterangepicker.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * jQuery UI date range picker widget
3 | * Copyright (c) 2017 Tamble, Inc.
4 | * Licensed under MIT (https://github.com/tamble/jquery-ui-daterangepicker/raw/master/LICENSE.txt)
5 | *
6 | * Depends:
7 | * - jQuery 1.8.3+
8 | * - jQuery UI 1.9.0+ (widget factory, position utility, button, menu, datepicker)
9 | * - moment.js 2.3.0+
10 | */
11 |
12 | (function($, window, undefined) {
13 |
14 | var uniqueId = 0; // used for unique ID generation within multiple plugin instances
15 |
16 | $.widget('comiseo.daterangepicker', {
17 | version: '0.6.0-beta.1',
18 |
19 | options: {
20 | // presetRanges: array of objects; each object describes an item in the presets menu
21 | // and must have the properties: text, dateStart, dateEnd.
22 | // dateStart, dateEnd are functions returning a moment object
23 | presetRanges: [
24 | {text: 'Today', dateStart: function() { return moment() }, dateEnd: function() { return moment() } },
25 | {text: 'Yesterday', dateStart: function() { return moment().subtract('days', 1) }, dateEnd: function() { return moment().subtract('days', 1) } },
26 | {text: 'Last 7 Days', dateStart: function() { return moment().subtract('days', 6) }, dateEnd: function() { return moment() } },
27 | {text: 'Last Week (Mo-Su)', dateStart: function() { return moment().subtract('days', 7).isoWeekday(1) }, dateEnd: function() { return moment().subtract('days', 7).isoWeekday(7) } },
28 | {text: 'Month to Date', dateStart: function() { return moment().startOf('month') }, dateEnd: function() { return moment() } },
29 | {text: 'Previous Month', dateStart: function() { return moment().subtract('month', 1).startOf('month') }, dateEnd: function() { return moment().subtract('month', 1).endOf('month') } },
30 | {text: 'Year to Date', dateStart: function() { return moment().startOf('year') }, dateEnd: function() { return moment() } }
31 | ],
32 | initialText: 'Select date range...', // placeholder text - shown when nothing is selected
33 | icon: 'ui-icon-triangle-1-s',
34 | applyButtonText: 'Apply', // use '' to get rid of the button
35 | clearButtonText: 'Clear', // use '' to get rid of the button
36 | cancelButtonText: 'Cancel', // use '' to get rid of the button
37 | rangeSplitter: ' - ', // string to use between dates
38 | dateFormat: 'M d, yy', // displayed date format. Available formats: http://api.jqueryui.com/datepicker/#utility-formatDate
39 | altFormat: 'yy-mm-dd', // submitted date format - inside JSON {"start":"...","end":"..."}
40 | verticalOffset: 0, // offset of the dropdown relative to the closest edge of the trigger button
41 | mirrorOnCollision: true, // reverse layout when there is not enough space on the right
42 | autoFitCalendars: true, // override datepicker's numberOfMonths option in order to fit widget width
43 | applyOnMenuSelect: true, // whether to auto apply menu selections
44 | open: null, // callback that executes when the dropdown opens
45 | close: null, // callback that executes when the dropdown closes
46 | change: null, // callback that executes when the date range changes
47 | clear: null, // callback that executes when the clear button is used
48 | cancel: null, // callback that executes when the cancel button is used
49 | onOpen: null, // @deprecated callback that executes when the dropdown opens
50 | onClose: null, // @deprecated callback that executes when the dropdown closes
51 | onChange: null, // @deprecated callback that executes when the date range changes
52 | onClear: null, // @deprecated callback that executes when the clear button is used
53 | datepickerOptions: { // object containing datepicker options. See http://api.jqueryui.com/datepicker/#options
54 | numberOfMonths: 3,
55 | // showCurrentAtPos: 1 // bug; use maxDate instead
56 | maxDate: 0 // the maximum selectable date is today (also current month is displayed on the last position)
57 | }
58 | },
59 |
60 | _create: function() {
61 | this._dateRangePicker = buildDateRangePicker(this.element, this, this.options);
62 | },
63 |
64 | _destroy: function() {
65 | this._dateRangePicker.destroy();
66 | },
67 |
68 | _setOptions: function(options) {
69 | this._super(options);
70 | this._dateRangePicker.enforceOptions();
71 | },
72 |
73 | open: function() {
74 | this._dateRangePicker.open();
75 | },
76 |
77 | close: function() {
78 | this._dateRangePicker.close();
79 | },
80 |
81 | setRange: function(range) {
82 | this._dateRangePicker.setRange(range);
83 | },
84 |
85 | getRange: function() {
86 | return this._dateRangePicker.getRange();
87 | },
88 |
89 | clearRange: function() {
90 | this._dateRangePicker.clearRange();
91 | },
92 |
93 | widget: function() {
94 | return this._dateRangePicker.getContainer();
95 | }
96 | });
97 |
98 | /**
99 | * factory for the trigger button (which visually replaces the original input form element)
100 | *
101 | * @param {jQuery} $originalElement jQuery object containing the input form element used to instantiate this widget instance
102 | * @param {String} classnameContext classname of the parent container
103 | * @param {Object} options
104 | */
105 | function buildTriggerButton($originalElement, classnameContext, options) {
106 | var $self, id;
107 |
108 | function fixReferences() {
109 | id = 'drp_autogen' + uniqueId++;
110 | $('label[for="' + $originalElement.attr('id') + '"]')
111 | .attr('for', id);
112 | }
113 |
114 | function init() {
115 | fixReferences();
116 | $self = $('')
117 | .addClass(classnameContext + '-triggerbutton')
118 | .attr({'title': $originalElement.attr('title'), 'tabindex': $originalElement.attr('tabindex'), id: id})
119 | .button({
120 | icons: {
121 | secondary: options.icon
122 | },
123 | icon: options.icon,
124 | iconPosition: 'end',
125 | label: options.initialText
126 | });
127 | }
128 |
129 | function getLabel() {
130 | return $self.button('option', 'label');
131 | }
132 |
133 | function setLabel(value) {
134 | $self.button('option', 'label', value);
135 | }
136 |
137 | function reset() {
138 | $originalElement.val('').change();
139 | setLabel(options.initialText);
140 | }
141 |
142 | function enforceOptions() {
143 | $self.button('option', {
144 | icons: {
145 | secondary: options.icon
146 | },
147 | icon: options.icon,
148 | iconPosition: 'end',
149 | label: options.initialText
150 | });
151 | }
152 |
153 | init();
154 | return {
155 | getElement: function() { return $self; },
156 | getLabel: getLabel,
157 | setLabel: setLabel,
158 | reset: reset,
159 | enforceOptions: enforceOptions
160 | };
161 | }
162 |
163 | /**
164 | * factory for the presets menu (containing built-in date ranges)
165 | *
166 | * @param {String} classnameContext classname of the parent container
167 | * @param {Object} options
168 | * @param {Function} onClick callback that executes when a preset is clicked
169 | */
170 | function buildPresetsMenu(classnameContext, options, onClick) {
171 | var $self,
172 | $menu,
173 | menuItemWrapper;
174 |
175 | function init() {
176 | $self = $('
')
177 | .addClass(classnameContext + '-presets');
178 |
179 | $menu = $('');
180 |
181 | if ($.ui.menu.prototype.options.items === undefined) {
182 | menuItemWrapper = {start: '', end: ''};
183 | } else {
184 | menuItemWrapper = {start: '', end: '
'};
185 | }
186 |
187 | $.each(options.presetRanges, function() {
188 | $(menuItemWrapper.start + this.text + menuItemWrapper.end)
189 | .data('dateStart', this.dateStart)
190 | .data('dateEnd', this.dateEnd)
191 | .click(onClick)
192 | .appendTo($menu);
193 | });
194 |
195 | $self.append($menu);
196 |
197 | $menu.menu()
198 | .data('ui-menu').delay = 0; // disable submenu delays
199 | }
200 |
201 | init();
202 | return {
203 | getElement: function() { return $self; }
204 | };
205 | }
206 |
207 | /**
208 | * factory for the multiple month date picker
209 | *
210 | * @param {String} classnameContext classname of the parent container
211 | * @param {Object} options
212 | */
213 | function buildCalendar(classnameContext, options) {
214 | var $self,
215 | range = {start: null, end: null}; // selected range
216 |
217 | function init() {
218 | $self = $('', {'class': classnameContext + '-calendar ui-widget-content'});
219 |
220 | $self.datepicker($.extend({}, options.datepickerOptions, {beforeShowDay: beforeShowDay, onSelect: onSelectDay}));
221 | updateAtMidnight();
222 | }
223 |
224 | function enforceOptions() {
225 | $self.datepicker('option', $.extend({}, options.datepickerOptions, {beforeShowDay: beforeShowDay, onSelect: onSelectDay}));
226 | }
227 |
228 | // called when a day is selected
229 | function onSelectDay(dateText, instance) {
230 | var dateFormat = options.datepickerOptions.dateFormat || $.datepicker._defaults.dateFormat,
231 | selectedDate = $.datepicker.parseDate(dateFormat, dateText);
232 |
233 | if (!range.start || range.end) { // start not set, or both already set
234 | range.start = selectedDate;
235 | range.end = null;
236 | } else if (selectedDate < range.start) { // start set, but selected date is earlier
237 | range.end = range.start;
238 | range.start = selectedDate;
239 | } else {
240 | range.end = selectedDate;
241 | }
242 | if (options.datepickerOptions.hasOwnProperty('onSelect')) {
243 | options.datepickerOptions.onSelect(dateText, instance);
244 | }
245 | }
246 |
247 | // called for each day in the datepicker before it is displayed
248 | function beforeShowDay(date) {
249 | var result = [
250 | true, // selectable
251 | range.start && ((+date === +range.start) || (range.end && range.start <= date && date <= range.end)) ? 'ui-state-highlight' : '' // class to be added
252 | ],
253 | userResult = [true, '', ''];
254 |
255 | if (options.datepickerOptions.hasOwnProperty('beforeShowDay')) {
256 | userResult = options.datepickerOptions.beforeShowDay(date);
257 | }
258 | return [
259 | result[0] && userResult[0],
260 | result[1] + ' ' + userResult[1],
261 | userResult[2]
262 | ];
263 | }
264 |
265 | function updateAtMidnight() {
266 | setTimeout(function() {
267 | refresh();
268 | updateAtMidnight();
269 | }, moment().endOf('day') - moment());
270 | }
271 |
272 | function scrollToRangeStart() {
273 | if (range.start) {
274 | $self.datepicker('setDate', range.start);
275 | }
276 | }
277 |
278 | function refresh() {
279 | $self.datepicker('refresh');
280 | $self.datepicker('setDate', null); // clear the selected date
281 | }
282 |
283 | function reset() {
284 | range = {start: null, end: null};
285 | refresh();
286 | }
287 |
288 | init();
289 | return {
290 | getElement: function() { return $self; },
291 | scrollToRangeStart: function() { return scrollToRangeStart(); },
292 | getRange: function() { return range; },
293 | setRange: function(value) { range = value; refresh(); },
294 | refresh: refresh,
295 | reset: reset,
296 | enforceOptions: enforceOptions
297 | };
298 | }
299 |
300 | /**
301 | * factory for the button panel
302 | *
303 | * @param {String} classnameContext classname of the parent container
304 | * @param {Object} options
305 | * @param {Object} handlers contains callbacks for each button
306 | */
307 | function buildButtonPanel(classnameContext, options, handlers) {
308 | var $self,
309 | applyButton,
310 | clearButton,
311 | cancelButton;
312 |
313 | function init() {
314 | $self = $('')
315 | .addClass(classnameContext + '-buttonpanel');
316 |
317 | if (options.applyButtonText) {
318 | applyButton = $('')
319 | .text(options.applyButtonText)
320 | .button();
321 |
322 | $self.append(applyButton);
323 | }
324 |
325 | if (options.clearButtonText) {
326 | clearButton = $('')
327 | .text(options.clearButtonText)
328 | .button();
329 |
330 | $self.append(clearButton);
331 | }
332 |
333 | if (options.cancelButtonText) {
334 | cancelButton = $('')
335 | .text(options.cancelButtonText)
336 | .button();
337 |
338 | $self.append(cancelButton);
339 | }
340 |
341 | bindEvents();
342 | }
343 |
344 | function enforceOptions() {
345 | if (applyButton) {
346 | applyButton.button('option', 'label', options.applyButtonText);
347 | }
348 |
349 | if (clearButton) {
350 | clearButton.button('option', 'label', options.clearButtonText);
351 | }
352 |
353 | if (cancelButton) {
354 | cancelButton.button('option', 'label', options.cancelButtonText);
355 | }
356 | }
357 |
358 | function bindEvents() {
359 | if (handlers) {
360 | if (applyButton) {
361 | applyButton.click(handlers.onApply);
362 | }
363 |
364 | if (clearButton) {
365 | clearButton.click(handlers.onClear);
366 | }
367 |
368 | if (cancelButton) {
369 | cancelButton.click(handlers.onCancel);
370 | }
371 | }
372 | }
373 |
374 | init();
375 | return {
376 | getElement: function() { return $self; },
377 | enforceOptions: enforceOptions
378 | };
379 | }
380 |
381 | /**
382 | * factory for the widget
383 | *
384 | * @param {jQuery} $originalElement jQuery object containing the input form element used to instantiate this widget instance
385 | * @param {Object} instance
386 | * @param {Object} options
387 | */
388 | function buildDateRangePicker($originalElement, instance, options) {
389 | var classname = 'comiseo-daterangepicker',
390 | $container, // the dropdown
391 | $mask, // ui helper (z-index fix)
392 | triggerButton,
393 | presetsMenu,
394 | calendar,
395 | buttonPanel,
396 | isOpen = false,
397 | autoFitNeeded = false,
398 | LEFT = 0,
399 | RIGHT = 1,
400 | TOP = 2,
401 | BOTTOM = 3,
402 | sides = ['left', 'right', 'top', 'bottom'],
403 | hSide = RIGHT, // initialized to pick layout styles from CSS
404 | vSide = null;
405 |
406 | function init() {
407 | triggerButton = buildTriggerButton($originalElement, classname, options);
408 | presetsMenu = buildPresetsMenu(classname, options, usePreset);
409 | calendar = buildCalendar(classname, options);
410 | autoFit.numberOfMonths = options.datepickerOptions.numberOfMonths; // save initial option!
411 | if (autoFit.numberOfMonths instanceof Array) { // not implemented
412 | options.autoFitCalendars = false;
413 | }
414 | buttonPanel = buildButtonPanel(classname, options, {
415 | onApply: function (event) {
416 | close(event);
417 | setRange(null, event);
418 | },
419 | onClear: function (event) {
420 | close(event);
421 | clearRange(event);
422 | },
423 | onCancel: function (event) {
424 | instance._trigger('cancel', event, {instance: instance});
425 | close(event);
426 | reset();
427 | }
428 | });
429 | render();
430 | autoFit();
431 | reset();
432 | bindEvents();
433 | }
434 |
435 | function render() {
436 | $container = $('', {'class': classname + ' ' + classname + '-' + sides[hSide] + ' ui-widget ui-widget-content ui-corner-all ui-front'})
437 | .append($('', {'class': classname + '-main ui-widget-content'})
438 | .append(presetsMenu.getElement())
439 | .append(calendar.getElement()))
440 | .append($('')
441 | .append(buttonPanel.getElement()))
442 | .hide();
443 | $originalElement.hide().after(triggerButton.getElement());
444 | $mask = $('', {'class': 'ui-front ' + classname + '-mask'}).hide();
445 | $('body').append($mask).append($container);
446 | }
447 |
448 | // auto adjusts the number of months in the date picker
449 | function autoFit() {
450 | if (options.autoFitCalendars) {
451 | var maxWidth = $(window).width(),
452 | initialWidth = $container.outerWidth(true),
453 | $calendar = calendar.getElement(),
454 | numberOfMonths = $calendar.datepicker('option', 'numberOfMonths'),
455 | initialNumberOfMonths = numberOfMonths;
456 |
457 | if (initialWidth > maxWidth) {
458 | while (numberOfMonths > 1 && $container.outerWidth(true) > maxWidth) {
459 | $calendar.datepicker('option', 'numberOfMonths', --numberOfMonths);
460 | }
461 | if (numberOfMonths !== initialNumberOfMonths) {
462 | autoFit.monthWidth = (initialWidth - $container.outerWidth(true)) / (initialNumberOfMonths - numberOfMonths);
463 | }
464 | } else {
465 | while (numberOfMonths < autoFit.numberOfMonths && (maxWidth - $container.outerWidth(true)) >= autoFit.monthWidth) {
466 | $calendar.datepicker('option', 'numberOfMonths', ++numberOfMonths);
467 | }
468 | }
469 | reposition();
470 | autoFitNeeded = false;
471 | }
472 | }
473 |
474 | function destroy() {
475 | $container.remove();
476 | triggerButton.getElement().remove();
477 | $originalElement.show();
478 | }
479 |
480 | function bindEvents() {
481 | triggerButton.getElement().click(toggle);
482 | triggerButton.getElement().keydown(keyPressTriggerOpenOrClose);
483 | $mask.click(function(event) {
484 | close(event);
485 | reset();
486 | });
487 | $(window).resize(function() { isOpen ? autoFit() : autoFitNeeded = true; });
488 | }
489 |
490 | function formatRangeForDisplay(range) {
491 | var dateFormat = options.dateFormat;
492 | return $.datepicker.formatDate(dateFormat, range.start) + (+range.end !== +range.start ? options.rangeSplitter + $.datepicker.formatDate(dateFormat, range.end) : '');
493 | }
494 |
495 | // formats a date range as JSON
496 | function formatRange(range) {
497 | var dateFormat = options.altFormat,
498 | formattedRange = {};
499 | formattedRange.start = $.datepicker.formatDate(dateFormat, range.start);
500 | formattedRange.end = $.datepicker.formatDate(dateFormat, range.end);
501 | return JSON.stringify(formattedRange);
502 | }
503 |
504 | // parses a date range in JSON format
505 | function parseRange(text) {
506 | var dateFormat = options.altFormat,
507 | range = null;
508 | if (text) {
509 | try {
510 | range = JSON.parse(text, function(key, value) {
511 | return key ? $.datepicker.parseDate(dateFormat, value) : value;
512 | });
513 | } catch (e) {
514 | }
515 | }
516 | return range;
517 | }
518 |
519 | function reset() {
520 | var range = getRange();
521 | if (range) {
522 | triggerButton.setLabel(formatRangeForDisplay(range));
523 | calendar.setRange(range);
524 | } else {
525 | calendar.reset();
526 | }
527 | }
528 |
529 | function setRange(value, event) {
530 | var range = value || calendar.getRange();
531 | if (!range.start) {
532 | return;
533 | }
534 | if (!range.end) {
535 | range.end = range.start;
536 | }
537 | value && calendar.setRange(range);
538 | triggerButton.setLabel(formatRangeForDisplay(range));
539 | $originalElement.val(formatRange(range)).change();
540 | if (options.onChange) {
541 | options.onChange();
542 | }
543 | instance._trigger('change', event, {instance: instance});
544 | }
545 |
546 | function getRange() {
547 | return parseRange($originalElement.val());
548 | }
549 |
550 | function clearRange(event) {
551 | triggerButton.reset();
552 | calendar.reset();
553 | if (options.onClear) {
554 | options.onClear();
555 | }
556 | instance._trigger('clear', event, {instance: instance});
557 | }
558 |
559 | // callback - used when the user clicks a preset range
560 | function usePreset(event) {
561 | var $this = $(this),
562 | start = $this.data('dateStart')().startOf('day').toDate(),
563 | end = $this.data('dateEnd')().startOf('day').toDate();
564 | calendar.setRange({ start: start, end: end });
565 | if (options.applyOnMenuSelect) {
566 | close(event);
567 | setRange(null, event);
568 | }
569 | return false;
570 | }
571 |
572 | // adjusts dropdown's position taking into account the available space
573 | function reposition() {
574 | $container.position({
575 | my: 'left top',
576 | at: 'left bottom' + (options.verticalOffset < 0 ? options.verticalOffset : '+' + options.verticalOffset),
577 | of: triggerButton.getElement(),
578 | collision : 'flipfit flipfit',
579 | using: function(coords, feedback) {
580 | var containerCenterX = feedback.element.left + feedback.element.width / 2,
581 | triggerButtonCenterX = feedback.target.left + feedback.target.width / 2,
582 | prevHSide = hSide,
583 | last,
584 | containerCenterY = feedback.element.top + feedback.element.height / 2,
585 | triggerButtonCenterY = feedback.target.top + feedback.target.height / 2,
586 | prevVSide = vSide,
587 | vFit; // is the container fit vertically
588 |
589 | hSide = (containerCenterX > triggerButtonCenterX) ? RIGHT : LEFT;
590 | if (hSide !== prevHSide) {
591 | if (options.mirrorOnCollision) {
592 | last = (hSide === LEFT) ? presetsMenu : calendar;
593 | $container.children().first().append(last.getElement());
594 | }
595 | $container.removeClass(classname + '-' + sides[prevHSide]);
596 | $container.addClass(classname + '-' + sides[hSide]);
597 | }
598 | $container.css({
599 | left: coords.left,
600 | top: coords.top
601 | });
602 |
603 | vSide = (containerCenterY > triggerButtonCenterY) ? BOTTOM : TOP;
604 | if (vSide !== prevVSide) {
605 | if (prevVSide !== null) {
606 | triggerButton.getElement().removeClass(classname + '-' + sides[prevVSide]);
607 | }
608 | triggerButton.getElement().addClass(classname + '-' + sides[vSide]);
609 | }
610 | vFit = vSide === BOTTOM && feedback.element.top - feedback.target.top !== feedback.target.height + options.verticalOffset
611 | || vSide === TOP && feedback.target.top - feedback.element.top !== feedback.element.height + options.verticalOffset;
612 | triggerButton.getElement().toggleClass(classname + '-vfit', vFit);
613 | }
614 | });
615 | }
616 |
617 | function killEvent(event) {
618 | event.preventDefault();
619 | event.stopPropagation();
620 | }
621 |
622 | function keyPressTriggerOpenOrClose(event) {
623 | switch (event.which) {
624 | case $.ui.keyCode.UP:
625 | case $.ui.keyCode.DOWN:
626 | killEvent(event);
627 | open(event);
628 | return;
629 | case $.ui.keyCode.ESCAPE:
630 | killEvent(event);
631 | close(event);
632 | reset();
633 | return;
634 | case $.ui.keyCode.TAB:
635 | close(event);
636 | return;
637 | }
638 | }
639 |
640 | function open(event) {
641 | if (!isOpen) {
642 | triggerButton.getElement().addClass(classname + '-active');
643 | $mask.show();
644 | isOpen = true;
645 | autoFitNeeded && autoFit();
646 | calendar.scrollToRangeStart();
647 | $container.show();
648 | reposition();
649 | }
650 | if (options.onOpen) {
651 | options.onOpen();
652 | }
653 | instance._trigger('open', event, {instance: instance});
654 | }
655 |
656 | function close(event) {
657 | if (isOpen) {
658 | $container.hide();
659 | $mask.hide();
660 | triggerButton.getElement().removeClass(classname + '-active');
661 | isOpen = false;
662 | }
663 | if (options.onClose) {
664 | options.onClose();
665 | }
666 | instance._trigger('close', event, {instance: instance});
667 | }
668 |
669 | function toggle(event) {
670 | if (isOpen) {
671 | close(event);
672 | reset();
673 | }
674 | else {
675 | open(event);
676 | }
677 | }
678 |
679 | function getContainer(){
680 | return $container;
681 | }
682 |
683 | function enforceOptions() {
684 | var oldPresetsMenu = presetsMenu;
685 | presetsMenu = buildPresetsMenu(classname, options, usePreset);
686 | oldPresetsMenu.getElement().replaceWith(presetsMenu.getElement());
687 | calendar.enforceOptions();
688 | buttonPanel.enforceOptions();
689 | triggerButton.enforceOptions();
690 | var range = getRange();
691 | if (range) {
692 | triggerButton.setLabel(formatRangeForDisplay(range));
693 | }
694 | }
695 |
696 | init();
697 | return {
698 | toggle: toggle,
699 | destroy: destroy,
700 | open: open,
701 | close: close,
702 | setRange: setRange,
703 | getRange: getRange,
704 | clearRange: clearRange,
705 | reset: reset,
706 | enforceOptions: enforceOptions,
707 | getContainer: getContainer
708 | };
709 | }
710 |
711 | })(jQuery, window);
712 |
--------------------------------------------------------------------------------