]>
Commit | Line | Data |
---|---|---|
31f18b77 FG |
1 | /* ========================================================= |
2 | * bootstrap-datepicker.js | |
3 | * Repo: https://github.com/eternicode/bootstrap-datepicker/ | |
4 | * Demo: http://eternicode.github.io/bootstrap-datepicker/ | |
5 | * Docs: http://bootstrap-datepicker.readthedocs.org/ | |
6 | * Forked from http://www.eyecon.ro/bootstrap-datepicker | |
7 | * ========================================================= | |
8 | * Started by Stefan Petre; improvements by Andrew Rowls + contributors | |
9 | * | |
10 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
11 | * you may not use this file except in compliance with the License. | |
12 | * You may obtain a copy of the License at | |
13 | * | |
14 | * http://www.apache.org/licenses/LICENSE-2.0 | |
15 | * | |
16 | * Unless required by applicable law or agreed to in writing, software | |
17 | * distributed under the License is distributed on an "AS IS" BASIS, | |
18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
19 | * See the License for the specific language governing permissions and | |
20 | * limitations under the License. | |
21 | * ========================================================= */ | |
22 | ||
23 | (function($, undefined){ | |
24 | ||
25 | var $window = $(window); | |
26 | ||
27 | function UTCDate(){ | |
28 | return new Date(Date.UTC.apply(Date, arguments)); | |
29 | } | |
30 | function UTCToday(){ | |
31 | var today = new Date(); | |
32 | return UTCDate(today.getFullYear(), today.getMonth(), today.getDate()); | |
33 | } | |
34 | function alias(method){ | |
35 | return function(){ | |
36 | return this[method].apply(this, arguments); | |
37 | }; | |
38 | } | |
39 | ||
40 | var DateArray = (function(){ | |
41 | var extras = { | |
42 | get: function(i){ | |
43 | return this.slice(i)[0]; | |
44 | }, | |
45 | contains: function(d){ | |
46 | // Array.indexOf is not cross-browser; | |
47 | // $.inArray doesn't work with Dates | |
48 | var val = d && d.valueOf(); | |
49 | for (var i=0, l=this.length; i < l; i++) | |
50 | if (this[i].valueOf() === val) | |
51 | return i; | |
52 | return -1; | |
53 | }, | |
54 | remove: function(i){ | |
55 | this.splice(i,1); | |
56 | }, | |
57 | replace: function(new_array){ | |
58 | if (!new_array) | |
59 | return; | |
60 | if (!$.isArray(new_array)) | |
61 | new_array = [new_array]; | |
62 | this.clear(); | |
63 | this.push.apply(this, new_array); | |
64 | }, | |
65 | clear: function(){ | |
66 | this.splice(0); | |
67 | }, | |
68 | copy: function(){ | |
69 | var a = new DateArray(); | |
70 | a.replace(this); | |
71 | return a; | |
72 | } | |
73 | }; | |
74 | ||
75 | return function(){ | |
76 | var a = []; | |
77 | a.push.apply(a, arguments); | |
78 | $.extend(a, extras); | |
79 | return a; | |
80 | }; | |
81 | })(); | |
82 | ||
83 | ||
84 | // Picker object | |
85 | ||
86 | var Datepicker = function(element, options){ | |
87 | this.dates = new DateArray(); | |
88 | this.viewDate = UTCToday(); | |
89 | this.focusDate = null; | |
90 | ||
91 | this._process_options(options); | |
92 | ||
93 | this.element = $(element); | |
94 | this.isInline = false; | |
95 | this.isInput = this.element.is('input'); | |
96 | this.component = this.element.is('.date') ? this.element.find('.add-on, .input-group-addon, .btn') : false; | |
97 | this.hasInput = this.component && this.element.find('input').length; | |
98 | if (this.component && this.component.length === 0) | |
99 | this.component = false; | |
100 | ||
101 | this.picker = $(DPGlobal.template); | |
102 | this._buildEvents(); | |
103 | this._attachEvents(); | |
104 | ||
105 | if (this.isInline){ | |
106 | this.picker.addClass('datepicker-inline').appendTo(this.element); | |
107 | } | |
108 | else { | |
109 | this.picker.addClass('datepicker-dropdown dropdown-menu'); | |
110 | } | |
111 | ||
112 | if (this.o.rtl){ | |
113 | this.picker.addClass('datepicker-rtl'); | |
114 | } | |
115 | ||
116 | this.viewMode = this.o.startView; | |
117 | ||
118 | if (this.o.calendarWeeks) | |
119 | this.picker.find('tfoot th.today') | |
120 | .attr('colspan', function(i, val){ | |
121 | return parseInt(val) + 1; | |
122 | }); | |
123 | ||
124 | this._allow_update = false; | |
125 | ||
126 | this.setStartDate(this._o.startDate); | |
127 | this.setEndDate(this._o.endDate); | |
128 | this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled); | |
129 | ||
130 | this.fillDow(); | |
131 | this.fillMonths(); | |
132 | ||
133 | this._allow_update = true; | |
134 | ||
135 | this.update(); | |
136 | this.showMode(); | |
137 | ||
138 | if (this.isInline){ | |
139 | this.show(); | |
140 | } | |
141 | }; | |
142 | ||
143 | Datepicker.prototype = { | |
144 | constructor: Datepicker, | |
145 | ||
146 | _process_options: function(opts){ | |
147 | // Store raw options for reference | |
148 | this._o = $.extend({}, this._o, opts); | |
149 | // Processed options | |
150 | var o = this.o = $.extend({}, this._o); | |
151 | ||
152 | // Check if "de-DE" style date is available, if not language should | |
153 | // fallback to 2 letter code eg "de" | |
154 | var lang = o.language; | |
155 | if (!dates[lang]){ | |
156 | lang = lang.split('-')[0]; | |
157 | if (!dates[lang]) | |
158 | lang = defaults.language; | |
159 | } | |
160 | o.language = lang; | |
161 | ||
162 | switch (o.startView){ | |
163 | case 2: | |
164 | case 'decade': | |
165 | o.startView = 2; | |
166 | break; | |
167 | case 1: | |
168 | case 'year': | |
169 | o.startView = 1; | |
170 | break; | |
171 | default: | |
172 | o.startView = 0; | |
173 | } | |
174 | ||
175 | switch (o.minViewMode){ | |
176 | case 1: | |
177 | case 'months': | |
178 | o.minViewMode = 1; | |
179 | break; | |
180 | case 2: | |
181 | case 'years': | |
182 | o.minViewMode = 2; | |
183 | break; | |
184 | default: | |
185 | o.minViewMode = 0; | |
186 | } | |
187 | ||
188 | o.startView = Math.max(o.startView, o.minViewMode); | |
189 | ||
190 | // true, false, or Number > 0 | |
191 | if (o.multidate !== true){ | |
192 | o.multidate = Number(o.multidate) || false; | |
193 | if (o.multidate !== false) | |
194 | o.multidate = Math.max(0, o.multidate); | |
195 | else | |
196 | o.multidate = 1; | |
197 | } | |
198 | o.multidateSeparator = String(o.multidateSeparator); | |
199 | ||
200 | o.weekStart %= 7; | |
201 | o.weekEnd = ((o.weekStart + 6) % 7); | |
202 | ||
203 | var format = DPGlobal.parseFormat(o.format); | |
204 | if (o.startDate !== -Infinity){ | |
205 | if (!!o.startDate){ | |
206 | if (o.startDate instanceof Date) | |
207 | o.startDate = this._local_to_utc(this._zero_time(o.startDate)); | |
208 | else | |
209 | o.startDate = DPGlobal.parseDate(o.startDate, format, o.language); | |
210 | } | |
211 | else { | |
212 | o.startDate = -Infinity; | |
213 | } | |
214 | } | |
215 | if (o.endDate !== Infinity){ | |
216 | if (!!o.endDate){ | |
217 | if (o.endDate instanceof Date) | |
218 | o.endDate = this._local_to_utc(this._zero_time(o.endDate)); | |
219 | else | |
220 | o.endDate = DPGlobal.parseDate(o.endDate, format, o.language); | |
221 | } | |
222 | else { | |
223 | o.endDate = Infinity; | |
224 | } | |
225 | } | |
226 | ||
227 | o.daysOfWeekDisabled = o.daysOfWeekDisabled||[]; | |
228 | if (!$.isArray(o.daysOfWeekDisabled)) | |
229 | o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/); | |
230 | o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function(d){ | |
231 | return parseInt(d, 10); | |
232 | }); | |
233 | ||
234 | var plc = String(o.orientation).toLowerCase().split(/\s+/g), | |
235 | _plc = o.orientation.toLowerCase(); | |
236 | plc = $.grep(plc, function(word){ | |
237 | return (/^auto|left|right|top|bottom$/).test(word); | |
238 | }); | |
239 | o.orientation = {x: 'auto', y: 'auto'}; | |
240 | if (!_plc || _plc === 'auto') | |
241 | ; // no action | |
242 | else if (plc.length === 1){ | |
243 | switch (plc[0]){ | |
244 | case 'top': | |
245 | case 'bottom': | |
246 | o.orientation.y = plc[0]; | |
247 | break; | |
248 | case 'left': | |
249 | case 'right': | |
250 | o.orientation.x = plc[0]; | |
251 | break; | |
252 | } | |
253 | } | |
254 | else { | |
255 | _plc = $.grep(plc, function(word){ | |
256 | return (/^left|right$/).test(word); | |
257 | }); | |
258 | o.orientation.x = _plc[0] || 'auto'; | |
259 | ||
260 | _plc = $.grep(plc, function(word){ | |
261 | return (/^top|bottom$/).test(word); | |
262 | }); | |
263 | o.orientation.y = _plc[0] || 'auto'; | |
264 | } | |
265 | }, | |
266 | _events: [], | |
267 | _secondaryEvents: [], | |
268 | _applyEvents: function(evs){ | |
269 | for (var i=0, el, ch, ev; i < evs.length; i++){ | |
270 | el = evs[i][0]; | |
271 | if (evs[i].length === 2){ | |
272 | ch = undefined; | |
273 | ev = evs[i][1]; | |
274 | } | |
275 | else if (evs[i].length === 3){ | |
276 | ch = evs[i][1]; | |
277 | ev = evs[i][2]; | |
278 | } | |
279 | el.on(ev, ch); | |
280 | } | |
281 | }, | |
282 | _unapplyEvents: function(evs){ | |
283 | for (var i=0, el, ev, ch; i < evs.length; i++){ | |
284 | el = evs[i][0]; | |
285 | if (evs[i].length === 2){ | |
286 | ch = undefined; | |
287 | ev = evs[i][1]; | |
288 | } | |
289 | else if (evs[i].length === 3){ | |
290 | ch = evs[i][1]; | |
291 | ev = evs[i][2]; | |
292 | } | |
293 | el.off(ev, ch); | |
294 | } | |
295 | }, | |
296 | _buildEvents: function(){ | |
297 | if (this.isInput){ // single input | |
298 | this._events = [ | |
299 | [this.element, { | |
300 | focus: $.proxy(this.show, this), | |
301 | keyup: $.proxy(function(e){ | |
302 | if ($.inArray(e.keyCode, [27,37,39,38,40,32,13,9]) === -1) | |
303 | this.update(); | |
304 | }, this), | |
305 | keydown: $.proxy(this.keydown, this) | |
306 | }] | |
307 | ]; | |
308 | } | |
309 | else if (this.component && this.hasInput){ // component: input + button | |
310 | this._events = [ | |
311 | // For components that are not readonly, allow keyboard nav | |
312 | [this.element.find('input'), { | |
313 | focus: $.proxy(this.show, this), | |
314 | keyup: $.proxy(function(e){ | |
315 | if ($.inArray(e.keyCode, [27,37,39,38,40,32,13,9]) === -1) | |
316 | this.update(); | |
317 | }, this), | |
318 | keydown: $.proxy(this.keydown, this) | |
319 | }], | |
320 | [this.component, { | |
321 | click: $.proxy(this.show, this) | |
322 | }] | |
323 | ]; | |
324 | } | |
325 | else if (this.element.is('div')){ // inline datepicker | |
326 | this.isInline = true; | |
327 | } | |
328 | else { | |
329 | this._events = [ | |
330 | [this.element, { | |
331 | click: $.proxy(this.show, this) | |
332 | }] | |
333 | ]; | |
334 | } | |
335 | this._events.push( | |
336 | // Component: listen for blur on element descendants | |
337 | [this.element, '*', { | |
338 | blur: $.proxy(function(e){ | |
339 | this._focused_from = e.target; | |
340 | }, this) | |
341 | }], | |
342 | // Input: listen for blur on element | |
343 | [this.element, { | |
344 | blur: $.proxy(function(e){ | |
345 | this._focused_from = e.target; | |
346 | }, this) | |
347 | }] | |
348 | ); | |
349 | ||
350 | this._secondaryEvents = [ | |
351 | [this.picker, { | |
352 | click: $.proxy(this.click, this) | |
353 | }], | |
354 | [$(window), { | |
355 | resize: $.proxy(this.place, this) | |
356 | }], | |
357 | [$(document), { | |
358 | 'mousedown touchstart': $.proxy(function(e){ | |
359 | // Clicked outside the datepicker, hide it | |
360 | if (!( | |
361 | this.element.is(e.target) || | |
362 | this.element.find(e.target).length || | |
363 | this.picker.is(e.target) || | |
364 | this.picker.find(e.target).length | |
365 | )){ | |
366 | this.hide(); | |
367 | } | |
368 | }, this) | |
369 | }] | |
370 | ]; | |
371 | }, | |
372 | _attachEvents: function(){ | |
373 | this._detachEvents(); | |
374 | this._applyEvents(this._events); | |
375 | }, | |
376 | _detachEvents: function(){ | |
377 | this._unapplyEvents(this._events); | |
378 | }, | |
379 | _attachSecondaryEvents: function(){ | |
380 | this._detachSecondaryEvents(); | |
381 | this._applyEvents(this._secondaryEvents); | |
382 | }, | |
383 | _detachSecondaryEvents: function(){ | |
384 | this._unapplyEvents(this._secondaryEvents); | |
385 | }, | |
386 | _trigger: function(event, altdate){ | |
387 | var date = altdate || this.dates.get(-1), | |
388 | local_date = this._utc_to_local(date); | |
389 | ||
390 | this.element.trigger({ | |
391 | type: event, | |
392 | date: local_date, | |
393 | dates: $.map(this.dates, this._utc_to_local), | |
394 | format: $.proxy(function(ix, format){ | |
395 | if (arguments.length === 0){ | |
396 | ix = this.dates.length - 1; | |
397 | format = this.o.format; | |
398 | } | |
399 | else if (typeof ix === 'string'){ | |
400 | format = ix; | |
401 | ix = this.dates.length - 1; | |
402 | } | |
403 | format = format || this.o.format; | |
404 | var date = this.dates.get(ix); | |
405 | return DPGlobal.formatDate(date, format, this.o.language); | |
406 | }, this) | |
407 | }); | |
408 | }, | |
409 | ||
410 | show: function(){ | |
411 | if (!this.isInline) | |
412 | this.picker.appendTo('body'); | |
413 | this.picker.show(); | |
414 | this.place(); | |
415 | this._attachSecondaryEvents(); | |
416 | this._trigger('show'); | |
417 | }, | |
418 | ||
419 | hide: function(){ | |
420 | if (this.isInline) | |
421 | return; | |
422 | if (!this.picker.is(':visible')) | |
423 | return; | |
424 | this.focusDate = null; | |
425 | this.picker.hide().detach(); | |
426 | this._detachSecondaryEvents(); | |
427 | this.viewMode = this.o.startView; | |
428 | this.showMode(); | |
429 | ||
430 | if ( | |
431 | this.o.forceParse && | |
432 | ( | |
433 | this.isInput && this.element.val() || | |
434 | this.hasInput && this.element.find('input').val() | |
435 | ) | |
436 | ) | |
437 | this.setValue(); | |
438 | this._trigger('hide'); | |
439 | }, | |
440 | ||
441 | remove: function(){ | |
442 | this.hide(); | |
443 | this._detachEvents(); | |
444 | this._detachSecondaryEvents(); | |
445 | this.picker.remove(); | |
446 | delete this.element.data().datepicker; | |
447 | if (!this.isInput){ | |
448 | delete this.element.data().date; | |
449 | } | |
450 | }, | |
451 | ||
452 | _utc_to_local: function(utc){ | |
453 | return utc && new Date(utc.getTime() + (utc.getTimezoneOffset()*60000)); | |
454 | }, | |
455 | _local_to_utc: function(local){ | |
456 | return local && new Date(local.getTime() - (local.getTimezoneOffset()*60000)); | |
457 | }, | |
458 | _zero_time: function(local){ | |
459 | return local && new Date(local.getFullYear(), local.getMonth(), local.getDate()); | |
460 | }, | |
461 | _zero_utc_time: function(utc){ | |
462 | return utc && new Date(Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate())); | |
463 | }, | |
464 | ||
465 | getDates: function(){ | |
466 | return $.map(this.dates, this._utc_to_local); | |
467 | }, | |
468 | ||
469 | getUTCDates: function(){ | |
470 | return $.map(this.dates, function(d){ | |
471 | return new Date(d); | |
472 | }); | |
473 | }, | |
474 | ||
475 | getDate: function(){ | |
476 | return this._utc_to_local(this.getUTCDate()); | |
477 | }, | |
478 | ||
479 | getUTCDate: function(){ | |
480 | return new Date(this.dates.get(-1)); | |
481 | }, | |
482 | ||
483 | setDates: function(){ | |
484 | var args = $.isArray(arguments[0]) ? arguments[0] : arguments; | |
485 | this.update.apply(this, args); | |
486 | this._trigger('changeDate'); | |
487 | this.setValue(); | |
488 | }, | |
489 | ||
490 | setUTCDates: function(){ | |
491 | var args = $.isArray(arguments[0]) ? arguments[0] : arguments; | |
492 | this.update.apply(this, $.map(args, this._utc_to_local)); | |
493 | this._trigger('changeDate'); | |
494 | this.setValue(); | |
495 | }, | |
496 | ||
497 | setDate: alias('setDates'), | |
498 | setUTCDate: alias('setUTCDates'), | |
499 | ||
500 | setValue: function(){ | |
501 | var formatted = this.getFormattedDate(); | |
502 | if (!this.isInput){ | |
503 | if (this.component){ | |
504 | this.element.find('input').val(formatted).change(); | |
505 | } | |
506 | } | |
507 | else { | |
508 | this.element.val(formatted).change(); | |
509 | } | |
510 | }, | |
511 | ||
512 | getFormattedDate: function(format){ | |
513 | if (format === undefined) | |
514 | format = this.o.format; | |
515 | ||
516 | var lang = this.o.language; | |
517 | return $.map(this.dates, function(d){ | |
518 | return DPGlobal.formatDate(d, format, lang); | |
519 | }).join(this.o.multidateSeparator); | |
520 | }, | |
521 | ||
522 | setStartDate: function(startDate){ | |
523 | this._process_options({startDate: startDate}); | |
524 | this.update(); | |
525 | this.updateNavArrows(); | |
526 | }, | |
527 | ||
528 | setEndDate: function(endDate){ | |
529 | this._process_options({endDate: endDate}); | |
530 | this.update(); | |
531 | this.updateNavArrows(); | |
532 | }, | |
533 | ||
534 | setDaysOfWeekDisabled: function(daysOfWeekDisabled){ | |
535 | this._process_options({daysOfWeekDisabled: daysOfWeekDisabled}); | |
536 | this.update(); | |
537 | this.updateNavArrows(); | |
538 | }, | |
539 | ||
540 | place: function(){ | |
541 | if (this.isInline) | |
542 | return; | |
543 | var calendarWidth = this.picker.outerWidth(), | |
544 | calendarHeight = this.picker.outerHeight(), | |
545 | visualPadding = 10, | |
546 | windowWidth = $window.width(), | |
547 | windowHeight = $window.height(), | |
548 | scrollTop = $window.scrollTop(); | |
549 | ||
550 | var zIndex = parseInt(this.element.parents().filter(function(){ | |
551 | return $(this).css('z-index') !== 'auto'; | |
552 | }).first().css('z-index'))+10; | |
553 | var offset = this.component ? this.component.parent().offset() : this.element.offset(); | |
554 | var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false); | |
555 | var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false); | |
556 | var left = offset.left, | |
557 | top = offset.top; | |
558 | ||
559 | this.picker.removeClass( | |
560 | 'datepicker-orient-top datepicker-orient-bottom '+ | |
561 | 'datepicker-orient-right datepicker-orient-left' | |
562 | ); | |
563 | ||
564 | if (this.o.orientation.x !== 'auto'){ | |
565 | this.picker.addClass('datepicker-orient-' + this.o.orientation.x); | |
566 | if (this.o.orientation.x === 'right') | |
567 | left -= calendarWidth - width; | |
568 | } | |
569 | // auto x orientation is best-placement: if it crosses a window | |
570 | // edge, fudge it sideways | |
571 | else { | |
572 | // Default to left | |
573 | this.picker.addClass('datepicker-orient-left'); | |
574 | if (offset.left < 0) | |
575 | left -= offset.left - visualPadding; | |
576 | else if (offset.left + calendarWidth > windowWidth) | |
577 | left = windowWidth - calendarWidth - visualPadding; | |
578 | } | |
579 | ||
580 | // auto y orientation is best-situation: top or bottom, no fudging, | |
581 | // decision based on which shows more of the calendar | |
582 | var yorient = this.o.orientation.y, | |
583 | top_overflow, bottom_overflow; | |
584 | if (yorient === 'auto'){ | |
585 | top_overflow = -scrollTop + offset.top - calendarHeight; | |
586 | bottom_overflow = scrollTop + windowHeight - (offset.top + height + calendarHeight); | |
587 | if (Math.max(top_overflow, bottom_overflow) === bottom_overflow) | |
588 | yorient = 'top'; | |
589 | else | |
590 | yorient = 'bottom'; | |
591 | } | |
592 | this.picker.addClass('datepicker-orient-' + yorient); | |
593 | if (yorient === 'top') | |
594 | top += height; | |
595 | else | |
596 | top -= calendarHeight + parseInt(this.picker.css('padding-top')); | |
597 | ||
598 | this.picker.css({ | |
599 | top: top, | |
600 | left: left, | |
601 | zIndex: zIndex | |
602 | }); | |
603 | }, | |
604 | ||
605 | _allow_update: true, | |
606 | update: function(){ | |
607 | if (!this._allow_update) | |
608 | return; | |
609 | ||
610 | var oldDates = this.dates.copy(), | |
611 | dates = [], | |
612 | fromArgs = false; | |
613 | if (arguments.length){ | |
614 | $.each(arguments, $.proxy(function(i, date){ | |
615 | if (date instanceof Date) | |
616 | date = this._local_to_utc(date); | |
617 | dates.push(date); | |
618 | }, this)); | |
619 | fromArgs = true; | |
620 | } | |
621 | else { | |
622 | dates = this.isInput | |
623 | ? this.element.val() | |
624 | : this.element.data('date') || this.element.find('input').val(); | |
625 | if (dates && this.o.multidate) | |
626 | dates = dates.split(this.o.multidateSeparator); | |
627 | else | |
628 | dates = [dates]; | |
629 | delete this.element.data().date; | |
630 | } | |
631 | ||
632 | dates = $.map(dates, $.proxy(function(date){ | |
633 | return DPGlobal.parseDate(date, this.o.format, this.o.language); | |
634 | }, this)); | |
635 | dates = $.grep(dates, $.proxy(function(date){ | |
636 | return ( | |
637 | date < this.o.startDate || | |
638 | date > this.o.endDate || | |
639 | !date | |
640 | ); | |
641 | }, this), true); | |
642 | this.dates.replace(dates); | |
643 | ||
644 | if (this.dates.length) | |
645 | this.viewDate = new Date(this.dates.get(-1)); | |
646 | else if (this.viewDate < this.o.startDate) | |
647 | this.viewDate = new Date(this.o.startDate); | |
648 | else if (this.viewDate > this.o.endDate) | |
649 | this.viewDate = new Date(this.o.endDate); | |
650 | ||
651 | if (fromArgs){ | |
652 | // setting date by clicking | |
653 | this.setValue(); | |
654 | } | |
655 | else if (dates.length){ | |
656 | // setting date by typing | |
657 | if (String(oldDates) !== String(this.dates)) | |
658 | this._trigger('changeDate'); | |
659 | } | |
660 | if (!this.dates.length && oldDates.length) | |
661 | this._trigger('clearDate'); | |
662 | ||
663 | this.fill(); | |
664 | }, | |
665 | ||
666 | fillDow: function(){ | |
667 | var dowCnt = this.o.weekStart, | |
668 | html = '<tr>'; | |
669 | if (this.o.calendarWeeks){ | |
670 | var cell = '<th class="cw"> </th>'; | |
671 | html += cell; | |
672 | this.picker.find('.datepicker-days thead tr:first-child').prepend(cell); | |
673 | } | |
674 | while (dowCnt < this.o.weekStart + 7){ | |
675 | html += '<th class="dow">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>'; | |
676 | } | |
677 | html += '</tr>'; | |
678 | this.picker.find('.datepicker-days thead').append(html); | |
679 | }, | |
680 | ||
681 | fillMonths: function(){ | |
682 | var html = '', | |
683 | i = 0; | |
684 | while (i < 12){ | |
685 | html += '<span class="month">'+dates[this.o.language].monthsShort[i++]+'</span>'; | |
686 | } | |
687 | this.picker.find('.datepicker-months td').html(html); | |
688 | }, | |
689 | ||
690 | setRange: function(range){ | |
691 | if (!range || !range.length) | |
692 | delete this.range; | |
693 | else | |
694 | this.range = $.map(range, function(d){ | |
695 | return d.valueOf(); | |
696 | }); | |
697 | this.fill(); | |
698 | }, | |
699 | ||
700 | getClassNames: function(date){ | |
701 | var cls = [], | |
702 | year = this.viewDate.getUTCFullYear(), | |
703 | month = this.viewDate.getUTCMonth(), | |
704 | today = new Date(); | |
705 | if (date.getUTCFullYear() < year || (date.getUTCFullYear() === year && date.getUTCMonth() < month)){ | |
706 | cls.push('old'); | |
707 | } | |
708 | else if (date.getUTCFullYear() > year || (date.getUTCFullYear() === year && date.getUTCMonth() > month)){ | |
709 | cls.push('new'); | |
710 | } | |
711 | if (this.focusDate && date.valueOf() === this.focusDate.valueOf()) | |
712 | cls.push('focused'); | |
713 | // Compare internal UTC date with local today, not UTC today | |
714 | if (this.o.todayHighlight && | |
715 | date.getUTCFullYear() === today.getFullYear() && | |
716 | date.getUTCMonth() === today.getMonth() && | |
717 | date.getUTCDate() === today.getDate()){ | |
718 | cls.push('today'); | |
719 | } | |
720 | if (this.dates.contains(date) !== -1) | |
721 | cls.push('active'); | |
722 | if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate || | |
723 | $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1){ | |
724 | cls.push('disabled'); | |
725 | } | |
726 | if (this.range){ | |
727 | if (date > this.range[0] && date < this.range[this.range.length-1]){ | |
728 | cls.push('range'); | |
729 | } | |
730 | if ($.inArray(date.valueOf(), this.range) !== -1){ | |
731 | cls.push('selected'); | |
732 | } | |
733 | } | |
734 | return cls; | |
735 | }, | |
736 | ||
737 | fill: function(){ | |
738 | var d = new Date(this.viewDate), | |
739 | year = d.getUTCFullYear(), | |
740 | month = d.getUTCMonth(), | |
741 | startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity, | |
742 | startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity, | |
743 | endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity, | |
744 | endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity, | |
745 | todaytxt = dates[this.o.language].today || dates['en'].today || '', | |
746 | cleartxt = dates[this.o.language].clear || dates['en'].clear || '', | |
747 | tooltip; | |
748 | this.picker.find('.datepicker-days thead th.datepicker-switch') | |
749 | .text(dates[this.o.language].months[month]+' '+year); | |
750 | this.picker.find('tfoot th.today') | |
751 | .text(todaytxt) | |
752 | .toggle(this.o.todayBtn !== false); | |
753 | this.picker.find('tfoot th.clear') | |
754 | .text(cleartxt) | |
755 | .toggle(this.o.clearBtn !== false); | |
756 | this.updateNavArrows(); | |
757 | this.fillMonths(); | |
758 | var prevMonth = UTCDate(year, month-1, 28), | |
759 | day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth()); | |
760 | prevMonth.setUTCDate(day); | |
761 | prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7); | |
762 | var nextMonth = new Date(prevMonth); | |
763 | nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); | |
764 | nextMonth = nextMonth.valueOf(); | |
765 | var html = []; | |
766 | var clsName; | |
767 | while (prevMonth.valueOf() < nextMonth){ | |
768 | if (prevMonth.getUTCDay() === this.o.weekStart){ | |
769 | html.push('<tr>'); | |
770 | if (this.o.calendarWeeks){ | |
771 | // ISO 8601: First week contains first thursday. | |
772 | // ISO also states week starts on Monday, but we can be more abstract here. | |
773 | var | |
774 | // Start of current week: based on weekstart/current date | |
775 | ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5), | |
776 | // Thursday of this week | |
777 | th = new Date(Number(ws) + (7 + 4 - ws.getUTCDay()) % 7 * 864e5), | |
778 | // First Thursday of year, year from thursday | |
779 | yth = new Date(Number(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5), | |
780 | // Calendar week: ms between thursdays, div ms per day, div 7 days | |
781 | calWeek = (th - yth) / 864e5 / 7 + 1; | |
782 | html.push('<td class="cw">'+ calWeek +'</td>'); | |
783 | ||
784 | } | |
785 | } | |
786 | clsName = this.getClassNames(prevMonth); | |
787 | clsName.push('day'); | |
788 | ||
789 | if (this.o.beforeShowDay !== $.noop){ | |
790 | var before = this.o.beforeShowDay(this._utc_to_local(prevMonth)); | |
791 | if (before === undefined) | |
792 | before = {}; | |
793 | else if (typeof(before) === 'boolean') | |
794 | before = {enabled: before}; | |
795 | else if (typeof(before) === 'string') | |
796 | before = {classes: before}; | |
797 | if (before.enabled === false) | |
798 | clsName.push('disabled'); | |
799 | if (before.classes) | |
800 | clsName = clsName.concat(before.classes.split(/\s+/)); | |
801 | if (before.tooltip) | |
802 | tooltip = before.tooltip; | |
803 | } | |
804 | ||
805 | clsName = $.unique(clsName); | |
806 | html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + '>'+prevMonth.getUTCDate() + '</td>'); | |
807 | if (prevMonth.getUTCDay() === this.o.weekEnd){ | |
808 | html.push('</tr>'); | |
809 | } | |
810 | prevMonth.setUTCDate(prevMonth.getUTCDate()+1); | |
811 | } | |
812 | this.picker.find('.datepicker-days tbody').empty().append(html.join('')); | |
813 | ||
814 | var months = this.picker.find('.datepicker-months') | |
815 | .find('th:eq(1)') | |
816 | .text(year) | |
817 | .end() | |
818 | .find('span').removeClass('active'); | |
819 | ||
820 | $.each(this.dates, function(i, d){ | |
821 | if (d.getUTCFullYear() === year) | |
822 | months.eq(d.getUTCMonth()).addClass('active'); | |
823 | }); | |
824 | ||
825 | if (year < startYear || year > endYear){ | |
826 | months.addClass('disabled'); | |
827 | } | |
828 | if (year === startYear){ | |
829 | months.slice(0, startMonth).addClass('disabled'); | |
830 | } | |
831 | if (year === endYear){ | |
832 | months.slice(endMonth+1).addClass('disabled'); | |
833 | } | |
834 | ||
835 | html = ''; | |
836 | year = parseInt(year/10, 10) * 10; | |
837 | var yearCont = this.picker.find('.datepicker-years') | |
838 | .find('th:eq(1)') | |
839 | .text(year + '-' + (year + 9)) | |
840 | .end() | |
841 | .find('td'); | |
842 | year -= 1; | |
843 | var years = $.map(this.dates, function(d){ | |
844 | return d.getUTCFullYear(); | |
845 | }), | |
846 | classes; | |
847 | for (var i = -1; i < 11; i++){ | |
848 | classes = ['year']; | |
849 | if (i === -1) | |
850 | classes.push('old'); | |
851 | else if (i === 10) | |
852 | classes.push('new'); | |
853 | if ($.inArray(year, years) !== -1) | |
854 | classes.push('active'); | |
855 | if (year < startYear || year > endYear) | |
856 | classes.push('disabled'); | |
857 | html += '<span class="' + classes.join(' ') + '">'+year+'</span>'; | |
858 | year += 1; | |
859 | } | |
860 | yearCont.html(html); | |
861 | }, | |
862 | ||
863 | updateNavArrows: function(){ | |
864 | if (!this._allow_update) | |
865 | return; | |
866 | ||
867 | var d = new Date(this.viewDate), | |
868 | year = d.getUTCFullYear(), | |
869 | month = d.getUTCMonth(); | |
870 | switch (this.viewMode){ | |
871 | case 0: | |
872 | if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()){ | |
873 | this.picker.find('.prev').css({visibility: 'hidden'}); | |
874 | } | |
875 | else { | |
876 | this.picker.find('.prev').css({visibility: 'visible'}); | |
877 | } | |
878 | if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()){ | |
879 | this.picker.find('.next').css({visibility: 'hidden'}); | |
880 | } | |
881 | else { | |
882 | this.picker.find('.next').css({visibility: 'visible'}); | |
883 | } | |
884 | break; | |
885 | case 1: | |
886 | case 2: | |
887 | if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()){ | |
888 | this.picker.find('.prev').css({visibility: 'hidden'}); | |
889 | } | |
890 | else { | |
891 | this.picker.find('.prev').css({visibility: 'visible'}); | |
892 | } | |
893 | if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()){ | |
894 | this.picker.find('.next').css({visibility: 'hidden'}); | |
895 | } | |
896 | else { | |
897 | this.picker.find('.next').css({visibility: 'visible'}); | |
898 | } | |
899 | break; | |
900 | } | |
901 | }, | |
902 | ||
903 | click: function(e){ | |
904 | e.preventDefault(); | |
905 | var target = $(e.target).closest('span, td, th'), | |
906 | year, month, day; | |
907 | if (target.length === 1){ | |
908 | switch (target[0].nodeName.toLowerCase()){ | |
909 | case 'th': | |
910 | switch (target[0].className){ | |
911 | case 'datepicker-switch': | |
912 | this.showMode(1); | |
913 | break; | |
914 | case 'prev': | |
915 | case 'next': | |
916 | var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1); | |
917 | switch (this.viewMode){ | |
918 | case 0: | |
919 | this.viewDate = this.moveMonth(this.viewDate, dir); | |
920 | this._trigger('changeMonth', this.viewDate); | |
921 | break; | |
922 | case 1: | |
923 | case 2: | |
924 | this.viewDate = this.moveYear(this.viewDate, dir); | |
925 | if (this.viewMode === 1) | |
926 | this._trigger('changeYear', this.viewDate); | |
927 | break; | |
928 | } | |
929 | this.fill(); | |
930 | break; | |
931 | case 'today': | |
932 | var date = new Date(); | |
933 | date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); | |
934 | ||
935 | this.showMode(-2); | |
936 | var which = this.o.todayBtn === 'linked' ? null : 'view'; | |
937 | this._setDate(date, which); | |
938 | break; | |
939 | case 'clear': | |
940 | var element; | |
941 | if (this.isInput) | |
942 | element = this.element; | |
943 | else if (this.component) | |
944 | element = this.element.find('input'); | |
945 | if (element) | |
946 | element.val("").change(); | |
947 | this.update(); | |
948 | this._trigger('changeDate'); | |
949 | if (this.o.autoclose) | |
950 | this.hide(); | |
951 | break; | |
952 | } | |
953 | break; | |
954 | case 'span': | |
955 | if (!target.is('.disabled')){ | |
956 | this.viewDate.setUTCDate(1); | |
957 | if (target.is('.month')){ | |
958 | day = 1; | |
959 | month = target.parent().find('span').index(target); | |
960 | year = this.viewDate.getUTCFullYear(); | |
961 | this.viewDate.setUTCMonth(month); | |
962 | this._trigger('changeMonth', this.viewDate); | |
963 | if (this.o.minViewMode === 1){ | |
964 | this._setDate(UTCDate(year, month, day)); | |
965 | } | |
966 | } | |
967 | else { | |
968 | day = 1; | |
969 | month = 0; | |
970 | year = parseInt(target.text(), 10)||0; | |
971 | this.viewDate.setUTCFullYear(year); | |
972 | this._trigger('changeYear', this.viewDate); | |
973 | if (this.o.minViewMode === 2){ | |
974 | this._setDate(UTCDate(year, month, day)); | |
975 | } | |
976 | } | |
977 | this.showMode(-1); | |
978 | this.fill(); | |
979 | } | |
980 | break; | |
981 | case 'td': | |
982 | if (target.is('.day') && !target.is('.disabled')){ | |
983 | day = parseInt(target.text(), 10)||1; | |
984 | year = this.viewDate.getUTCFullYear(); | |
985 | month = this.viewDate.getUTCMonth(); | |
986 | if (target.is('.old')){ | |
987 | if (month === 0){ | |
988 | month = 11; | |
989 | year -= 1; | |
990 | } | |
991 | else { | |
992 | month -= 1; | |
993 | } | |
994 | } | |
995 | else if (target.is('.new')){ | |
996 | if (month === 11){ | |
997 | month = 0; | |
998 | year += 1; | |
999 | } | |
1000 | else { | |
1001 | month += 1; | |
1002 | } | |
1003 | } | |
1004 | this._setDate(UTCDate(year, month, day)); | |
1005 | } | |
1006 | break; | |
1007 | } | |
1008 | } | |
1009 | if (this.picker.is(':visible') && this._focused_from){ | |
1010 | $(this._focused_from).focus(); | |
1011 | } | |
1012 | delete this._focused_from; | |
1013 | }, | |
1014 | ||
1015 | _toggle_multidate: function(date){ | |
1016 | var ix = this.dates.contains(date); | |
1017 | if (!date){ | |
1018 | this.dates.clear(); | |
1019 | } | |
1020 | else if (ix !== -1){ | |
1021 | this.dates.remove(ix); | |
1022 | } | |
1023 | else { | |
1024 | this.dates.push(date); | |
1025 | } | |
1026 | if (typeof this.o.multidate === 'number') | |
1027 | while (this.dates.length > this.o.multidate) | |
1028 | this.dates.remove(0); | |
1029 | }, | |
1030 | ||
1031 | _setDate: function(date, which){ | |
1032 | if (!which || which === 'date') | |
1033 | this._toggle_multidate(date && new Date(date)); | |
1034 | if (!which || which === 'view') | |
1035 | this.viewDate = date && new Date(date); | |
1036 | ||
1037 | this.fill(); | |
1038 | this.setValue(); | |
1039 | this._trigger('changeDate'); | |
1040 | var element; | |
1041 | if (this.isInput){ | |
1042 | element = this.element; | |
1043 | } | |
1044 | else if (this.component){ | |
1045 | element = this.element.find('input'); | |
1046 | } | |
1047 | if (element){ | |
1048 | element.change(); | |
1049 | } | |
1050 | if (this.o.autoclose && (!which || which === 'date')){ | |
1051 | this.hide(); | |
1052 | } | |
1053 | }, | |
1054 | ||
1055 | moveMonth: function(date, dir){ | |
1056 | if (!date) | |
1057 | return undefined; | |
1058 | if (!dir) | |
1059 | return date; | |
1060 | var new_date = new Date(date.valueOf()), | |
1061 | day = new_date.getUTCDate(), | |
1062 | month = new_date.getUTCMonth(), | |
1063 | mag = Math.abs(dir), | |
1064 | new_month, test; | |
1065 | dir = dir > 0 ? 1 : -1; | |
1066 | if (mag === 1){ | |
1067 | test = dir === -1 | |
1068 | // If going back one month, make sure month is not current month | |
1069 | // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02) | |
1070 | ? function(){ | |
1071 | return new_date.getUTCMonth() === month; | |
1072 | } | |
1073 | // If going forward one month, make sure month is as expected | |
1074 | // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02) | |
1075 | : function(){ | |
1076 | return new_date.getUTCMonth() !== new_month; | |
1077 | }; | |
1078 | new_month = month + dir; | |
1079 | new_date.setUTCMonth(new_month); | |
1080 | // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 | |
1081 | if (new_month < 0 || new_month > 11) | |
1082 | new_month = (new_month + 12) % 12; | |
1083 | } | |
1084 | else { | |
1085 | // For magnitudes >1, move one month at a time... | |
1086 | for (var i=0; i < mag; i++) | |
1087 | // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)... | |
1088 | new_date = this.moveMonth(new_date, dir); | |
1089 | // ...then reset the day, keeping it in the new month | |
1090 | new_month = new_date.getUTCMonth(); | |
1091 | new_date.setUTCDate(day); | |
1092 | test = function(){ | |
1093 | return new_month !== new_date.getUTCMonth(); | |
1094 | }; | |
1095 | } | |
1096 | // Common date-resetting loop -- if date is beyond end of month, make it | |
1097 | // end of month | |
1098 | while (test()){ | |
1099 | new_date.setUTCDate(--day); | |
1100 | new_date.setUTCMonth(new_month); | |
1101 | } | |
1102 | return new_date; | |
1103 | }, | |
1104 | ||
1105 | moveYear: function(date, dir){ | |
1106 | return this.moveMonth(date, dir*12); | |
1107 | }, | |
1108 | ||
1109 | dateWithinRange: function(date){ | |
1110 | return date >= this.o.startDate && date <= this.o.endDate; | |
1111 | }, | |
1112 | ||
1113 | keydown: function(e){ | |
1114 | if (this.picker.is(':not(:visible)')){ | |
1115 | if (e.keyCode === 27) // allow escape to hide and re-show picker | |
1116 | this.show(); | |
1117 | return; | |
1118 | } | |
1119 | var dateChanged = false, | |
1120 | dir, newDate, newViewDate, | |
1121 | focusDate = this.focusDate || this.viewDate; | |
1122 | switch (e.keyCode){ | |
1123 | case 27: // escape | |
1124 | if (this.focusDate){ | |
1125 | this.focusDate = null; | |
1126 | this.viewDate = this.dates.get(-1) || this.viewDate; | |
1127 | this.fill(); | |
1128 | } | |
1129 | else | |
1130 | this.hide(); | |
1131 | e.preventDefault(); | |
1132 | break; | |
1133 | case 37: // left | |
1134 | case 39: // right | |
1135 | if (!this.o.keyboardNavigation) | |
1136 | break; | |
1137 | dir = e.keyCode === 37 ? -1 : 1; | |
1138 | if (e.ctrlKey){ | |
1139 | newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir); | |
1140 | newViewDate = this.moveYear(focusDate, dir); | |
1141 | this._trigger('changeYear', this.viewDate); | |
1142 | } | |
1143 | else if (e.shiftKey){ | |
1144 | newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir); | |
1145 | newViewDate = this.moveMonth(focusDate, dir); | |
1146 | this._trigger('changeMonth', this.viewDate); | |
1147 | } | |
1148 | else { | |
1149 | newDate = new Date(this.dates.get(-1) || UTCToday()); | |
1150 | newDate.setUTCDate(newDate.getUTCDate() + dir); | |
1151 | newViewDate = new Date(focusDate); | |
1152 | newViewDate.setUTCDate(focusDate.getUTCDate() + dir); | |
1153 | } | |
1154 | if (this.dateWithinRange(newDate)){ | |
1155 | this.focusDate = this.viewDate = newViewDate; | |
1156 | this.setValue(); | |
1157 | this.fill(); | |
1158 | e.preventDefault(); | |
1159 | } | |
1160 | break; | |
1161 | case 38: // up | |
1162 | case 40: // down | |
1163 | if (!this.o.keyboardNavigation) | |
1164 | break; | |
1165 | dir = e.keyCode === 38 ? -1 : 1; | |
1166 | if (e.ctrlKey){ | |
1167 | newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir); | |
1168 | newViewDate = this.moveYear(focusDate, dir); | |
1169 | this._trigger('changeYear', this.viewDate); | |
1170 | } | |
1171 | else if (e.shiftKey){ | |
1172 | newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir); | |
1173 | newViewDate = this.moveMonth(focusDate, dir); | |
1174 | this._trigger('changeMonth', this.viewDate); | |
1175 | } | |
1176 | else { | |
1177 | newDate = new Date(this.dates.get(-1) || UTCToday()); | |
1178 | newDate.setUTCDate(newDate.getUTCDate() + dir * 7); | |
1179 | newViewDate = new Date(focusDate); | |
1180 | newViewDate.setUTCDate(focusDate.getUTCDate() + dir * 7); | |
1181 | } | |
1182 | if (this.dateWithinRange(newDate)){ | |
1183 | this.focusDate = this.viewDate = newViewDate; | |
1184 | this.setValue(); | |
1185 | this.fill(); | |
1186 | e.preventDefault(); | |
1187 | } | |
1188 | break; | |
1189 | case 32: // spacebar | |
1190 | // Spacebar is used in manually typing dates in some formats. | |
1191 | // As such, its behavior should not be hijacked. | |
1192 | break; | |
1193 | case 13: // enter | |
1194 | focusDate = this.focusDate || this.dates.get(-1) || this.viewDate; | |
1195 | this._toggle_multidate(focusDate); | |
1196 | dateChanged = true; | |
1197 | this.focusDate = null; | |
1198 | this.viewDate = this.dates.get(-1) || this.viewDate; | |
1199 | this.setValue(); | |
1200 | this.fill(); | |
1201 | if (this.picker.is(':visible')){ | |
1202 | e.preventDefault(); | |
1203 | if (this.o.autoclose) | |
1204 | this.hide(); | |
1205 | } | |
1206 | break; | |
1207 | case 9: // tab | |
1208 | this.focusDate = null; | |
1209 | this.viewDate = this.dates.get(-1) || this.viewDate; | |
1210 | this.fill(); | |
1211 | this.hide(); | |
1212 | break; | |
1213 | } | |
1214 | if (dateChanged){ | |
1215 | if (this.dates.length) | |
1216 | this._trigger('changeDate'); | |
1217 | else | |
1218 | this._trigger('clearDate'); | |
1219 | var element; | |
1220 | if (this.isInput){ | |
1221 | element = this.element; | |
1222 | } | |
1223 | else if (this.component){ | |
1224 | element = this.element.find('input'); | |
1225 | } | |
1226 | if (element){ | |
1227 | element.change(); | |
1228 | } | |
1229 | } | |
1230 | }, | |
1231 | ||
1232 | showMode: function(dir){ | |
1233 | if (dir){ | |
1234 | this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir)); | |
1235 | } | |
1236 | this.picker | |
1237 | .find('>div') | |
1238 | .hide() | |
1239 | .filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName) | |
1240 | .css('display', 'block'); | |
1241 | this.updateNavArrows(); | |
1242 | } | |
1243 | }; | |
1244 | ||
1245 | var DateRangePicker = function(element, options){ | |
1246 | this.element = $(element); | |
1247 | this.inputs = $.map(options.inputs, function(i){ | |
1248 | return i.jquery ? i[0] : i; | |
1249 | }); | |
1250 | delete options.inputs; | |
1251 | ||
1252 | $(this.inputs) | |
1253 | .datepicker(options) | |
1254 | .bind('changeDate', $.proxy(this.dateUpdated, this)); | |
1255 | ||
1256 | this.pickers = $.map(this.inputs, function(i){ | |
1257 | return $(i).data('datepicker'); | |
1258 | }); | |
1259 | this.updateDates(); | |
1260 | }; | |
1261 | DateRangePicker.prototype = { | |
1262 | updateDates: function(){ | |
1263 | this.dates = $.map(this.pickers, function(i){ | |
1264 | return i.getUTCDate(); | |
1265 | }); | |
1266 | this.updateRanges(); | |
1267 | }, | |
1268 | updateRanges: function(){ | |
1269 | var range = $.map(this.dates, function(d){ | |
1270 | return d.valueOf(); | |
1271 | }); | |
1272 | $.each(this.pickers, function(i, p){ | |
1273 | p.setRange(range); | |
1274 | }); | |
1275 | }, | |
1276 | dateUpdated: function(e){ | |
1277 | // `this.updating` is a workaround for preventing infinite recursion | |
1278 | // between `changeDate` triggering and `setUTCDate` calling. Until | |
1279 | // there is a better mechanism. | |
1280 | if (this.updating) | |
1281 | return; | |
1282 | this.updating = true; | |
1283 | ||
1284 | var dp = $(e.target).data('datepicker'), | |
1285 | new_date = dp.getUTCDate(), | |
1286 | i = $.inArray(e.target, this.inputs), | |
1287 | l = this.inputs.length; | |
1288 | if (i === -1) | |
1289 | return; | |
1290 | ||
1291 | $.each(this.pickers, function(i, p){ | |
1292 | if (!p.getUTCDate()) | |
1293 | p.setUTCDate(new_date); | |
1294 | }); | |
1295 | ||
1296 | if (new_date < this.dates[i]){ | |
1297 | // Date being moved earlier/left | |
1298 | while (i >= 0 && new_date < this.dates[i]){ | |
1299 | this.pickers[i--].setUTCDate(new_date); | |
1300 | } | |
1301 | } | |
1302 | else if (new_date > this.dates[i]){ | |
1303 | // Date being moved later/right | |
1304 | while (i < l && new_date > this.dates[i]){ | |
1305 | this.pickers[i++].setUTCDate(new_date); | |
1306 | } | |
1307 | } | |
1308 | this.updateDates(); | |
1309 | ||
1310 | delete this.updating; | |
1311 | }, | |
1312 | remove: function(){ | |
1313 | $.map(this.pickers, function(p){ p.remove(); }); | |
1314 | delete this.element.data().datepicker; | |
1315 | } | |
1316 | }; | |
1317 | ||
1318 | function opts_from_el(el, prefix){ | |
1319 | // Derive options from element data-attrs | |
1320 | var data = $(el).data(), | |
1321 | out = {}, inkey, | |
1322 | replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'); | |
1323 | prefix = new RegExp('^' + prefix.toLowerCase()); | |
1324 | function re_lower(_,a){ | |
1325 | return a.toLowerCase(); | |
1326 | } | |
1327 | for (var key in data) | |
1328 | if (prefix.test(key)){ | |
1329 | inkey = key.replace(replace, re_lower); | |
1330 | out[inkey] = data[key]; | |
1331 | } | |
1332 | return out; | |
1333 | } | |
1334 | ||
1335 | function opts_from_locale(lang){ | |
1336 | // Derive options from locale plugins | |
1337 | var out = {}; | |
1338 | // Check if "de-DE" style date is available, if not language should | |
1339 | // fallback to 2 letter code eg "de" | |
1340 | if (!dates[lang]){ | |
1341 | lang = lang.split('-')[0]; | |
1342 | if (!dates[lang]) | |
1343 | return; | |
1344 | } | |
1345 | var d = dates[lang]; | |
1346 | $.each(locale_opts, function(i,k){ | |
1347 | if (k in d) | |
1348 | out[k] = d[k]; | |
1349 | }); | |
1350 | return out; | |
1351 | } | |
1352 | ||
1353 | var old = $.fn.datepicker; | |
1354 | $.fn.datepicker = function(option){ | |
1355 | var args = Array.apply(null, arguments); | |
1356 | args.shift(); | |
1357 | var internal_return; | |
1358 | this.each(function(){ | |
1359 | var $this = $(this), | |
1360 | data = $this.data('datepicker'), | |
1361 | options = typeof option === 'object' && option; | |
1362 | if (!data){ | |
1363 | var elopts = opts_from_el(this, 'date'), | |
1364 | // Preliminary otions | |
1365 | xopts = $.extend({}, defaults, elopts, options), | |
1366 | locopts = opts_from_locale(xopts.language), | |
1367 | // Options priority: js args, data-attrs, locales, defaults | |
1368 | opts = $.extend({}, defaults, locopts, elopts, options); | |
1369 | if ($this.is('.input-daterange') || opts.inputs){ | |
1370 | var ropts = { | |
1371 | inputs: opts.inputs || $this.find('input').toArray() | |
1372 | }; | |
1373 | $this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts)))); | |
1374 | } | |
1375 | else { | |
1376 | $this.data('datepicker', (data = new Datepicker(this, opts))); | |
1377 | } | |
1378 | } | |
1379 | if (typeof option === 'string' && typeof data[option] === 'function'){ | |
1380 | internal_return = data[option].apply(data, args); | |
1381 | if (internal_return !== undefined) | |
1382 | return false; | |
1383 | } | |
1384 | }); | |
1385 | if (internal_return !== undefined) | |
1386 | return internal_return; | |
1387 | else | |
1388 | return this; | |
1389 | }; | |
1390 | ||
1391 | var defaults = $.fn.datepicker.defaults = { | |
1392 | autoclose: false, | |
1393 | beforeShowDay: $.noop, | |
1394 | calendarWeeks: false, | |
1395 | clearBtn: false, | |
1396 | daysOfWeekDisabled: [], | |
1397 | endDate: Infinity, | |
1398 | forceParse: true, | |
1399 | format: 'mm/dd/yyyy', | |
1400 | keyboardNavigation: true, | |
1401 | language: 'en', | |
1402 | minViewMode: 0, | |
1403 | multidate: false, | |
1404 | multidateSeparator: ',', | |
1405 | orientation: "auto", | |
1406 | rtl: false, | |
1407 | startDate: -Infinity, | |
1408 | startView: 0, | |
1409 | todayBtn: false, | |
1410 | todayHighlight: false, | |
1411 | weekStart: 0 | |
1412 | }; | |
1413 | var locale_opts = $.fn.datepicker.locale_opts = [ | |
1414 | 'format', | |
1415 | 'rtl', | |
1416 | 'weekStart' | |
1417 | ]; | |
1418 | $.fn.datepicker.Constructor = Datepicker; | |
1419 | var dates = $.fn.datepicker.dates = { | |
1420 | en: { | |
1421 | days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], | |
1422 | daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], | |
1423 | daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], | |
1424 | months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], | |
1425 | monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], | |
1426 | today: "Today", | |
1427 | clear: "Clear" | |
1428 | } | |
1429 | }; | |
1430 | ||
1431 | var DPGlobal = { | |
1432 | modes: [ | |
1433 | { | |
1434 | clsName: 'days', | |
1435 | navFnc: 'Month', | |
1436 | navStep: 1 | |
1437 | }, | |
1438 | { | |
1439 | clsName: 'months', | |
1440 | navFnc: 'FullYear', | |
1441 | navStep: 1 | |
1442 | }, | |
1443 | { | |
1444 | clsName: 'years', | |
1445 | navFnc: 'FullYear', | |
1446 | navStep: 10 | |
1447 | }], | |
1448 | isLeapYear: function(year){ | |
1449 | return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); | |
1450 | }, | |
1451 | getDaysInMonth: function(year, month){ | |
1452 | return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; | |
1453 | }, | |
1454 | validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g, | |
1455 | nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g, | |
1456 | parseFormat: function(format){ | |
1457 | // IE treats \0 as a string end in inputs (truncating the value), | |
1458 | // so it's a bad format delimiter, anyway | |
1459 | var separators = format.replace(this.validParts, '\0').split('\0'), | |
1460 | parts = format.match(this.validParts); | |
1461 | if (!separators || !separators.length || !parts || parts.length === 0){ | |
1462 | throw new Error("Invalid date format."); | |
1463 | } | |
1464 | return {separators: separators, parts: parts}; | |
1465 | }, | |
1466 | parseDate: function(date, format, language){ | |
1467 | if (!date) | |
1468 | return undefined; | |
1469 | if (date instanceof Date) | |
1470 | return date; | |
1471 | if (typeof format === 'string') | |
1472 | format = DPGlobal.parseFormat(format); | |
1473 | var part_re = /([\-+]\d+)([dmwy])/, | |
1474 | parts = date.match(/([\-+]\d+)([dmwy])/g), | |
1475 | part, dir, i; | |
1476 | if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)){ | |
1477 | date = new Date(); | |
1478 | for (i=0; i < parts.length; i++){ | |
1479 | part = part_re.exec(parts[i]); | |
1480 | dir = parseInt(part[1]); | |
1481 | switch (part[2]){ | |
1482 | case 'd': | |
1483 | date.setUTCDate(date.getUTCDate() + dir); | |
1484 | break; | |
1485 | case 'm': | |
1486 | date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir); | |
1487 | break; | |
1488 | case 'w': | |
1489 | date.setUTCDate(date.getUTCDate() + dir * 7); | |
1490 | break; | |
1491 | case 'y': | |
1492 | date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir); | |
1493 | break; | |
1494 | } | |
1495 | } | |
1496 | return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0); | |
1497 | } | |
1498 | parts = date && date.match(this.nonpunctuation) || []; | |
1499 | date = new Date(); | |
1500 | var parsed = {}, | |
1501 | setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'], | |
1502 | setters_map = { | |
1503 | yyyy: function(d,v){ | |
1504 | return d.setUTCFullYear(v); | |
1505 | }, | |
1506 | yy: function(d,v){ | |
1507 | return d.setUTCFullYear(2000+v); | |
1508 | }, | |
1509 | m: function(d,v){ | |
1510 | if (isNaN(d)) | |
1511 | return d; | |
1512 | v -= 1; | |
1513 | while (v < 0) v += 12; | |
1514 | v %= 12; | |
1515 | d.setUTCMonth(v); | |
1516 | while (d.getUTCMonth() !== v) | |
1517 | d.setUTCDate(d.getUTCDate()-1); | |
1518 | return d; | |
1519 | }, | |
1520 | d: function(d,v){ | |
1521 | return d.setUTCDate(v); | |
1522 | } | |
1523 | }, | |
1524 | val, filtered; | |
1525 | setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m']; | |
1526 | setters_map['dd'] = setters_map['d']; | |
1527 | date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); | |
1528 | var fparts = format.parts.slice(); | |
1529 | // Remove noop parts | |
1530 | if (parts.length !== fparts.length){ | |
1531 | fparts = $(fparts).filter(function(i,p){ | |
1532 | return $.inArray(p, setters_order) !== -1; | |
1533 | }).toArray(); | |
1534 | } | |
1535 | // Process remainder | |
1536 | function match_part(){ | |
1537 | var m = this.slice(0, parts[i].length), | |
1538 | p = parts[i].slice(0, m.length); | |
1539 | return m === p; | |
1540 | } | |
1541 | if (parts.length === fparts.length){ | |
1542 | var cnt; | |
1543 | for (i=0, cnt = fparts.length; i < cnt; i++){ | |
1544 | val = parseInt(parts[i], 10); | |
1545 | part = fparts[i]; | |
1546 | if (isNaN(val)){ | |
1547 | switch (part){ | |
1548 | case 'MM': | |
1549 | filtered = $(dates[language].months).filter(match_part); | |
1550 | val = $.inArray(filtered[0], dates[language].months) + 1; | |
1551 | break; | |
1552 | case 'M': | |
1553 | filtered = $(dates[language].monthsShort).filter(match_part); | |
1554 | val = $.inArray(filtered[0], dates[language].monthsShort) + 1; | |
1555 | break; | |
1556 | } | |
1557 | } | |
1558 | parsed[part] = val; | |
1559 | } | |
1560 | var _date, s; | |
1561 | for (i=0; i < setters_order.length; i++){ | |
1562 | s = setters_order[i]; | |
1563 | if (s in parsed && !isNaN(parsed[s])){ | |
1564 | _date = new Date(date); | |
1565 | setters_map[s](_date, parsed[s]); | |
1566 | if (!isNaN(_date)) | |
1567 | date = _date; | |
1568 | } | |
1569 | } | |
1570 | } | |
1571 | return date; | |
1572 | }, | |
1573 | formatDate: function(date, format, language){ | |
1574 | if (!date) | |
1575 | return ''; | |
1576 | if (typeof format === 'string') | |
1577 | format = DPGlobal.parseFormat(format); | |
1578 | var val = { | |
1579 | d: date.getUTCDate(), | |
1580 | D: dates[language].daysShort[date.getUTCDay()], | |
1581 | DD: dates[language].days[date.getUTCDay()], | |
1582 | m: date.getUTCMonth() + 1, | |
1583 | M: dates[language].monthsShort[date.getUTCMonth()], | |
1584 | MM: dates[language].months[date.getUTCMonth()], | |
1585 | yy: date.getUTCFullYear().toString().substring(2), | |
1586 | yyyy: date.getUTCFullYear() | |
1587 | }; | |
1588 | val.dd = (val.d < 10 ? '0' : '') + val.d; | |
1589 | val.mm = (val.m < 10 ? '0' : '') + val.m; | |
1590 | date = []; | |
1591 | var seps = $.extend([], format.separators); | |
1592 | for (var i=0, cnt = format.parts.length; i <= cnt; i++){ | |
1593 | if (seps.length) | |
1594 | date.push(seps.shift()); | |
1595 | date.push(val[format.parts[i]]); | |
1596 | } | |
1597 | return date.join(''); | |
1598 | }, | |
1599 | headTemplate: '<thead>'+ | |
1600 | '<tr>'+ | |
1601 | '<th class="prev">«</th>'+ | |
1602 | '<th colspan="5" class="datepicker-switch"></th>'+ | |
1603 | '<th class="next">»</th>'+ | |
1604 | '</tr>'+ | |
1605 | '</thead>', | |
1606 | contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>', | |
1607 | footTemplate: '<tfoot>'+ | |
1608 | '<tr>'+ | |
1609 | '<th colspan="7" class="today"></th>'+ | |
1610 | '</tr>'+ | |
1611 | '<tr>'+ | |
1612 | '<th colspan="7" class="clear"></th>'+ | |
1613 | '</tr>'+ | |
1614 | '</tfoot>' | |
1615 | }; | |
1616 | DPGlobal.template = '<div class="datepicker">'+ | |
1617 | '<div class="datepicker-days">'+ | |
1618 | '<table class="table table-condensed">'+ | |
1619 | DPGlobal.headTemplate+ | |
1620 | '<tbody></tbody>'+ | |
1621 | DPGlobal.footTemplate+ | |
1622 | '</table>'+ | |
1623 | '</div>'+ | |
1624 | '<div class="datepicker-months">'+ | |
1625 | '<table class="table table-condensed">'+ | |
1626 | DPGlobal.headTemplate+ | |
1627 | DPGlobal.contTemplate+ | |
1628 | DPGlobal.footTemplate+ | |
1629 | '</table>'+ | |
1630 | '</div>'+ | |
1631 | '<div class="datepicker-years">'+ | |
1632 | '<table class="table table-condensed">'+ | |
1633 | DPGlobal.headTemplate+ | |
1634 | DPGlobal.contTemplate+ | |
1635 | DPGlobal.footTemplate+ | |
1636 | '</table>'+ | |
1637 | '</div>'+ | |
1638 | '</div>'; | |
1639 | ||
1640 | $.fn.datepicker.DPGlobal = DPGlobal; | |
1641 | ||
1642 | ||
1643 | /* DATEPICKER NO CONFLICT | |
1644 | * =================== */ | |
1645 | ||
1646 | $.fn.datepicker.noConflict = function(){ | |
1647 | $.fn.datepicker = old; | |
1648 | return this; | |
1649 | }; | |
1650 | ||
1651 | ||
1652 | /* DATEPICKER DATA-API | |
1653 | * ================== */ | |
1654 | ||
1655 | $(document).on( | |
1656 | 'focus.datepicker.data-api click.datepicker.data-api', | |
1657 | '[data-provide="datepicker"]', | |
1658 | function(e){ | |
1659 | var $this = $(this); | |
1660 | if ($this.data('datepicker')) | |
1661 | return; | |
1662 | e.preventDefault(); | |
1663 | // component click requires us to explicitly show it | |
1664 | $this.datepicker('show'); | |
1665 | } | |
1666 | ); | |
1667 | $(function(){ | |
1668 | $('[data-provide="datepicker-inline"]').datepicker(); | |
1669 | }); | |
1670 | ||
1671 | }(window.jQuery)); |