PageSourceSearch

https://www.moselle.fr/js/lib/bootstrap-datetimepicker/bootstrap-datetimepicker.js

js moselle.fr collected 2026-09-25 02:42:21 UTC 102,801 bytes, 2,528 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.