hjg
2024-07-09 30304784e82d4bba24121328da8eb8490aec4f4f
提交 | 用户 | 时间
58d006 1 /*! version : 4.17.37
A 2  =========================================================
3  bootstrap-datetimejs
4  https://github.com/Eonasdan/bootstrap-datetimepicker
5  Copyright (c) 2015 Jonathan Peterson
6  =========================================================
7  */
8 /*
9  The MIT License (MIT)
10
11  Copyright (c) 2015 Jonathan Peterson
12
13  Permission is hereby granted, free of charge, to any person obtaining a copy
14  of this software and associated documentation files (the "Software"), to deal
15  in the Software without restriction, including without limitation the rights
16  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17  copies of the Software, and to permit persons to whom the Software is
18  furnished to do so, subject to the following conditions:
19
20  The above copyright notice and this permission notice shall be included in
21  all copies or substantial portions of the Software.
22
23  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29  THE SOFTWARE.
30  */
31 /*global define:false */
32 /*global exports:false */
33 /*global require:false */
34 /*global jQuery:false */
35 /*global moment:false */
36 (function (factory) {
37     'use strict';
38     if (typeof define === 'function' && define.amd) {
39         // AMD is used - Register as an anonymous module.
40         define(['jquery', 'moment'], factory);
41     } else if (typeof exports === 'object') {
42         factory(require('jquery'), require('moment'));
43     } else {
44         // Neither AMD nor CommonJS used. Use global variables.
45         if (typeof jQuery === 'undefined') {
46             throw 'bootstrap-datetimepicker requires jQuery to be loaded first';
47         }
48         if (typeof moment === 'undefined') {
49             throw 'bootstrap-datetimepicker requires Moment.js to be loaded first';
50         }
51         factory(jQuery, moment);
52     }
53 }(function ($, moment) {
54     'use strict';
55     if (!moment) {
56         throw new Error('bootstrap-datetimepicker requires Moment.js to be loaded first');
57     }
58
59     var dateTimePicker = function (element, options) {
60         var picker = {},
61             date,
62             viewDate,
63             unset = true,
64             input,
65             component = false,
66             widget = false,
67             use24Hours,
68             minViewModeNumber = 0,
69             actualFormat,
70             parseFormats,
71             currentViewMode,
72             datePickerModes = [
73                 {
74                     clsName: 'days',
75                     navFnc: 'M',
76                     navStep: 1
77                 },
78                 {
79                     clsName: 'months',
80                     navFnc: 'y',
81                     navStep: 1
82                 },
83                 {
84                     clsName: 'years',
85                     navFnc: 'y',
86                     navStep: 10
87                 },
88                 {
89                     clsName: 'decades',
90                     navFnc: 'y',
91                     navStep: 100
92                 }
93             ],
94             viewModes = ['days', 'months', 'years', 'decades'],
95             verticalModes = ['top', 'bottom', 'auto'],
96             horizontalModes = ['left', 'right', 'auto'],
97             toolbarPlacements = ['default', 'top', 'bottom'],
98             keyMap = {
99                 'up': 38,
100                 38: 'up',
101                 'down': 40,
102                 40: 'down',
103                 'left': 37,
104                 37: 'left',
105                 'right': 39,
106                 39: 'right',
107                 'tab': 9,
108                 9: 'tab',
109                 'escape': 27,
110                 27: 'escape',
111                 'enter': 13,
112                 13: 'enter',
113                 'pageUp': 33,
114                 33: 'pageUp',
115                 'pageDown': 34,
116                 34: 'pageDown',
117                 'shift': 16,
118                 16: 'shift',
119                 'control': 17,
120                 17: 'control',
121                 'space': 32,
122                 32: 'space',
123                 't': 84,
124                 84: 't',
125                 'delete': 46,
126                 46: 'delete'
127             },
128             keyState = {},
129
130             /********************************************************************************
131              *
132              * Private functions
133              *
134              ********************************************************************************/
135             getMoment = function (d) {
136                 var tzEnabled = false,
137                     returnMoment,
138                     currentZoneOffset,
139                     incomingZoneOffset,
140                     timeZoneIndicator,
141                     dateWithTimeZoneInfo;
142
143                 if (moment.tz !== undefined && options.timeZone !== undefined && options.timeZone !== null && options.timeZone !== '') {
144                     tzEnabled = true;
145                 }
146                 if (d === undefined || d === null) {
147                     if (tzEnabled) {
148                         returnMoment = moment().tz(options.timeZone).startOf('d');
149                     } else {
150                         returnMoment = moment().startOf('d');
151                     }
152                 } else {
153                     if (tzEnabled) {
154                         currentZoneOffset = moment().tz(options.timeZone).utcOffset();
155                         incomingZoneOffset = moment(d, parseFormats, options.useStrict).utcOffset();
156                         if (incomingZoneOffset !== currentZoneOffset) {
157                             timeZoneIndicator = moment().tz(options.timeZone).format('Z');
158                             dateWithTimeZoneInfo = moment(d, parseFormats, options.useStrict).format('YYYY-MM-DD[T]HH:mm:ss') + timeZoneIndicator;
159                             returnMoment = moment(dateWithTimeZoneInfo, parseFormats, options.useStrict).tz(options.timeZone);
160                         } else {
161                             returnMoment = moment(d, parseFormats, options.useStrict).tz(options.timeZone);
162                         }
163                     } else {
164                         returnMoment = moment(d, parseFormats, options.useStrict);
165                     }
166                 }
167                 return returnMoment;
168             },
169             isEnabled = function (granularity) {
170                 if (typeof granularity !== 'string' || granularity.length > 1) {
171                     throw new TypeError('isEnabled expects a single character string parameter');
172                 }
173                 switch (granularity) {
174                     case 'y':
175                         return actualFormat.indexOf('Y') !== -1;
176                     case 'M':
177                         return actualFormat.indexOf('M') !== -1;
178                     case 'd':
179                         return actualFormat.toLowerCase().indexOf('d') !== -1;
180                     case 'h':
181                     case 'H':
182                         return actualFormat.toLowerCase().indexOf('h') !== -1;
183                     case 'm':
184                         return actualFormat.indexOf('m') !== -1;
185                     case 's':
186                         return actualFormat.indexOf('s') !== -1;
187                     default:
188                         return false;
189                 }
190             },
191             hasTime = function () {
192                 return (isEnabled('h') || isEnabled('m') || isEnabled('s'));
193             },
194
195             hasDate = function () {
196                 return (isEnabled('y') || isEnabled('M') || isEnabled('d'));
197             },
198
199             getDatePickerTemplate = function () {
200                 var headTemplate = $('<thead>')
201                         .append($('<tr>')
202                             .append($('<th>').addClass('prev').attr('data-action', 'previous')
203                                 .append($('<span>').addClass(options.icons.previous))
204                                 )
205                             .append($('<th>').addClass('picker-switch').attr('data-action', 'pickerSwitch').attr('colspan', (options.calendarWeeks ? '6' : '5')))
206                             .append($('<th>').addClass('next').attr('data-action', 'next')
207                                 .append($('<span>').addClass(options.icons.next))
208                                 )
209                             ),
210                     contTemplate = $('<tbody>')
211                         .append($('<tr>')
212                             .append($('<td>').attr('colspan', (options.calendarWeeks ? '8' : '7')))
213                             );
214
215                 return [
216                     $('<div>').addClass('datepicker-days')
217                         .append($('<table>').addClass('table-condensed')
218                             .append(headTemplate)
219                             .append($('<tbody>'))
220                             ),
221                     $('<div>').addClass('datepicker-months')
222                         .append($('<table>').addClass('table-condensed')
223                             .append(headTemplate.clone())
224                             .append(contTemplate.clone())
225                             ),
226                     $('<div>').addClass('datepicker-years')
227                         .append($('<table>').addClass('table-condensed')
228                             .append(headTemplate.clone())
229                             .append(contTemplate.clone())
230                             ),
231                     $('<div>').addClass('datepicker-decades')
232                         .append($('<table>').addClass('table-condensed')
233                             .append(headTemplate.clone())
234                             .append(contTemplate.clone())
235                             )
236                 ];
237             },
238
239             getTimePickerMainTemplate = function () {
240                 var topRow = $('<tr>'),
241                     middleRow = $('<tr>'),
242                     bottomRow = $('<tr>');
243
244                 if (isEnabled('h')) {
245                     topRow.append($('<td>')
246                         .append($('<a>').attr({href: '#', tabindex: '-1', 'title': options.tooltips.incrementHour}).addClass('btn').attr('data-action', 'incrementHours')
247                             .append($('<span>').addClass(options.icons.up))));
248                     middleRow.append($('<td>')
249                         .append($('<span>').addClass('timepicker-hour').attr({'data-time-component':'hours', 'title': options.tooltips.pickHour}).attr('data-action', 'showHours')));
250                     bottomRow.append($('<td>')
251                         .append($('<a>').attr({href: '#', tabindex: '-1', 'title': options.tooltips.decrementHour}).addClass('btn').attr('data-action', 'decrementHours')
252                             .append($('<span>').addClass(options.icons.down))));
253                 }
254                 if (isEnabled('m')) {
255                     if (isEnabled('h')) {
256                         topRow.append($('<td>').addClass('separator'));
257                         middleRow.append($('<td>').addClass('separator').html(':'));
258                         bottomRow.append($('<td>').addClass('separator'));
259                     }
260                     topRow.append($('<td>')
261                         .append($('<a>').attr({href: '#', tabindex: '-1', 'title': options.tooltips.incrementMinute}).addClass('btn').attr('data-action', 'incrementMinutes')
262                             .append($('<span>').addClass(options.icons.up))));
263                     middleRow.append($('<td>')
264                         .append($('<span>').addClass('timepicker-minute').attr({'data-time-component': 'minutes', 'title': options.tooltips.pickMinute}).attr('data-action', 'showMinutes')));
265                     bottomRow.append($('<td>')
266                         .append($('<a>').attr({href: '#', tabindex: '-1', 'title': options.tooltips.decrementMinute}).addClass('btn').attr('data-action', 'decrementMinutes')
267                             .append($('<span>').addClass(options.icons.down))));
268                 }
269                 if (isEnabled('s')) {
270                     if (isEnabled('m')) {
271                         topRow.append($('<td>').addClass('separator'));
272                         middleRow.append($('<td>').addClass('separator').html(':'));
273                         bottomRow.append($('<td>').addClass('separator'));
274                     }
275                     topRow.append($('<td>')
276                         .append($('<a>').attr({href: '#', tabindex: '-1', 'title': options.tooltips.incrementSecond}).addClass('btn').attr('data-action', 'incrementSeconds')
277                             .append($('<span>').addClass(options.icons.up))));
278                     middleRow.append($('<td>')
279                         .append($('<span>').addClass('timepicker-second').attr({'data-time-component': 'seconds', 'title': options.tooltips.pickSecond}).attr('data-action', 'showSeconds')));
280                     bottomRow.append($('<td>')
281                         .append($('<a>').attr({href: '#', tabindex: '-1', 'title': options.tooltips.decrementSecond}).addClass('btn').attr('data-action', 'decrementSeconds')
282                             .append($('<span>').addClass(options.icons.down))));
283                 }
284
285                 if (!use24Hours) {
286                     topRow.append($('<td>').addClass('separator'));
287                     middleRow.append($('<td>')
288                         .append($('<button>').addClass('btn btn-primary').attr({'data-action': 'togglePeriod', tabindex: '-1', 'title': options.tooltips.togglePeriod})));
289                     bottomRow.append($('<td>').addClass('separator'));
290                 }
291
292                 return $('<div>').addClass('timepicker-picker')
293                     .append($('<table>').addClass('table-condensed')
294                         .append([topRow, middleRow, bottomRow]));
295             },
296
297             getTimePickerTemplate = function () {
298                 var hoursView = $('<div>').addClass('timepicker-hours')
299                         .append($('<table>').addClass('table-condensed')),
300                     minutesView = $('<div>').addClass('timepicker-minutes')
301                         .append($('<table>').addClass('table-condensed')),
302                     secondsView = $('<div>').addClass('timepicker-seconds')
303                         .append($('<table>').addClass('table-condensed')),
304                     ret = [getTimePickerMainTemplate()];
305
306                 if (isEnabled('h')) {
307                     ret.push(hoursView);
308                 }
309                 if (isEnabled('m')) {
310                     ret.push(minutesView);
311                 }
312                 if (isEnabled('s')) {
313                     ret.push(secondsView);
314                 }
315
316                 return ret;
317             },
318
319             getToolbar = function () {
320                 var row = [];
321                 if (options.showTodayButton) {
322                     row.push($('<td>').append($('<a>').attr({'data-action':'today', 'title': options.tooltips.today}).append($('<span>').addClass(options.icons.today))));
323                 }
324                 if (!options.sideBySide && hasDate() && hasTime()) {
325                     row.push($('<td>').append($('<a>').attr({'data-action':'togglePicker', 'title': options.tooltips.selectTime}).append($('<span>').addClass(options.icons.time))));
326                 }
327                 if (options.showClear) {
328                     row.push($('<td>').append($('<a>').attr({'data-action':'clear', 'title': options.tooltips.clear}).append($('<span>').addClass(options.icons.clear))));
329                 }
330                 if (options.showClose) {
331                     row.push($('<td>').append($('<a>').attr({'data-action':'close', 'title': options.tooltips.close}).append($('<span>').addClass(options.icons.close))));
332                 }
333                 return $('<table>').addClass('table-condensed').append($('<tbody>').append($('<tr>').append(row)));
334             },
335
336             getTemplate = function () {
337                 var template = $('<div>').addClass('bootstrap-datetimepicker-widget dropdown-menu'),
338                     dateView = $('<div>').addClass('datepicker').append(getDatePickerTemplate()),
339                     timeView = $('<div>').addClass('timepicker').append(getTimePickerTemplate()),
340                     content = $('<ul>').addClass('list-unstyled'),
341                     toolbar = $('<li>').addClass('picker-switch' + (options.collapse ? ' accordion-toggle' : '')).append(getToolbar());
342
343                 if (options.inline) {
344                     template.removeClass('dropdown-menu');
345                 }
346
347                 if (use24Hours) {
348                     template.addClass('usetwentyfour');
349                 }
350                 if (isEnabled('s') && !use24Hours) {
351                     template.addClass('wider');
352                 }
353
354                 if (options.sideBySide && hasDate() && hasTime()) {
355                     template.addClass('timepicker-sbs');
356                     if (options.toolbarPlacement === 'top') {
357                         template.append(toolbar);
358                     }
359                     template.append(
360                         $('<div>').addClass('row')
361                             .append(dateView.addClass('col-md-6'))
362                             .append(timeView.addClass('col-md-6'))
363                     );
364                     if (options.toolbarPlacement === 'bottom') {
365                         template.append(toolbar);
366                     }
367                     return template;
368                 }
369
370                 if (options.toolbarPlacement === 'top') {
371                     content.append(toolbar);
372                 }
373                 if (hasDate()) {
374                     content.append($('<li>').addClass((options.collapse && hasTime() ? 'collapse in' : '')).append(dateView));
375                 }
376                 if (options.toolbarPlacement === 'default') {
377                     content.append(toolbar);
378                 }
379                 if (hasTime()) {
380                     content.append($('<li>').addClass((options.collapse && hasDate() ? 'collapse' : '')).append(timeView));
381                 }
382                 if (options.toolbarPlacement === 'bottom') {
383                     content.append(toolbar);
384                 }
385                 return template.append(content);
386             },
387
388             dataToOptions = function () {
389                 var eData,
390                     dataOptions = {};
391
392                 if (element.is('input') || options.inline) {
393                     eData = element.data();
394                 } else {
395                     eData = element.find('input').data();
396                 }
397
398                 if (eData.dateOptions && eData.dateOptions instanceof Object) {
399                     dataOptions = $.extend(true, dataOptions, eData.dateOptions);
400                 }
401
402                 $.each(options, function (key) {
403                     var attributeName = 'date' + key.charAt(0).toUpperCase() + key.slice(1);
404                     if (eData[attributeName] !== undefined) {
405                         dataOptions[key] = eData[attributeName];
406                     }
407                 });
408                 return dataOptions;
409             },
410
411             place = function () {
412                 var position = (component || element).position(),
413                     offset = (component || element).offset(),
414                     vertical = options.widgetPositioning.vertical,
415                     horizontal = options.widgetPositioning.horizontal,
416                     parent;
417
418                 if (options.widgetParent) {
419                     parent = options.widgetParent.append(widget);
420                 } else if (element.is('input')) {
421                     parent = element.after(widget).parent();
422                 } else if (options.inline) {
423                     parent = element.append(widget);
424                     return;
425                 } else {
426                     parent = element;
427                     element.children().first().after(widget);
428                 }
429
430                 // Top and bottom logic
431                 if (vertical === 'auto') {
432                     if (offset.top + widget.height() * 1.5 >= $(window).height() + $(window).scrollTop() &&
433                         widget.height() + element.outerHeight() < offset.top) {
434                         vertical = 'top';
435                     } else {
436                         vertical = 'bottom';
437                     }
438                 }
439
440                 // Left and right logic
441                 if (horizontal === 'auto') {
442                     if (parent.width() < offset.left + widget.outerWidth() / 2 &&
443                         offset.left + widget.outerWidth() > $(window).width()) {
444                         horizontal = 'right';
445                     } else {
446                         horizontal = 'left';
447                     }
448                 }
449
450                 if (vertical === 'top') {
451                     widget.addClass('top').removeClass('bottom');
452                 } else {
453                     widget.addClass('bottom').removeClass('top');
454                 }
455
456                 if (horizontal === 'right') {
457                     widget.addClass('pull-right');
458                 } else {
459                     widget.removeClass('pull-right');
460                 }
461
462                 // find the first parent element that has a relative css positioning
463                 if (parent.css('position') !== 'relative') {
464                     parent = parent.parents().filter(function () {
465                         return $(this).css('position') === 'relative';
466                     }).first();
467                 }
468
469                 if (parent.length === 0) {
470                     throw new Error('datetimepicker component should be placed within a relative positioned container');
471                 }
472
473                 widget.css({
474                     top: vertical === 'top' ? 'auto' : position.top + element.outerHeight(),
475                     bottom: vertical === 'top' ? position.top + element.outerHeight() : 'auto',
476                     left: horizontal === 'left' ? (parent === element ? 0 : position.left) : 'auto',
477                     right: horizontal === 'left' ? 'auto' : parent.outerWidth() - element.outerWidth() - (parent === element ? 0 : position.left)
478                 });
479             },
480
481             notifyEvent = function (e) {
482                 if (e.type === 'dp.change' && ((e.date && e.date.isSame(e.oldDate)) || (!e.date && !e.oldDate))) {
483                     return;
484                 }
485                 element.trigger(e);
486             },
487
488             viewUpdate = function (e) {
489                 if (e === 'y') {
490                     e = 'YYYY';
491                 }
492                 notifyEvent({
493                     type: 'dp.update',
494                     change: e,
495                     viewDate: viewDate.clone()
496                 });
497             },
498
499             showMode = function (dir) {
500                 if (!widget) {
501                     return;
502                 }
503                 if (dir) {
504                     currentViewMode = Math.max(minViewModeNumber, Math.min(3, currentViewMode + dir));
505                 }
506                 widget.find('.datepicker > div').hide().filter('.datepicker-' + datePickerModes[currentViewMode].clsName).show();
507             },
508
509             fillDow = function () {
510                 var row = $('<tr>'),
511                     currentDate = viewDate.clone().startOf('w').startOf('d');
512
513                 if (options.calendarWeeks === true) {
514                     row.append($('<th>').addClass('cw').text('#'));
515                 }
516
517                 while (currentDate.isBefore(viewDate.clone().endOf('w'))) {
518                     row.append($('<th>').addClass('dow').text(currentDate.format('dd')));
519                     currentDate.add(1, 'd');
520                 }
521                 widget.find('.datepicker-days thead').append(row);
522             },
523
524             isInDisabledDates = function (testDate) {
525                 return options.disabledDates[testDate.format('YYYY-MM-DD')] === true;
526             },
527
528             isInEnabledDates = function (testDate) {
529                 return options.enabledDates[testDate.format('YYYY-MM-DD')] === true;
530             },
531
532             isInDisabledHours = function (testDate) {
533                 return options.disabledHours[testDate.format('H')] === true;
534             },
535
536             isInEnabledHours = function (testDate) {
537                 return options.enabledHours[testDate.format('H')] === true;
538             },
539
540             isValid = function (targetMoment, granularity) {
541                 if (!targetMoment.isValid()) {
542                     return false;
543                 }
544                 if (options.disabledDates && granularity === 'd' && isInDisabledDates(targetMoment)) {
545                     return false;
546                 }
547                 if (options.enabledDates && granularity === 'd' && !isInEnabledDates(targetMoment)) {
548                     return false;
549                 }
550                 if (options.minDate && targetMoment.isBefore(options.minDate, granularity)) {
551                     return false;
552                 }
553                 if (options.maxDate && targetMoment.isAfter(options.maxDate, granularity)) {
554                     return false;
555                 }
556                 if (options.daysOfWeekDisabled && granularity === 'd' && options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) {
557                     return false;
558                 }
559                 if (options.disabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && isInDisabledHours(targetMoment)) {
560                     return false;
561                 }
562                 if (options.enabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && !isInEnabledHours(targetMoment)) {
563                     return false;
564                 }
565                 if (options.disabledTimeIntervals && (granularity === 'h' || granularity === 'm' || granularity === 's')) {
566                     var found = false;
567                     $.each(options.disabledTimeIntervals, function () {
568                         if (targetMoment.isBetween(this[0], this[1])) {
569                             found = true;
570                             return false;
571                         }
572                     });
573                     if (found) {
574                         return false;
575                     }
576                 }
577                 return true;
578             },
579
580             fillMonths = function () {
581                 var spans = [],
582                     monthsShort = viewDate.clone().startOf('y').startOf('d');
583                 while (monthsShort.isSame(viewDate, 'y')) {
584                     spans.push($('<span>').attr('data-action', 'selectMonth').addClass('month').text(monthsShort.format('MMM')));
585                     monthsShort.add(1, 'M');
586                 }
587                 widget.find('.datepicker-months td').empty().append(spans);
588             },
589
590             updateMonths = function () {
591                 var monthsView = widget.find('.datepicker-months'),
592                     monthsViewHeader = monthsView.find('th'),
593                     months = monthsView.find('tbody').find('span');
594
595                 monthsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevYear);
596                 monthsViewHeader.eq(1).attr('title', options.tooltips.selectYear);
597                 monthsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextYear);
598
599                 monthsView.find('.disabled').removeClass('disabled');
600
601                 if (!isValid(viewDate.clone().subtract(1, 'y'), 'y')) {
602                     monthsViewHeader.eq(0).addClass('disabled');
603                 }
604
605                 monthsViewHeader.eq(1).text(viewDate.year());
606
607                 if (!isValid(viewDate.clone().add(1, 'y'), 'y')) {
608                     monthsViewHeader.eq(2).addClass('disabled');
609                 }
610
611                 months.removeClass('active');
612                 if (date.isSame(viewDate, 'y') && !unset) {
613                     months.eq(date.month()).addClass('active');
614                 }
615
616                 months.each(function (index) {
617                     if (!isValid(viewDate.clone().month(index), 'M')) {
618                         $(this).addClass('disabled');
619                     }
620                 });
621             },
622
623             updateYears = function () {
624                 var yearsView = widget.find('.datepicker-years'),
625                     yearsViewHeader = yearsView.find('th'),
626                     startYear = viewDate.clone().subtract(5, 'y'),
627                     endYear = viewDate.clone().add(6, 'y'),
628                     html = '';
629
630                 yearsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevDecade);
631                 yearsViewHeader.eq(1).attr('title', options.tooltips.selectDecade);
632                 yearsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextDecade);
633
634                 yearsView.find('.disabled').removeClass('disabled');
635
636                 if (options.minDate && options.minDate.isAfter(startYear, 'y')) {
637                     yearsViewHeader.eq(0).addClass('disabled');
638                 }
639
640                 yearsViewHeader.eq(1).text(startYear.year() + '-' + endYear.year());
641
642                 if (options.maxDate && options.maxDate.isBefore(endYear, 'y')) {
643                     yearsViewHeader.eq(2).addClass('disabled');
644                 }
645
646                 while (!startYear.isAfter(endYear, 'y')) {
647                     html += '<span data-action="selectYear" class="year' + (startYear.isSame(date, 'y') && !unset ? ' active' : '') + (!isValid(startYear, 'y') ? ' disabled' : '') + '">' + startYear.year() + '</span>';
648                     startYear.add(1, 'y');
649                 }
650
651                 yearsView.find('td').html(html);
652             },
653
654             updateDecades = function () {
655                 var decadesView = widget.find('.datepicker-decades'),
656                     decadesViewHeader = decadesView.find('th'),
657                     startDecade = moment({y: viewDate.year() - (viewDate.year() % 100) - 1}),
658                     endDecade = startDecade.clone().add(100, 'y'),
659                     startedAt = startDecade.clone(),
660                     html = '';
661
662                 decadesViewHeader.eq(0).find('span').attr('title', options.tooltips.prevCentury);
663                 decadesViewHeader.eq(2).find('span').attr('title', options.tooltips.nextCentury);
664
665                 decadesView.find('.disabled').removeClass('disabled');
666
667                 if (startDecade.isSame(moment({y: 1900})) || (options.minDate && options.minDate.isAfter(startDecade, 'y'))) {
668                     decadesViewHeader.eq(0).addClass('disabled');
669                 }
670
671                 decadesViewHeader.eq(1).text(startDecade.year() + '-' + endDecade.year());
672
673                 if (startDecade.isSame(moment({y: 2000})) || (options.maxDate && options.maxDate.isBefore(endDecade, 'y'))) {
674                     decadesViewHeader.eq(2).addClass('disabled');
675                 }
676
677                 while (!startDecade.isAfter(endDecade, 'y')) {
678                     html += '<span data-action="selectDecade" class="decade' + (startDecade.isSame(date, 'y') ? ' active' : '') +
679                         (!isValid(startDecade, 'y') ? ' disabled' : '') + '" data-selection="' + (startDecade.year() + 6) + '">' + (startDecade.year() + 1) + ' - ' + (startDecade.year() + 12) + '</span>';
680                     startDecade.add(12, 'y');
681                 }
682                 html += '<span></span><span></span><span></span>'; //push the dangling block over, at least this way it's even
683
684                 decadesView.find('td').html(html);
685                 decadesViewHeader.eq(1).text((startedAt.year() + 1) + '-' + (startDecade.year()));
686             },
687
688             fillDate = function () {
689                 var daysView = widget.find('.datepicker-days'),
690                     daysViewHeader = daysView.find('th'),
691                     currentDate,
692                     html = [],
693                     row,
694                     clsName,
695                     i;
696
697                 if (!hasDate()) {
698                     return;
699                 }
700
701                 daysViewHeader.eq(0).find('span').attr('title', options.tooltips.prevMonth);
702                 daysViewHeader.eq(1).attr('title', options.tooltips.selectMonth);
703                 daysViewHeader.eq(2).find('span').attr('title', options.tooltips.nextMonth);
704
705                 daysView.find('.disabled').removeClass('disabled');
706                 daysViewHeader.eq(1).text(viewDate.format(options.dayViewHeaderFormat));
707
708                 if (!isValid(viewDate.clone().subtract(1, 'M'), 'M')) {
709                     daysViewHeader.eq(0).addClass('disabled');
710                 }
711                 if (!isValid(viewDate.clone().add(1, 'M'), 'M')) {
712                     daysViewHeader.eq(2).addClass('disabled');
713                 }
714
715                 currentDate = viewDate.clone().startOf('M').startOf('w').startOf('d');
716
717                 for (i = 0; i < 42; i++) { //always display 42 days (should show 6 weeks)
718                     if (currentDate.weekday() === 0) {
719                         row = $('<tr>');
720                         if (options.calendarWeeks) {
721                             row.append('<td class="cw">' + currentDate.week() + '</td>');
722                         }
723                         html.push(row);
724                     }
725                     clsName = '';
726                     if (currentDate.isBefore(viewDate, 'M')) {
727                         clsName += ' old';
728                     }
729                     if (currentDate.isAfter(viewDate, 'M')) {
730                         clsName += ' new';
731                     }
732                     if (currentDate.isSame(date, 'd') && !unset) {
733                         clsName += ' active';
734                     }
735                     if (!isValid(currentDate, 'd')) {
736                         clsName += ' disabled';
737                     }
738                     if (currentDate.isSame(getMoment(), 'd')) {
739                         clsName += ' today';
740                     }
741                     if (currentDate.day() === 0 || currentDate.day() === 6) {
742                         clsName += ' weekend';
743                     }
744                     row.append('<td data-action="selectDay" data-day="' + currentDate.format('L') + '" class="day' + clsName + '">' + currentDate.date() + '</td>');
745                     currentDate.add(1, 'd');
746                 }
747
748                 daysView.find('tbody').empty().append(html);
749
750                 updateMonths();
751
752                 updateYears();
753
754                 updateDecades();
755             },
756
757             fillHours = function () {
758                 var table = widget.find('.timepicker-hours table'),
759                     currentHour = viewDate.clone().startOf('d'),
760                     html = [],
761                     row = $('<tr>');
762
763                 if (viewDate.hour() > 11 && !use24Hours) {
764                     currentHour.hour(12);
765                 }
766                 while (currentHour.isSame(viewDate, 'd') && (use24Hours || (viewDate.hour() < 12 && currentHour.hour() < 12) || viewDate.hour() > 11)) {
767                     if (currentHour.hour() % 4 === 0) {
768                         row = $('<tr>');
769                         html.push(row);
770                     }
771                     row.append('<td data-action="selectHour" class="hour' + (!isValid(currentHour, 'h') ? ' disabled' : '') + '">' + currentHour.format(use24Hours ? 'HH' : 'hh') + '</td>');
772                     currentHour.add(1, 'h');
773                 }
774                 table.empty().append(html);
775             },
776
777             fillMinutes = function () {
778                 var table = widget.find('.timepicker-minutes table'),
779                     currentMinute = viewDate.clone().startOf('h'),
780                     html = [],
781                     row = $('<tr>'),
782                     step = options.stepping === 1 ? 5 : options.stepping;
783
784                 while (viewDate.isSame(currentMinute, 'h')) {
785                     if (currentMinute.minute() % (step * 4) === 0) {
786                         row = $('<tr>');
787                         html.push(row);
788                     }
789                     row.append('<td data-action="selectMinute" class="minute' + (!isValid(currentMinute, 'm') ? ' disabled' : '') + '">' + currentMinute.format('mm') + '</td>');
790                     currentMinute.add(step, 'm');
791                 }
792                 table.empty().append(html);
793             },
794
795             fillSeconds = function () {
796                 var table = widget.find('.timepicker-seconds table'),
797                     currentSecond = viewDate.clone().startOf('m'),
798                     html = [],
799                     row = $('<tr>');
800
801                 while (viewDate.isSame(currentSecond, 'm')) {
802                     if (currentSecond.second() % 20 === 0) {
803                         row = $('<tr>');
804                         html.push(row);
805                     }
806                     row.append('<td data-action="selectSecond" class="second' + (!isValid(currentSecond, 's') ? ' disabled' : '') + '">' + currentSecond.format('ss') + '</td>');
807                     currentSecond.add(5, 's');
808                 }
809
810                 table.empty().append(html);
811             },
812
813             fillTime = function () {
814                 var toggle, newDate, timeComponents = widget.find('.timepicker span[data-time-component]');
815
816                 if (!use24Hours) {
817                     toggle = widget.find('.timepicker [data-action=togglePeriod]');
818                     newDate = date.clone().add((date.hours() >= 12) ? -12 : 12, 'h');
819
820                     toggle.text(date.format('A'));
821
822                     if (isValid(newDate, 'h')) {
823                         toggle.removeClass('disabled');
824                     } else {
825                         toggle.addClass('disabled');
826                     }
827                 }
828                 timeComponents.filter('[data-time-component=hours]').text(date.format(use24Hours ? 'HH' : 'hh'));
829                 timeComponents.filter('[data-time-component=minutes]').text(date.format('mm'));
830                 timeComponents.filter('[data-time-component=seconds]').text(date.format('ss'));
831
832                 fillHours();
833                 fillMinutes();
834                 fillSeconds();
835             },
836
837             update = function () {
838                 if (!widget) {
839                     return;
840                 }
841                 fillDate();
842                 fillTime();
843             },
844
845             setValue = function (targetMoment) {
846                 var oldDate = unset ? null : date;
847
848                 // case of calling setValue(null or false)
849                 if (!targetMoment) {
850                     unset = true;
851                     input.val('');
852                     element.data('date', '');
853                     notifyEvent({
854                         type: 'dp.change',
855                         date: false,
856                         oldDate: oldDate
857                     });
858                     update();
859                     return;
860                 }
861
862                 targetMoment = targetMoment.clone().locale(options.locale);
863
864                 if (options.stepping !== 1) {
865                     targetMoment.minutes((Math.round(targetMoment.minutes() / options.stepping) * options.stepping) % 60).seconds(0);
866                 }
867
868                 if (isValid(targetMoment)) {
869                     date = targetMoment;
870                     viewDate = date.clone();
871                     input.val(date.format(actualFormat));
872                     element.data('date', date.format(actualFormat));
873                     unset = false;
874                     update();
875                     notifyEvent({
876                         type: 'dp.change',
877                         date: date.clone(),
878                         oldDate: oldDate
879                     });
880                 } else {
881                     if (!options.keepInvalid) {
882                         input.val(unset ? '' : date.format(actualFormat));
883                     }
884                     notifyEvent({
885                         type: 'dp.error',
886                         date: targetMoment
887                     });
888                 }
889             },
890
891             hide = function () {
892                 ///<summary>Hides the widget. Possibly will emit dp.hide</summary>
893                 var transitioning = false;
894                 if (!widget) {
895                     return picker;
896                 }
897                 // Ignore event if in the middle of a picker transition
898                 widget.find('.collapse').each(function () {
899                     var collapseData = $(this).data('collapse');
900                     if (collapseData && collapseData.transitioning) {
901                         transitioning = true;
902                         return false;
903                     }
904                     return true;
905                 });
906                 if (transitioning) {
907                     return picker;
908                 }
909                 if (component && component.hasClass('btn')) {
910                     component.toggleClass('active');
911                 }
912                 widget.hide();
913
914                 $(window).off('resize', place);
915                 widget.off('click', '[data-action]');
916                 widget.off('mousedown', false);
917
918                 widget.remove();
919                 widget = false;
920
921                 notifyEvent({
922                     type: 'dp.hide',
923                     date: date.clone()
924                 });
925
926                 input.blur();
927
928                 return picker;
929             },
930
931             clear = function () {
932                 setValue(null);
933             },
934
935             /********************************************************************************
936              *
937              * Widget UI interaction functions
938              *
939              ********************************************************************************/
940             actions = {
941                 next: function () {
942                     var navFnc = datePickerModes[currentViewMode].navFnc;
943                     viewDate.add(datePickerModes[currentViewMode].navStep, navFnc);
944                     fillDate();
945                     viewUpdate(navFnc);
946                 },
947
948                 previous: function () {
949                     var navFnc = datePickerModes[currentViewMode].navFnc;
950                     viewDate.subtract(datePickerModes[currentViewMode].navStep, navFnc);
951                     fillDate();
952                     viewUpdate(navFnc);
953                 },
954
955                 pickerSwitch: function () {
956                     showMode(1);
957                 },
958
959                 selectMonth: function (e) {
960                     var month = $(e.target).closest('tbody').find('span').index($(e.target));
961                     viewDate.month(month);
962                     if (currentViewMode === minViewModeNumber) {
963                         setValue(date.clone().year(viewDate.year()).month(viewDate.month()));
964                         if (!options.inline) {
965                             hide();
966                         }
967                     } else {
968                         showMode(-1);
969                         fillDate();
970                     }
971                     viewUpdate('M');
972                 },
973
974                 selectYear: function (e) {
975                     var year = parseInt($(e.target).text(), 10) || 0;
976                     viewDate.year(year);
977                     if (currentViewMode === minViewModeNumber) {
978                         setValue(date.clone().year(viewDate.year()));
979                         if (!options.inline) {
980                             hide();
981                         }
982                     } else {
983                         showMode(-1);
984                         fillDate();
985                     }
986                     viewUpdate('YYYY');
987                 },
988
989                 selectDecade: function (e) {
990                     var year = parseInt($(e.target).data('selection'), 10) || 0;
991                     viewDate.year(year);
992                     if (currentViewMode === minViewModeNumber) {
993                         setValue(date.clone().year(viewDate.year()));
994                         if (!options.inline) {
995                             hide();
996                         }
997                     } else {
998                         showMode(-1);
999                         fillDate();
1000                     }
1001                     viewUpdate('YYYY');
1002                 },
1003
1004                 selectDay: function (e) {
1005                     var day = viewDate.clone();
1006                     if ($(e.target).is('.old')) {
1007                         day.subtract(1, 'M');
1008                     }
1009                     if ($(e.target).is('.new')) {
1010                         day.add(1, 'M');
1011                     }
1012                     setValue(day.date(parseInt($(e.target).text(), 10)));
1013                     if (!hasTime() && !options.keepOpen && !options.inline) {
1014                         hide();
1015                     }
1016                 },
1017
1018                 incrementHours: function () {
1019                     var newDate = date.clone().add(1, 'h');
1020                     if (isValid(newDate, 'h')) {
1021                         setValue(newDate);
1022                     }
1023                 },
1024
1025                 incrementMinutes: function () {
1026                     var newDate = date.clone().add(options.stepping, 'm');
1027                     if (isValid(newDate, 'm')) {
1028                         setValue(newDate);
1029                     }
1030                 },
1031
1032                 incrementSeconds: function () {
1033                     var newDate = date.clone().add(1, 's');
1034                     if (isValid(newDate, 's')) {
1035                         setValue(newDate);
1036                     }
1037                 },
1038
1039                 decrementHours: function () {
1040                     var newDate = date.clone().subtract(1, 'h');
1041                     if (isValid(newDate, 'h')) {
1042                         setValue(newDate);
1043                     }
1044                 },
1045
1046                 decrementMinutes: function () {
1047                     var newDate = date.clone().subtract(options.stepping, 'm');
1048                     if (isValid(newDate, 'm')) {
1049                         setValue(newDate);
1050                     }
1051                 },
1052
1053                 decrementSeconds: function () {
1054                     var newDate = date.clone().subtract(1, 's');
1055                     if (isValid(newDate, 's')) {
1056                         setValue(newDate);
1057                     }
1058                 },
1059
1060                 togglePeriod: function () {
1061                     setValue(date.clone().add((date.hours() >= 12) ? -12 : 12, 'h'));
1062                 },
1063
1064                 togglePicker: function (e) {
1065                     var $this = $(e.target),
1066                         $parent = $this.closest('ul'),
1067                         expanded = $parent.find('.in'),
1068                         closed = $parent.find('.collapse:not(.in)'),
1069                         collapseData;
1070
1071                     if (expanded && expanded.length) {
1072                         collapseData = expanded.data('collapse');
1073                         if (collapseData && collapseData.transitioning) {
1074                             return;
1075                         }
1076                         if (expanded.collapse) { // if collapse plugin is available through bootstrap.js then use it
1077                             expanded.collapse('hide');
1078                             closed.collapse('show');
1079                         } else { // otherwise just toggle in class on the two views
1080                             expanded.removeClass('in');
1081                             closed.addClass('in');
1082                         }
1083                         if ($this.is('span')) {
1084                             $this.toggleClass(options.icons.time + ' ' + options.icons.date);
1085                         } else {
1086                             $this.find('span').toggleClass(options.icons.time + ' ' + options.icons.date);
1087                         }
1088
1089                         // NOTE: uncomment if toggled state will be restored in show()
1090                         //if (component) {
1091                         //    component.find('span').toggleClass(options.icons.time + ' ' + options.icons.date);
1092                         //}
1093                     }
1094                 },
1095
1096                 showPicker: function () {
1097                     widget.find('.timepicker > div:not(.timepicker-picker)').hide();
1098                     widget.find('.timepicker .timepicker-picker').show();
1099                 },
1100
1101                 showHours: function () {
1102                     widget.find('.timepicker .timepicker-picker').hide();
1103                     widget.find('.timepicker .timepicker-hours').show();
1104                 },
1105
1106                 showMinutes: function () {
1107                     widget.find('.timepicker .timepicker-picker').hide();
1108                     widget.find('.timepicker .timepicker-minutes').show();
1109                 },
1110
1111                 showSeconds: function () {
1112                     widget.find('.timepicker .timepicker-picker').hide();
1113                     widget.find('.timepicker .timepicker-seconds').show();
1114                 },
1115
1116                 selectHour: function (e) {
1117                     var hour = parseInt($(e.target).text(), 10);
1118
1119                     if (!use24Hours) {
1120                         if (date.hours() >= 12) {
1121                             if (hour !== 12) {
1122                                 hour += 12;
1123                             }
1124                         } else {
1125                             if (hour === 12) {
1126                                 hour = 0;
1127                             }
1128                         }
1129                     }
1130                     setValue(date.clone().hours(hour));
1131                     actions.showPicker.call(picker);
1132                 },
1133
1134                 selectMinute: function (e) {
1135                     setValue(date.clone().minutes(parseInt($(e.target).text(), 10)));
1136                     actions.showPicker.call(picker);
1137                 },
1138
1139                 selectSecond: function (e) {
1140                     setValue(date.clone().seconds(parseInt($(e.target).text(), 10)));
1141                     actions.showPicker.call(picker);
1142                 },
1143
1144                 clear: clear,
1145
1146                 today: function () {
1147                     var todaysDate = getMoment();
1148                     if (isValid(todaysDate, 'd')) {
1149                         setValue(todaysDate);
1150                     }
1151                 },
1152
1153                 close: hide
1154             },
1155
1156             doAction = function (e) {
1157                 if ($(e.currentTarget).is('.disabled')) {
1158                     return false;
1159                 }
1160                 actions[$(e.currentTarget).data('action')].apply(picker, arguments);
1161                 return false;
1162             },
1163
1164             show = function () {
1165                 ///<summary>Shows the widget. Possibly will emit dp.show and dp.change</summary>
1166                 var currentMoment,
1167                     useCurrentGranularity = {
1168                         'year': function (m) {
1169                             return m.month(0).date(1).hours(0).seconds(0).minutes(0);
1170                         },
1171                         'month': function (m) {
1172                             return m.date(1).hours(0).seconds(0).minutes(0);
1173                         },
1174                         'day': function (m) {
1175                             return m.hours(0).seconds(0).minutes(0);
1176                         },
1177                         'hour': function (m) {
1178                             return m.seconds(0).minutes(0);
1179                         },
1180                         'minute': function (m) {
1181                             return m.seconds(0);
1182                         }
1183                     };
1184
1185                 if (input.prop('disabled') || (!options.ignoreReadonly && input.prop('readonly')) || widget) {
1186                     return picker;
1187                 }
1188                 if (input.val() !== undefined && input.val().trim().length !== 0) {
1189                     setValue(parseInputDate(input.val().trim()));
1190                 } else if (options.useCurrent && unset && ((input.is('input') && input.val().trim().length === 0) || options.inline)) {
1191                     currentMoment = getMoment();
1192                     if (typeof options.useCurrent === 'string') {
1193                         currentMoment = useCurrentGranularity[options.useCurrent](currentMoment);
1194                     }
1195                     setValue(currentMoment);
1196                 }
1197
1198                 widget = getTemplate();
1199
1200                 fillDow();
1201                 fillMonths();
1202
1203                 widget.find('.timepicker-hours').hide();
1204                 widget.find('.timepicker-minutes').hide();
1205                 widget.find('.timepicker-seconds').hide();
1206
1207                 update();
1208                 showMode();
1209
1210                 $(window).on('resize', place);
1211                 widget.on('click', '[data-action]', doAction); // this handles clicks on the widget
1212                 widget.on('mousedown', false);
1213
1214                 if (component && component.hasClass('btn')) {
1215                     component.toggleClass('active');
1216                 }
1217                 widget.show();
1218                 place();
1219
1220                 if (options.focusOnShow && !input.is(':focus')) {
1221                     input.focus();
1222                 }
1223
1224                 notifyEvent({
1225                     type: 'dp.show'
1226                 });
1227                 return picker;
1228             },
1229
1230             toggle = function () {
1231                 /// <summary>Shows or hides the widget</summary>
1232                 return (widget ? hide() : show());
1233             },
1234
1235             parseInputDate = function (inputDate) {
1236                 if (options.parseInputDate === undefined) {
1237                     if (moment.isMoment(inputDate) || inputDate instanceof Date) {
1238                         inputDate = moment(inputDate);
1239                     } else {
1240                         inputDate = getMoment(inputDate);
1241                     }
1242                 } else {
1243                     inputDate = options.parseInputDate(inputDate);
1244                 }
1245                 inputDate.locale(options.locale);
1246                 return inputDate;
1247             },
1248
1249             keydown = function (e) {
1250                 var handler = null,
1251                     index,
1252                     index2,
1253                     pressedKeys = [],
1254                     pressedModifiers = {},
1255                     currentKey = e.which,
1256                     keyBindKeys,
1257                     allModifiersPressed,
1258                     pressed = 'p';
1259
1260                 keyState[currentKey] = pressed;
1261
1262                 for (index in keyState) {
1263                     if (keyState.hasOwnProperty(index) && keyState[index] === pressed) {
1264                         pressedKeys.push(index);
1265                         if (parseInt(index, 10) !== currentKey) {
1266                             pressedModifiers[index] = true;
1267                         }
1268                     }
1269                 }
1270
1271                 for (index in options.keyBinds) {
1272                     if (options.keyBinds.hasOwnProperty(index) && typeof (options.keyBinds[index]) === 'function') {
1273                         keyBindKeys = index.split(' ');
1274                         if (keyBindKeys.length === pressedKeys.length && keyMap[currentKey] === keyBindKeys[keyBindKeys.length - 1]) {
1275                             allModifiersPressed = true;
1276                             for (index2 = keyBindKeys.length - 2; index2 >= 0; index2--) {
1277                                 if (!(keyMap[keyBindKeys[index2]] in pressedModifiers)) {
1278                                     allModifiersPressed = false;
1279                                     break;
1280                                 }
1281                             }
1282                             if (allModifiersPressed) {
1283                                 handler = options.keyBinds[index];
1284                                 break;
1285                             }
1286                         }
1287                     }
1288                 }
1289
1290                 if (handler) {
1291                     handler.call(picker, widget);
1292                     e.stopPropagation();
1293                     e.preventDefault();
1294                 }
1295             },
1296
1297             keyup = function (e) {
1298                 keyState[e.which] = 'r';
1299                 e.stopPropagation();
1300                 e.preventDefault();
1301             },
1302
1303             change = function (e) {
1304                 var val = $(e.target).val().trim(),
1305                     parsedDate = val ? parseInputDate(val) : null;
1306                 setValue(parsedDate);
1307                 e.stopImmediatePropagation();
1308                 return false;
1309             },
1310
1311             attachDatePickerElementEvents = function () {
1312                 input.on({
1313                     'change': change,
1314                     'blur': options.debug ? '' : hide,
1315                     'keydown': keydown,
1316                     'keyup': keyup,
1317                     'focus': options.allowInputToggle ? show : ''
1318                 });
1319
1320                 if (element.is('input')) {
1321                     input.on({
1322                         'focus': show
1323                     });
1324                 } else if (component) {
1325                     component.on('click', toggle);
1326                     component.on('mousedown', false);
1327                 }
1328             },
1329
1330             detachDatePickerElementEvents = function () {
1331                 input.off({
1332                     'change': change,
1333                     'blur': blur,
1334                     'keydown': keydown,
1335                     'keyup': keyup,
1336                     'focus': options.allowInputToggle ? hide : ''
1337                 });
1338
1339                 if (element.is('input')) {
1340                     input.off({
1341                         'focus': show
1342                     });
1343                 } else if (component) {
1344                     component.off('click', toggle);
1345                     component.off('mousedown', false);
1346                 }
1347             },
1348
1349             indexGivenDates = function (givenDatesArray) {
1350                 // Store given enabledDates and disabledDates as keys.
1351                 // This way we can check their existence in O(1) time instead of looping through whole array.
1352                 // (for example: options.enabledDates['2014-02-27'] === true)
1353                 var givenDatesIndexed = {};
1354                 $.each(givenDatesArray, function () {
1355                     var dDate = parseInputDate(this);
1356                     if (dDate.isValid()) {
1357                         givenDatesIndexed[dDate.format('YYYY-MM-DD')] = true;
1358                     }
1359                 });
1360                 return (Object.keys(givenDatesIndexed).length) ? givenDatesIndexed : false;
1361             },
1362
1363             indexGivenHours = function (givenHoursArray) {
1364                 // Store given enabledHours and disabledHours as keys.
1365                 // This way we can check their existence in O(1) time instead of looping through whole array.
1366                 // (for example: options.enabledHours['2014-02-27'] === true)
1367                 var givenHoursIndexed = {};
1368                 $.each(givenHoursArray, function () {
1369                     givenHoursIndexed[this] = true;
1370                 });
1371                 return (Object.keys(givenHoursIndexed).length) ? givenHoursIndexed : false;
1372             },
1373
1374             initFormatting = function () {
1375                 var format = options.format || 'L LT';
1376
1377                 actualFormat = format.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput) {
1378                     var newinput = date.localeData().longDateFormat(formatInput) || formatInput;
1379                     return newinput.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput2) { //temp fix for #740
1380                         return date.localeData().longDateFormat(formatInput2) || formatInput2;
1381                     });
1382                 });
1383
1384
1385                 parseFormats = options.extraFormats ? options.extraFormats.slice() : [];
1386                 if (parseFormats.indexOf(format) < 0 && parseFormats.indexOf(actualFormat) < 0) {
1387                     parseFormats.push(actualFormat);
1388                 }
1389
1390                 use24Hours = (actualFormat.toLowerCase().indexOf('a') < 1 && actualFormat.replace(/\[.*?\]/g, '').indexOf('h') < 1);
1391
1392                 if (isEnabled('y')) {
1393                     minViewModeNumber = 2;
1394                 }
1395                 if (isEnabled('M')) {
1396                     minViewModeNumber = 1;
1397                 }
1398                 if (isEnabled('d')) {
1399                     minViewModeNumber = 0;
1400                 }
1401
1402                 currentViewMode = Math.max(minViewModeNumber, currentViewMode);
1403
1404                 if (!unset) {
1405                     setValue(date);
1406                 }
1407             };
1408
1409         /********************************************************************************
1410          *
1411          * Public API functions
1412          * =====================
1413          *
1414          * Important: Do not expose direct references to private objects or the options
1415          * object to the outer world. Always return a clone when returning values or make
1416          * a clone when setting a private variable.
1417          *
1418          ********************************************************************************/
1419         picker.destroy = function () {
1420             ///<summary>Destroys the widget and removes all attached event listeners</summary>
1421             hide();
1422             detachDatePickerElementEvents();
1423             element.removeData('DateTimePicker');
1424             element.removeData('date');
1425         };
1426
1427         picker.toggle = toggle;
1428
1429         picker.show = show;
1430
1431         picker.hide = hide;
1432
1433         picker.disable = function () {
1434             ///<summary>Disables the input element, the component is attached to, by adding a disabled="true" attribute to it.
1435             ///If the widget was visible before that call it is hidden. Possibly emits dp.hide</summary>
1436             hide();
1437             if (component && component.hasClass('btn')) {
1438                 component.addClass('disabled');
1439             }
1440             input.prop('disabled', true);
1441             return picker;
1442         };
1443
1444         picker.enable = function () {
1445             ///<summary>Enables the input element, the component is attached to, by removing disabled attribute from it.</summary>
1446             if (component && component.hasClass('btn')) {
1447                 component.removeClass('disabled');
1448             }
1449             input.prop('disabled', false);
1450             return picker;
1451         };
1452
1453         picker.ignoreReadonly = function (ignoreReadonly) {
1454             if (arguments.length === 0) {
1455                 return options.ignoreReadonly;
1456             }
1457             if (typeof ignoreReadonly !== 'boolean') {
1458                 throw new TypeError('ignoreReadonly () expects a boolean parameter');
1459             }
1460             options.ignoreReadonly = ignoreReadonly;
1461             return picker;
1462         };
1463
1464         picker.options = function (newOptions) {
1465             if (arguments.length === 0) {
1466                 return $.extend(true, {}, options);
1467             }
1468
1469             if (!(newOptions instanceof Object)) {
1470                 throw new TypeError('options() options parameter should be an object');
1471             }
1472             $.extend(true, options, newOptions);
1473             $.each(options, function (key, value) {
1474                 if (picker[key] !== undefined) {
1475                     picker[key](value);
1476                 } else {
1477                     throw new TypeError('option ' + key + ' is not recognized!');
1478                 }
1479             });
1480             return picker;
1481         };
1482
1483         picker.date = function (newDate) {
1484             ///<signature helpKeyword="$.fn.datetimepicker.date">
1485             ///<summary>Returns the component's model current date, a moment object or null if not set.</summary>
1486             ///<returns type="Moment">date.clone()</returns>
1487             ///</signature>
1488             ///<signature>
1489             ///<summary>Sets the components model current moment to it. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration.</summary>
1490             ///<param name="newDate" locid="$.fn.datetimepicker.date_p:newDate">Takes string, Date, moment, null parameter.</param>
1491             ///</signature>
1492             if (arguments.length === 0) {
1493                 if (unset) {
1494                     return null;
1495                 }
1496                 return date.clone();
1497             }
1498
1499             if (newDate !== null && typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) {
1500                 throw new TypeError('date() parameter must be one of [null, string, moment or Date]');
1501             }
1502
1503             setValue(newDate === null ? null : parseInputDate(newDate));
1504             return picker;
1505         };
1506
1507         picker.format = function (newFormat) {
1508             ///<summary>test su</summary>
1509             ///<param name="newFormat">info about para</param>
1510             ///<returns type="string|boolean">returns foo</returns>
1511             if (arguments.length === 0) {
1512                 return options.format;
1513             }
1514
1515             if ((typeof newFormat !== 'string') && ((typeof newFormat !== 'boolean') || (newFormat !== false))) {
1516                 throw new TypeError('format() expects a sting or boolean:false parameter ' + newFormat);
1517             }
1518
1519             options.format = newFormat;
1520             if (actualFormat) {
1521                 initFormatting(); // reinit formatting
1522             }
1523             return picker;
1524         };
1525
1526         picker.timeZone = function (newZone) {
1527             if (arguments.length === 0) {
1528                 return options.timeZone;
1529             }
1530
1531             options.timeZone = newZone;
1532
1533             return picker;
1534         };
1535
1536         picker.dayViewHeaderFormat = function (newFormat) {
1537             if (arguments.length === 0) {
1538                 return options.dayViewHeaderFormat;
1539             }
1540
1541             if (typeof newFormat !== 'string') {
1542                 throw new TypeError('dayViewHeaderFormat() expects a string parameter');
1543             }
1544
1545             options.dayViewHeaderFormat = newFormat;
1546             return picker;
1547         };
1548
1549         picker.extraFormats = function (formats) {
1550             if (arguments.length === 0) {
1551                 return options.extraFormats;
1552             }
1553
1554             if (formats !== false && !(formats instanceof Array)) {
1555                 throw new TypeError('extraFormats() expects an array or false parameter');
1556             }
1557
1558             options.extraFormats = formats;
1559             if (parseFormats) {
1560                 initFormatting(); // reinit formatting
1561             }
1562             return picker;
1563         };
1564
1565         picker.disabledDates = function (dates) {
1566             ///<signature helpKeyword="$.fn.datetimepicker.disabledDates">
1567             ///<summary>Returns an array with the currently set disabled dates on the component.</summary>
1568             ///<returns type="array">options.disabledDates</returns>
1569             ///</signature>
1570             ///<signature>
1571             ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
1572             ///options.enabledDates if such exist.</summary>
1573             ///<param name="dates" locid="$.fn.datetimepicker.disabledDates_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
1574             ///</signature>
1575             if (arguments.length === 0) {
1576                 return (options.disabledDates ? $.extend({}, options.disabledDates) : options.disabledDates);
1577             }
1578
1579             if (!dates) {
1580                 options.disabledDates = false;
1581                 update();
1582                 return picker;
1583             }
1584             if (!(dates instanceof Array)) {
1585                 throw new TypeError('disabledDates() expects an array parameter');
1586             }
1587             options.disabledDates = indexGivenDates(dates);
1588             options.enabledDates = false;
1589             update();
1590             return picker;
1591         };
1592
1593         picker.enabledDates = function (dates) {
1594             ///<signature helpKeyword="$.fn.datetimepicker.enabledDates">
1595             ///<summary>Returns an array with the currently set enabled dates on the component.</summary>
1596             ///<returns type="array">options.enabledDates</returns>
1597             ///</signature>
1598             ///<signature>
1599             ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledDates if such exist.</summary>
1600             ///<param name="dates" locid="$.fn.datetimepicker.enabledDates_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
1601             ///</signature>
1602             if (arguments.length === 0) {
1603                 return (options.enabledDates ? $.extend({}, options.enabledDates) : options.enabledDates);
1604             }
1605
1606             if (!dates) {
1607                 options.enabledDates = false;
1608                 update();
1609                 return picker;
1610             }
1611             if (!(dates instanceof Array)) {
1612                 throw new TypeError('enabledDates() expects an array parameter');
1613             }
1614             options.enabledDates = indexGivenDates(dates);
1615             options.disabledDates = false;
1616             update();
1617             return picker;
1618         };
1619
1620         picker.daysOfWeekDisabled = function (daysOfWeekDisabled) {
1621             if (arguments.length === 0) {
1622                 return options.daysOfWeekDisabled.splice(0);
1623             }
1624
1625             if ((typeof daysOfWeekDisabled === 'boolean') && !daysOfWeekDisabled) {
1626                 options.daysOfWeekDisabled = false;
1627                 update();
1628                 return picker;
1629             }
1630
1631             if (!(daysOfWeekDisabled instanceof Array)) {
1632                 throw new TypeError('daysOfWeekDisabled() expects an array parameter');
1633             }
1634             options.daysOfWeekDisabled = daysOfWeekDisabled.reduce(function (previousValue, currentValue) {
1635                 currentValue = parseInt(currentValue, 10);
1636                 if (currentValue > 6 || currentValue < 0 || isNaN(currentValue)) {
1637                     return previousValue;
1638                 }
1639                 if (previousValue.indexOf(currentValue) === -1) {
1640                     previousValue.push(currentValue);
1641                 }
1642                 return previousValue;
1643             }, []).sort();
1644             if (options.useCurrent && !options.keepInvalid) {
1645                 var tries = 0;
1646                 while (!isValid(date, 'd')) {
1647                     date.add(1, 'd');
1648                     if (tries === 7) {
1649                         throw 'Tried 7 times to find a valid date';
1650                     }
1651                     tries++;
1652                 }
1653                 setValue(date);
1654             }
1655             update();
1656             return picker;
1657         };
1658
1659         picker.maxDate = function (maxDate) {
1660             if (arguments.length === 0) {
1661                 return options.maxDate ? options.maxDate.clone() : options.maxDate;
1662             }
1663
1664             if ((typeof maxDate === 'boolean') && maxDate === false) {
1665                 options.maxDate = false;
1666                 update();
1667                 return picker;
1668             }
1669
1670             if (typeof maxDate === 'string') {
1671                 if (maxDate === 'now' || maxDate === 'moment') {
1672                     maxDate = getMoment();
1673                 }
1674             }
1675
1676             var parsedDate = parseInputDate(maxDate);
1677
1678             if (!parsedDate.isValid()) {
1679                 throw new TypeError('maxDate() Could not parse date parameter: ' + maxDate);
1680             }
1681             if (options.minDate && parsedDate.isBefore(options.minDate)) {
1682                 throw new TypeError('maxDate() date parameter is before options.minDate: ' + parsedDate.format(actualFormat));
1683             }
1684             options.maxDate = parsedDate;
1685             if (options.useCurrent && !options.keepInvalid && date.isAfter(maxDate)) {
1686                 setValue(options.maxDate);
1687             }
1688             if (viewDate.isAfter(parsedDate)) {
1689                 viewDate = parsedDate.clone().subtract(options.stepping, 'm');
1690             }
1691             update();
1692             return picker;
1693         };
1694
1695         picker.minDate = function (minDate) {
1696             if (arguments.length === 0) {
1697                 return options.minDate ? options.minDate.clone() : options.minDate;
1698             }
1699
1700             if ((typeof minDate === 'boolean') && minDate === false) {
1701                 options.minDate = false;
1702                 update();
1703                 return picker;
1704             }
1705
1706             if (typeof minDate === 'string') {
1707                 if (minDate === 'now' || minDate === 'moment') {
1708                     minDate = getMoment();
1709                 }
1710             }
1711
1712             var parsedDate = parseInputDate(minDate);
1713
1714             if (!parsedDate.isValid()) {
1715                 throw new TypeError('minDate() Could not parse date parameter: ' + minDate);
1716             }
1717             if (options.maxDate && parsedDate.isAfter(options.maxDate)) {
1718                 throw new TypeError('minDate() date parameter is after options.maxDate: ' + parsedDate.format(actualFormat));
1719             }
1720             options.minDate = parsedDate;
1721             if (options.useCurrent && !options.keepInvalid && date.isBefore(minDate)) {
1722                 setValue(options.minDate);
1723             }
1724             if (viewDate.isBefore(parsedDate)) {
1725                 viewDate = parsedDate.clone().add(options.stepping, 'm');
1726             }
1727             update();
1728             return picker;
1729         };
1730
1731         picker.defaultDate = function (defaultDate) {
1732             ///<signature helpKeyword="$.fn.datetimepicker.defaultDate">
1733             ///<summary>Returns a moment with the options.defaultDate option configuration or false if not set</summary>
1734             ///<returns type="Moment">date.clone()</returns>
1735             ///</signature>
1736             ///<signature>
1737             ///<summary>Will set the picker's inital date. If a boolean:false value is passed the options.defaultDate parameter is cleared.</summary>
1738             ///<param name="defaultDate" locid="$.fn.datetimepicker.defaultDate_p:defaultDate">Takes a string, Date, moment, boolean:false</param>
1739             ///</signature>
1740             if (arguments.length === 0) {
1741                 return options.defaultDate ? options.defaultDate.clone() : options.defaultDate;
1742             }
1743             if (!defaultDate) {
1744                 options.defaultDate = false;
1745                 return picker;
1746             }
1747
1748             if (typeof defaultDate === 'string') {
1749                 if (defaultDate === 'now' || defaultDate === 'moment') {
1750                     defaultDate = getMoment();
1751                 }
1752             }
1753
1754             var parsedDate = parseInputDate(defaultDate);
1755             if (!parsedDate.isValid()) {
1756                 throw new TypeError('defaultDate() Could not parse date parameter: ' + defaultDate);
1757             }
1758             if (!isValid(parsedDate)) {
1759                 throw new TypeError('defaultDate() date passed is invalid according to component setup validations');
1760             }
1761
1762             options.defaultDate = parsedDate;
1763
1764             if ((options.defaultDate && options.inline) || input.val().trim() === '') {
1765                 setValue(options.defaultDate);
1766             }
1767             return picker;
1768         };
1769
1770         picker.locale = function (locale) {
1771             if (arguments.length === 0) {
1772                 return options.locale;
1773             }
1774
1775             if (!moment.localeData(locale)) {
1776                 throw new TypeError('locale() locale ' + locale + ' is not loaded from moment locales!');
1777             }
1778
1779             options.locale = locale;
1780             date.locale(options.locale);
1781             viewDate.locale(options.locale);
1782
1783             if (actualFormat) {
1784                 initFormatting(); // reinit formatting
1785             }
1786             if (widget) {
1787                 hide();
1788                 show();
1789             }
1790             return picker;
1791         };
1792
1793         picker.stepping = function (stepping) {
1794             if (arguments.length === 0) {
1795                 return options.stepping;
1796             }
1797
1798             stepping = parseInt(stepping, 10);
1799             if (isNaN(stepping) || stepping < 1) {
1800                 stepping = 1;
1801             }
1802             options.stepping = stepping;
1803             return picker;
1804         };
1805
1806         picker.useCurrent = function (useCurrent) {
1807             var useCurrentOptions = ['year', 'month', 'day', 'hour', 'minute'];
1808             if (arguments.length === 0) {
1809                 return options.useCurrent;
1810             }
1811
1812             if ((typeof useCurrent !== 'boolean') && (typeof useCurrent !== 'string')) {
1813                 throw new TypeError('useCurrent() expects a boolean or string parameter');
1814             }
1815             if (typeof useCurrent === 'string' && useCurrentOptions.indexOf(useCurrent.toLowerCase()) === -1) {
1816                 throw new TypeError('useCurrent() expects a string parameter of ' + useCurrentOptions.join(', '));
1817             }
1818             options.useCurrent = useCurrent;
1819             return picker;
1820         };
1821
1822         picker.collapse = function (collapse) {
1823             if (arguments.length === 0) {
1824                 return options.collapse;
1825             }
1826
1827             if (typeof collapse !== 'boolean') {
1828                 throw new TypeError('collapse() expects a boolean parameter');
1829             }
1830             if (options.collapse === collapse) {
1831                 return picker;
1832             }
1833             options.collapse = collapse;
1834             if (widget) {
1835                 hide();
1836                 show();
1837             }
1838             return picker;
1839         };
1840
1841         picker.icons = function (icons) {
1842             if (arguments.length === 0) {
1843                 return $.extend({}, options.icons);
1844             }
1845
1846             if (!(icons instanceof Object)) {
1847                 throw new TypeError('icons() expects parameter to be an Object');
1848             }
1849             $.extend(options.icons, icons);
1850             if (widget) {
1851                 hide();
1852                 show();
1853             }
1854             return picker;
1855         };
1856
1857         picker.tooltips = function (tooltips) {
1858             if (arguments.length === 0) {
1859                 return $.extend({}, options.tooltips);
1860             }
1861
1862             if (!(tooltips instanceof Object)) {
1863                 throw new TypeError('tooltips() expects parameter to be an Object');
1864             }
1865             $.extend(options.tooltips, tooltips);
1866             if (widget) {
1867                 hide();
1868                 show();
1869             }
1870             return picker;
1871         };
1872
1873         picker.useStrict = function (useStrict) {
1874             if (arguments.length === 0) {
1875                 return options.useStrict;
1876             }
1877
1878             if (typeof useStrict !== 'boolean') {
1879                 throw new TypeError('useStrict() expects a boolean parameter');
1880             }
1881             options.useStrict = useStrict;
1882             return picker;
1883         };
1884
1885         picker.sideBySide = function (sideBySide) {
1886             if (arguments.length === 0) {
1887                 return options.sideBySide;
1888             }
1889
1890             if (typeof sideBySide !== 'boolean') {
1891                 throw new TypeError('sideBySide() expects a boolean parameter');
1892             }
1893             options.sideBySide = sideBySide;
1894             if (widget) {
1895                 hide();
1896                 show();
1897             }
1898             return picker;
1899         };
1900
1901         picker.viewMode = function (viewMode) {
1902             if (arguments.length === 0) {
1903                 return options.viewMode;
1904             }
1905
1906             if (typeof viewMode !== 'string') {
1907                 throw new TypeError('viewMode() expects a string parameter');
1908             }
1909
1910             if (viewModes.indexOf(viewMode) === -1) {
1911                 throw new TypeError('viewMode() parameter must be one of (' + viewModes.join(', ') + ') value');
1912             }
1913
1914             options.viewMode = viewMode;
1915             currentViewMode = Math.max(viewModes.indexOf(viewMode), minViewModeNumber);
1916
1917             showMode();
1918             return picker;
1919         };
1920
1921         picker.toolbarPlacement = function (toolbarPlacement) {
1922             if (arguments.length === 0) {
1923                 return options.toolbarPlacement;
1924             }
1925
1926             if (typeof toolbarPlacement !== 'string') {
1927                 throw new TypeError('toolbarPlacement() expects a string parameter');
1928             }
1929             if (toolbarPlacements.indexOf(toolbarPlacement) === -1) {
1930                 throw new TypeError('toolbarPlacement() parameter must be one of (' + toolbarPlacements.join(', ') + ') value');
1931             }
1932             options.toolbarPlacement = toolbarPlacement;
1933
1934             if (widget) {
1935                 hide();
1936                 show();
1937             }
1938             return picker;
1939         };
1940
1941         picker.widgetPositioning = function (widgetPositioning) {
1942             if (arguments.length === 0) {
1943                 return $.extend({}, options.widgetPositioning);
1944             }
1945
1946             if (({}).toString.call(widgetPositioning) !== '[object Object]') {
1947                 throw new TypeError('widgetPositioning() expects an object variable');
1948             }
1949             if (widgetPositioning.horizontal) {
1950                 if (typeof widgetPositioning.horizontal !== 'string') {
1951                     throw new TypeError('widgetPositioning() horizontal variable must be a string');
1952                 }
1953                 widgetPositioning.horizontal = widgetPositioning.horizontal.toLowerCase();
1954                 if (horizontalModes.indexOf(widgetPositioning.horizontal) === -1) {
1955                     throw new TypeError('widgetPositioning() expects horizontal parameter to be one of (' + horizontalModes.join(', ') + ')');
1956                 }
1957                 options.widgetPositioning.horizontal = widgetPositioning.horizontal;
1958             }
1959             if (widgetPositioning.vertical) {
1960                 if (typeof widgetPositioning.vertical !== 'string') {
1961                     throw new TypeError('widgetPositioning() vertical variable must be a string');
1962                 }
1963                 widgetPositioning.vertical = widgetPositioning.vertical.toLowerCase();
1964                 if (verticalModes.indexOf(widgetPositioning.vertical) === -1) {
1965                     throw new TypeError('widgetPositioning() expects vertical parameter to be one of (' + verticalModes.join(', ') + ')');
1966                 }
1967                 options.widgetPositioning.vertical = widgetPositioning.vertical;
1968             }
1969             update();
1970             return picker;
1971         };
1972
1973         picker.calendarWeeks = function (calendarWeeks) {
1974             if (arguments.length === 0) {
1975                 return options.calendarWeeks;
1976             }
1977
1978             if (typeof calendarWeeks !== 'boolean') {
1979                 throw new TypeError('calendarWeeks() expects parameter to be a boolean value');
1980             }
1981
1982             options.calendarWeeks = calendarWeeks;
1983             update();
1984             return picker;
1985         };
1986
1987         picker.showTodayButton = function (showTodayButton) {
1988             if (arguments.length === 0) {
1989                 return options.showTodayButton;
1990             }
1991
1992             if (typeof showTodayButton !== 'boolean') {
1993                 throw new TypeError('showTodayButton() expects a boolean parameter');
1994             }
1995
1996             options.showTodayButton = showTodayButton;
1997             if (widget) {
1998                 hide();
1999                 show();
2000             }
2001             return picker;
2002         };
2003
2004         picker.showClear = function (showClear) {
2005             if (arguments.length === 0) {
2006                 return options.showClear;
2007             }
2008
2009             if (typeof showClear !== 'boolean') {
2010                 throw new TypeError('showClear() expects a boolean parameter');
2011             }
2012
2013             options.showClear = showClear;
2014             if (widget) {
2015                 hide();
2016                 show();
2017             }
2018             return picker;
2019         };
2020
2021         picker.widgetParent = function (widgetParent) {
2022             if (arguments.length === 0) {
2023                 return options.widgetParent;
2024             }
2025
2026             if (typeof widgetParent === 'string') {
2027                 widgetParent = $(widgetParent);
2028             }
2029
2030             if (widgetParent !== null && (typeof widgetParent !== 'string' && !(widgetParent instanceof $))) {
2031                 throw new TypeError('widgetParent() expects a string or a jQuery object parameter');
2032             }
2033
2034             options.widgetParent = widgetParent;
2035             if (widget) {
2036                 hide();
2037                 show();
2038             }
2039             return picker;
2040         };
2041
2042         picker.keepOpen = function (keepOpen) {
2043             if (arguments.length === 0) {
2044                 return options.keepOpen;
2045             }
2046
2047             if (typeof keepOpen !== 'boolean') {
2048                 throw new TypeError('keepOpen() expects a boolean parameter');
2049             }
2050
2051             options.keepOpen = keepOpen;
2052             return picker;
2053         };
2054
2055         picker.focusOnShow = function (focusOnShow) {
2056             if (arguments.length === 0) {
2057                 return options.focusOnShow;
2058             }
2059
2060             if (typeof focusOnShow !== 'boolean') {
2061                 throw new TypeError('focusOnShow() expects a boolean parameter');
2062             }
2063
2064             options.focusOnShow = focusOnShow;
2065             return picker;
2066         };
2067
2068         picker.inline = function (inline) {
2069             if (arguments.length === 0) {
2070                 return options.inline;
2071             }
2072
2073             if (typeof inline !== 'boolean') {
2074                 throw new TypeError('inline() expects a boolean parameter');
2075             }
2076
2077             options.inline = inline;
2078             return picker;
2079         };
2080
2081         picker.clear = function () {
2082             clear();
2083             return picker;
2084         };
2085
2086         picker.keyBinds = function (keyBinds) {
2087             options.keyBinds = keyBinds;
2088             return picker;
2089         };
2090
2091         picker.getMoment = function (d) {
2092             return getMoment(d);
2093         };
2094
2095         picker.debug = function (debug) {
2096             if (typeof debug !== 'boolean') {
2097                 throw new TypeError('debug() expects a boolean parameter');
2098             }
2099
2100             options.debug = debug;
2101             return picker;
2102         };
2103
2104         picker.allowInputToggle = function (allowInputToggle) {
2105             if (arguments.length === 0) {
2106                 return options.allowInputToggle;
2107             }
2108
2109             if (typeof allowInputToggle !== 'boolean') {
2110                 throw new TypeError('allowInputToggle() expects a boolean parameter');
2111             }
2112
2113             options.allowInputToggle = allowInputToggle;
2114             return picker;
2115         };
2116
2117         picker.showClose = function (showClose) {
2118             if (arguments.length === 0) {
2119                 return options.showClose;
2120             }
2121
2122             if (typeof showClose !== 'boolean') {
2123                 throw new TypeError('showClose() expects a boolean parameter');
2124             }
2125
2126             options.showClose = showClose;
2127             return picker;
2128         };
2129
2130         picker.keepInvalid = function (keepInvalid) {
2131             if (arguments.length === 0) {
2132                 return options.keepInvalid;
2133             }
2134
2135             if (typeof keepInvalid !== 'boolean') {
2136                 throw new TypeError('keepInvalid() expects a boolean parameter');
2137             }
2138             options.keepInvalid = keepInvalid;
2139             return picker;
2140         };
2141
2142         picker.datepickerInput = function (datepickerInput) {
2143             if (arguments.length === 0) {
2144                 return options.datepickerInput;
2145             }
2146
2147             if (typeof datepickerInput !== 'string') {
2148                 throw new TypeError('datepickerInput() expects a string parameter');
2149             }
2150
2151             options.datepickerInput = datepickerInput;
2152             return picker;
2153         };
2154
2155         picker.parseInputDate = function (parseInputDate) {
2156             if (arguments.length === 0) {
2157                 return options.parseInputDate;
2158             }
2159
2160             if (typeof parseInputDate !== 'function') {
2161                 throw new TypeError('parseInputDate() sholud be as function');
2162             }
2163
2164             options.parseInputDate = parseInputDate;
2165
2166             return picker;
2167         };
2168
2169         picker.disabledTimeIntervals = function (disabledTimeIntervals) {
2170             ///<signature helpKeyword="$.fn.datetimepicker.disabledTimeIntervals">
2171             ///<summary>Returns an array with the currently set disabled dates on the component.</summary>
2172             ///<returns type="array">options.disabledTimeIntervals</returns>
2173             ///</signature>
2174             ///<signature>
2175             ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
2176             ///options.enabledDates if such exist.</summary>
2177             ///<param name="dates" locid="$.fn.datetimepicker.disabledTimeIntervals_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
2178             ///</signature>
2179             if (arguments.length === 0) {
2180                 return (options.disabledTimeIntervals ? $.extend({}, options.disabledTimeIntervals) : options.disabledTimeIntervals);
2181             }
2182
2183             if (!disabledTimeIntervals) {
2184                 options.disabledTimeIntervals = false;
2185                 update();
2186                 return picker;
2187             }
2188             if (!(disabledTimeIntervals instanceof Array)) {
2189                 throw new TypeError('disabledTimeIntervals() expects an array parameter');
2190             }
2191             options.disabledTimeIntervals = disabledTimeIntervals;
2192             update();
2193             return picker;
2194         };
2195
2196         picker.disabledHours = function (hours) {
2197             ///<signature helpKeyword="$.fn.datetimepicker.disabledHours">
2198             ///<summary>Returns an array with the currently set disabled hours on the component.</summary>
2199             ///<returns type="array">options.disabledHours</returns>
2200             ///</signature>
2201             ///<signature>
2202             ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
2203             ///options.enabledHours if such exist.</summary>
2204             ///<param name="hours" locid="$.fn.datetimepicker.disabledHours_p:hours">Takes an [ int ] of values and disallows the user to select only from those hours.</param>
2205             ///</signature>
2206             if (arguments.length === 0) {
2207                 return (options.disabledHours ? $.extend({}, options.disabledHours) : options.disabledHours);
2208             }
2209
2210             if (!hours) {
2211                 options.disabledHours = false;
2212                 update();
2213                 return picker;
2214             }
2215             if (!(hours instanceof Array)) {
2216                 throw new TypeError('disabledHours() expects an array parameter');
2217             }
2218             options.disabledHours = indexGivenHours(hours);
2219             options.enabledHours = false;
2220             if (options.useCurrent && !options.keepInvalid) {
2221                 var tries = 0;
2222                 while (!isValid(date, 'h')) {
2223                     date.add(1, 'h');
2224                     if (tries === 24) {
2225                         throw 'Tried 24 times to find a valid date';
2226                     }
2227                     tries++;
2228                 }
2229                 setValue(date);
2230             }
2231             update();
2232             return picker;
2233         };
2234
2235         picker.enabledHours = function (hours) {
2236             ///<signature helpKeyword="$.fn.datetimepicker.enabledHours">
2237             ///<summary>Returns an array with the currently set enabled hours on the component.</summary>
2238             ///<returns type="array">options.enabledHours</returns>
2239             ///</signature>
2240             ///<signature>
2241             ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledHours if such exist.</summary>
2242             ///<param name="hours" locid="$.fn.datetimepicker.enabledHours_p:hours">Takes an [ int ] of values and allows the user to select only from those hours.</param>
2243             ///</signature>
2244             if (arguments.length === 0) {
2245                 return (options.enabledHours ? $.extend({}, options.enabledHours) : options.enabledHours);
2246             }
2247
2248             if (!hours) {
2249                 options.enabledHours = false;
2250                 update();
2251                 return picker;
2252             }
2253             if (!(hours instanceof Array)) {
2254                 throw new TypeError('enabledHours() expects an array parameter');
2255             }
2256             options.enabledHours = indexGivenHours(hours);
2257             options.disabledHours = false;
2258             if (options.useCurrent && !options.keepInvalid) {
2259                 var tries = 0;
2260                 while (!isValid(date, 'h')) {
2261                     date.add(1, 'h');
2262                     if (tries === 24) {
2263                         throw 'Tried 24 times to find a valid date';
2264                     }
2265                     tries++;
2266                 }
2267                 setValue(date);
2268             }
2269             update();
2270             return picker;
2271         };
2272
2273         picker.viewDate = function (newDate) {
2274             ///<signature helpKeyword="$.fn.datetimepicker.viewDate">
2275             ///<summary>Returns the component's model current viewDate, a moment object or null if not set.</summary>
2276             ///<returns type="Moment">viewDate.clone()</returns>
2277             ///</signature>
2278             ///<signature>
2279             ///<summary>Sets the components model current moment to it. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration.</summary>
2280             ///<param name="newDate" locid="$.fn.datetimepicker.date_p:newDate">Takes string, viewDate, moment, null parameter.</param>
2281             ///</signature>
2282             if (arguments.length === 0) {
2283                 return viewDate.clone();
2284             }
2285
2286             if (!newDate) {
2287                 viewDate = date.clone();
2288                 return picker;
2289             }
2290
2291             if (typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) {
2292                 throw new TypeError('viewDate() parameter must be one of [string, moment or Date]');
2293             }
2294
2295             viewDate = parseInputDate(newDate);
2296             viewUpdate();
2297             return picker;
2298         };
2299
2300         // initializing element and component attributes
2301         if (element.is('input')) {
2302             input = element;
2303         } else {
2304             input = element.find(options.datepickerInput);
2305             if (input.size() === 0) {
2306                 input = element.find('input');
2307             } else if (!input.is('input')) {
2308                 throw new Error('CSS class "' + options.datepickerInput + '" cannot be applied to non input element');
2309             }
2310         }
2311
2312         if (element.hasClass('input-group')) {
2313             // in case there is more then one 'input-group-addon' Issue #48
2314             if (element.find('.datepickerbutton').size() === 0) {
2315                 component = element.find('.input-group-addon');
2316             } else {
2317                 component = element.find('.datepickerbutton');
2318             }
2319         }
2320
2321         if (!options.inline && !input.is('input')) {
2322             throw new Error('Could not initialize DateTimePicker without an input element');
2323         }
2324
2325         // Set defaults for date here now instead of in var declaration
2326         date = getMoment();
2327         viewDate = date.clone();
2328
2329         $.extend(true, options, dataToOptions());
2330
2331         picker.options(options);
2332
2333         initFormatting();
2334
2335         attachDatePickerElementEvents();
2336
2337         if (input.prop('disabled')) {
2338             picker.disable();
2339         }
2340         if (input.is('input') && input.val().trim().length !== 0) {
2341             setValue(parseInputDate(input.val().trim()));
2342         }
2343         else if (options.defaultDate && input.attr('placeholder') === undefined) {
2344             setValue(options.defaultDate);
2345         }
2346         if (options.inline) {
2347             show();
2348         }
2349         return picker;
2350     };
2351
2352     /********************************************************************************
2353      *
2354      * jQuery plugin constructor and defaults object
2355      *
2356      ********************************************************************************/
2357
2358     $.fn.datetimepicker = function (options) {
2359         return this.each(function () {
2360             var $this = $(this);
2361             if (!$this.data('DateTimePicker')) {
2362                 // create a private copy of the defaults object
2363                 options = $.extend(true, {}, $.fn.datetimepicker.defaults, options);
2364                 $this.data('DateTimePicker', dateTimePicker($this, options));
2365             }
2366         });
2367     };
2368
2369     $.fn.datetimepicker.defaults = {
2370         timeZone: 'Etc/UTC',
2371         format: false,
2372         dayViewHeaderFormat: 'MMMM YYYY',
2373         extraFormats: false,
2374         stepping: 1,
2375         minDate: false,
2376         maxDate: false,
2377         useCurrent: true,
2378         collapse: true,
2379         locale: moment.locale(),
2380         defaultDate: false,
2381         disabledDates: false,
2382         enabledDates: false,
2383         icons: {
2384             time: 'glyphicon glyphicon-time',
2385             date: 'glyphicon glyphicon-calendar',
2386             up: 'glyphicon glyphicon-chevron-up',
2387             down: 'glyphicon glyphicon-chevron-down',
2388             previous: 'glyphicon glyphicon-chevron-left',
2389             next: 'glyphicon glyphicon-chevron-right',
2390             today: 'glyphicon glyphicon-screenshot',
2391             clear: 'glyphicon glyphicon-trash',
2392             close: 'glyphicon glyphicon-remove'
2393         },
2394         tooltips: {
2395             today: 'Go to today',
2396             clear: 'Clear selection',
2397             close: 'Close the picker',
2398             selectMonth: 'Select Month',
2399             prevMonth: 'Previous Month',
2400             nextMonth: 'Next Month',
2401             selectYear: 'Select Year',
2402             prevYear: 'Previous Year',
2403             nextYear: 'Next Year',
2404             selectDecade: 'Select Decade',
2405             prevDecade: 'Previous Decade',
2406             nextDecade: 'Next Decade',
2407             prevCentury: 'Previous Century',
2408             nextCentury: 'Next Century',
2409             pickHour: 'Pick Hour',
2410             incrementHour: 'Increment Hour',
2411             decrementHour: 'Decrement Hour',
2412             pickMinute: 'Pick Minute',
2413             incrementMinute: 'Increment Minute',
2414             decrementMinute: 'Decrement Minute',
2415             pickSecond: 'Pick Second',
2416             incrementSecond: 'Increment Second',
2417             decrementSecond: 'Decrement Second',
2418             togglePeriod: 'Toggle Period',
2419             selectTime: 'Select Time'
2420         },
2421         useStrict: false,
2422         sideBySide: false,
2423         daysOfWeekDisabled: false,
2424         calendarWeeks: false,
2425         viewMode: 'days',
2426         toolbarPlacement: 'default',
2427         showTodayButton: false,
2428         showClear: false,
2429         showClose: false,
2430         widgetPositioning: {
2431             horizontal: 'auto',
2432             vertical: 'auto'
2433         },
2434         widgetParent: null,
2435         ignoreReadonly: false,
2436         keepOpen: false,
2437         focusOnShow: true,
2438         inline: false,
2439         keepInvalid: false,
2440         datepickerInput: '.datepickerinput',
2441         keyBinds: {
2442             up: function (widget) {
2443                 if (!widget) {
2444                     return;
2445                 }
2446                 var d = this.date() || this.getMoment();
2447                 if (widget.find('.datepicker').is(':visible')) {
2448                     this.date(d.clone().subtract(7, 'd'));
2449                 } else {
2450                     this.date(d.clone().add(this.stepping(), 'm'));
2451                 }
2452             },
2453             down: function (widget) {
2454                 if (!widget) {
2455                     this.show();
2456                     return;
2457                 }
2458                 var d = this.date() || this.getMoment();
2459                 if (widget.find('.datepicker').is(':visible')) {
2460                     this.date(d.clone().add(7, 'd'));
2461                 } else {
2462                     this.date(d.clone().subtract(this.stepping(), 'm'));
2463                 }
2464             },
2465             'control up': function (widget) {
2466                 if (!widget) {
2467                     return;
2468                 }
2469                 var d = this.date() || this.getMoment();
2470                 if (widget.find('.datepicker').is(':visible')) {
2471                     this.date(d.clone().subtract(1, 'y'));
2472                 } else {
2473                     this.date(d.clone().add(1, 'h'));
2474                 }
2475             },
2476             'control down': function (widget) {
2477                 if (!widget) {
2478                     return;
2479                 }
2480                 var d = this.date() || this.getMoment();
2481                 if (widget.find('.datepicker').is(':visible')) {
2482                     this.date(d.clone().add(1, 'y'));
2483                 } else {
2484                     this.date(d.clone().subtract(1, 'h'));
2485                 }
2486             },
2487             left: function (widget) {
2488                 if (!widget) {
2489                     return;
2490                 }
2491                 var d = this.date() || this.getMoment();
2492                 if (widget.find('.datepicker').is(':visible')) {
2493                     this.date(d.clone().subtract(1, 'd'));
2494                 }
2495             },
2496             right: function (widget) {
2497                 if (!widget) {
2498                     return;
2499                 }
2500                 var d = this.date() || this.getMoment();
2501                 if (widget.find('.datepicker').is(':visible')) {
2502                     this.date(d.clone().add(1, 'd'));
2503                 }
2504             },
2505             pageUp: function (widget) {
2506                 if (!widget) {
2507                     return;
2508                 }
2509                 var d = this.date() || this.getMoment();
2510                 if (widget.find('.datepicker').is(':visible')) {
2511                     this.date(d.clone().subtract(1, 'M'));
2512                 }
2513             },
2514             pageDown: function (widget) {
2515                 if (!widget) {
2516                     return;
2517                 }
2518                 var d = this.date() || this.getMoment();
2519                 if (widget.find('.datepicker').is(':visible')) {
2520                     this.date(d.clone().add(1, 'M'));
2521                 }
2522             },
2523             enter: function () {
2524                 this.hide();
2525             },
2526             escape: function () {
2527                 this.hide();
2528             },
2529             //tab: function (widget) { //this break the flow of the form. disabling for now
2530             //    var toggle = widget.find('.picker-switch a[data-action="togglePicker"]');
2531             //    if(toggle.length > 0) toggle.click();
2532             //},
2533             'control space': function (widget) {
2534                 if (widget.find('.timepicker').is(':visible')) {
2535                     widget.find('.btn[data-action="togglePeriod"]').click();
2536                 }
2537             },
2538             t: function () {
2539                 this.date(this.getMoment());
2540             },
2541             'delete': function () {
2542                 this.clear();
2543             }
2544         },
2545         debug: false,
2546         allowInputToggle: false,
2547         disabledTimeIntervals: false,
2548         disabledHours: false,
2549         enabledHours: false,
2550         viewDate: false
2551     };
2552 }));