UNPKG

eonasdan-bootstrap-datetimepicker

Version:

A date/time picker component designed to work with Bootstrap 3 and Momentjs. For usage, installation and demos see Project Site on GitHub

1,118 lines (983 loc) 108 kB
/*! * Bootstrap Datetime Picker v4.17.49 * Copyright 2015-2020 Jonathan Peterson * Licensed under MIT (https://github.com/Eonasdan/bootstrap-datetimepicker/blob/master/LICENSE) */ /*global define:false */ /*global exports:false */ /*global require:false */ /*global jQuery:false */ /*global moment:false */ (function (factory) { 'use strict'; if (typeof define === 'function' && define.amd) { // AMD is used - Register as an anonymous module. define(['jquery', 'moment'], factory); } else if (typeof exports === 'object') { module.exports = factory(require('jquery'), require('moment')); } else { // Neither AMD nor CommonJS used. Use global variables. if (typeof jQuery === 'undefined') { throw 'bootstrap-datetimepicker requires jQuery to be loaded first'; } if (typeof moment === 'undefined') { throw 'bootstrap-datetimepicker requires Moment.js to be loaded first'; } factory(jQuery, moment); } }(function ($, moment) { 'use strict'; if (!moment) { throw new Error('bootstrap-datetimepicker requires Moment.js to be loaded first'); } var dateTimePicker = function (element, options) { var picker = {}, date, viewDate, unset = true, input, component = false, widget = false, use24Hours, minViewModeNumber = 0, actualFormat, parseFormats, currentViewMode, datePickerModes = [ { clsName: 'days', navFnc: 'M', navStep: 1 }, { clsName: 'months', navFnc: 'y', navStep: 1 }, { clsName: 'years', navFnc: 'y', navStep: 10 }, { clsName: 'decades', navFnc: 'y', navStep: 100 } ], viewModes = ['days', 'months', 'years', 'decades'], verticalModes = ['top', 'bottom', 'auto'], horizontalModes = ['left', 'right', 'auto'], toolbarPlacements = ['default', 'top', 'bottom'], keyMap = { 'up': 38, 38: 'up', 'down': 40, 40: 'down', 'left': 37, 37: 'left', 'right': 39, 39: 'right', 'tab': 9, 9: 'tab', 'escape': 27, 27: 'escape', 'enter': 13, 13: 'enter', 'pageUp': 33, 33: 'pageUp', 'pageDown': 34, 34: 'pageDown', 'shift': 16, 16: 'shift', 'control': 17, 17: 'control', 'space': 32, 32: 'space', 't': 84, 84: 't', 'delete': 46, 46: 'delete' }, keyState = {}, /******************************************************************************** * * Private functions * ********************************************************************************/ hasTimeZone = function () { return moment.tz !== undefined && options.timeZone !== undefined && options.timeZone !== null && options.timeZone !== ''; }, getMoment = function (d) { var returnMoment; if (d === undefined || d === null) { returnMoment = moment(); //TODO should this use format? and locale? } else if (moment.isDate(d) || moment.isMoment(d)) { // If the date that is passed in is already a Date() or moment() object, // pass it directly to moment. returnMoment = moment(d); } else if (hasTimeZone()) { // There is a string to parse and a default time zone // parse with the tz function which takes a default time zone if it is not in the format string returnMoment = moment.tz(d, parseFormats, options.useStrict, options.timeZone); } else { returnMoment = moment(d, parseFormats, options.useStrict); } if (hasTimeZone()) { returnMoment.tz(options.timeZone); } return returnMoment; }, isEnabled = function (granularity) { if (typeof granularity !== 'string' || granularity.length > 1) { throw new TypeError('isEnabled expects a single character string parameter'); } switch (granularity) { case 'y': return actualFormat.indexOf('Y') !== -1; case 'M': return actualFormat.indexOf('M') !== -1; case 'd': return actualFormat.toLowerCase().indexOf('d') !== -1; case 'h': case 'H': return actualFormat.toLowerCase().indexOf('h') !== -1; case 'm': return actualFormat.indexOf('m') !== -1; case 's': return actualFormat.indexOf('s') !== -1; default: return false; } }, hasTime = function () { return (isEnabled('h') || isEnabled('m') || isEnabled('s')); }, hasDate = function () { return (isEnabled('y') || isEnabled('M') || isEnabled('d')); }, getDatePickerTemplate = function () { var headTemplate = $('<thead>') .append($('<tr>') .append($('<th>').addClass('prev').attr('data-action', 'previous') .append($('<span>').addClass(options.icons.previous)) ) .append($('<th>').addClass('picker-switch').attr('data-action', 'pickerSwitch').attr('colspan', (options.calendarWeeks ? '6' : '5'))) .append($('<th>').addClass('next').attr('data-action', 'next') .append($('<span>').addClass(options.icons.next)) ) ), contTemplate = $('<tbody>') .append($('<tr>') .append($('<td>').attr('colspan', (options.calendarWeeks ? '8' : '7'))) ); return [ $('<div>').addClass('datepicker-days') .append($('<table>').addClass('table-condensed') .append(headTemplate) .append($('<tbody>')) ), $('<div>').addClass('datepicker-months') .append($('<table>').addClass('table-condensed') .append(headTemplate.clone()) .append(contTemplate.clone()) ), $('<div>').addClass('datepicker-years') .append($('<table>').addClass('table-condensed') .append(headTemplate.clone()) .append(contTemplate.clone()) ), $('<div>').addClass('datepicker-decades') .append($('<table>').addClass('table-condensed') .append(headTemplate.clone()) .append(contTemplate.clone()) ) ]; }, getTimePickerMainTemplate = function () { var topRow = $('<tr>'), middleRow = $('<tr>'), bottomRow = $('<tr>'); if (isEnabled('h')) { topRow.append($('<td>') .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementHour }).addClass('btn').attr('data-action', 'incrementHours').append($('<span>').addClass(options.icons.up)))); middleRow.append($('<td>') .append($('<span>').addClass('timepicker-hour').attr({ 'data-time-component': 'hours', 'title': options.tooltips.pickHour }).attr('data-action', 'showHours'))); bottomRow.append($('<td>') .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementHour }).addClass('btn').attr('data-action', 'decrementHours').append($('<span>').addClass(options.icons.down)))); } if (isEnabled('m')) { if (isEnabled('h')) { topRow.append($('<td>').addClass('separator')); middleRow.append($('<td>').addClass('separator').html(':')); bottomRow.append($('<td>').addClass('separator')); } topRow.append($('<td>') .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementMinute }).addClass('btn').attr('data-action', 'incrementMinutes') .append($('<span>').addClass(options.icons.up)))); middleRow.append($('<td>') .append($('<span>').addClass('timepicker-minute').attr({ 'data-time-component': 'minutes', 'title': options.tooltips.pickMinute }).attr('data-action', 'showMinutes'))); bottomRow.append($('<td>') .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementMinute }).addClass('btn').attr('data-action', 'decrementMinutes') .append($('<span>').addClass(options.icons.down)))); } if (isEnabled('s')) { if (isEnabled('m')) { topRow.append($('<td>').addClass('separator')); middleRow.append($('<td>').addClass('separator').html(':')); bottomRow.append($('<td>').addClass('separator')); } topRow.append($('<td>') .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementSecond }).addClass('btn').attr('data-action', 'incrementSeconds') .append($('<span>').addClass(options.icons.up)))); middleRow.append($('<td>') .append($('<span>').addClass('timepicker-second').attr({ 'data-time-component': 'seconds', 'title': options.tooltips.pickSecond }).attr('data-action', 'showSeconds'))); bottomRow.append($('<td>') .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementSecond }).addClass('btn').attr('data-action', 'decrementSeconds') .append($('<span>').addClass(options.icons.down)))); } if (!use24Hours) { topRow.append($('<td>').addClass('separator')); middleRow.append($('<td>') .append($('<button>').addClass('btn btn-primary').attr({ 'data-action': 'togglePeriod', tabindex: '-1', 'title': options.tooltips.togglePeriod }))); bottomRow.append($('<td>').addClass('separator')); } return $('<div>').addClass('timepicker-picker') .append($('<table>').addClass('table-condensed') .append([topRow, middleRow, bottomRow])); }, getTimePickerTemplate = function () { var hoursView = $('<div>').addClass('timepicker-hours') .append($('<table>').addClass('table-condensed')), minutesView = $('<div>').addClass('timepicker-minutes') .append($('<table>').addClass('table-condensed')), secondsView = $('<div>').addClass('timepicker-seconds') .append($('<table>').addClass('table-condensed')), ret = [getTimePickerMainTemplate()]; if (isEnabled('h')) { ret.push(hoursView); } if (isEnabled('m')) { ret.push(minutesView); } if (isEnabled('s')) { ret.push(secondsView); } return ret; }, getToolbar = function () { var row = []; if (options.showTodayButton) { row.push($('<td>').append($('<a>').attr({ 'data-action': 'today', 'title': options.tooltips.today }).append($('<span>').addClass(options.icons.today)))); } if (!options.sideBySide && hasDate() && hasTime()) { row.push($('<td>').append($('<a>').attr({ 'data-action': 'togglePicker', 'title': options.tooltips.selectTime }).append($('<span>').addClass(options.icons.time)))); } if (options.showClear) { row.push($('<td>').append($('<a>').attr({ 'data-action': 'clear', 'title': options.tooltips.clear }).append($('<span>').addClass(options.icons.clear)))); } if (options.showClose) { row.push($('<td>').append($('<a>').attr({ 'data-action': 'close', 'title': options.tooltips.close }).append($('<span>').addClass(options.icons.close)))); } return $('<table>').addClass('table-condensed').append($('<tbody>').append($('<tr>').append(row))); }, getTemplate = function () { var template = $('<div>').addClass('bootstrap-datetimepicker-widget dropdown-menu'), dateView = $('<div>').addClass('datepicker').append(getDatePickerTemplate()), timeView = $('<div>').addClass('timepicker').append(getTimePickerTemplate()), content = $('<ul>').addClass('list-unstyled'), toolbar = $('<li>').addClass('picker-switch' + (options.collapse ? ' accordion-toggle' : '')).append(getToolbar()); if (options.inline) { template.removeClass('dropdown-menu'); } if (use24Hours) { template.addClass('usetwentyfour'); } if (isEnabled('s') && !use24Hours) { template.addClass('wider'); } if (options.sideBySide && hasDate() && hasTime()) { template.addClass('timepicker-sbs'); if (options.toolbarPlacement === 'top') { template.append(toolbar); } template.append( $('<div>').addClass('row') .append(dateView.addClass('col-md-6')) .append(timeView.addClass('col-md-6')) ); if (options.toolbarPlacement === 'bottom') { template.append(toolbar); } return template; } if (options.toolbarPlacement === 'top') { content.append(toolbar); } if (hasDate()) { content.append($('<li>').addClass((options.collapse && hasTime() ? 'collapse in' : '')).append(dateView)); } if (options.toolbarPlacement === 'default') { content.append(toolbar); } if (hasTime()) { content.append($('<li>').addClass((options.collapse && hasDate() ? 'collapse' : '')).append(timeView)); } if (options.toolbarPlacement === 'bottom') { content.append(toolbar); } return template.append(content); }, dataToOptions = function () { var eData, dataOptions = {}; if (element.is('input') || options.inline) { eData = element.data(); } else { eData = element.find('input').data(); } if (eData.dateOptions && eData.dateOptions instanceof Object) { dataOptions = $.extend(true, dataOptions, eData.dateOptions); } $.each(options, function (key) { var attributeName = 'date' + key.charAt(0).toUpperCase() + key.slice(1); if (eData[attributeName] !== undefined) { dataOptions[key] = eData[attributeName]; } }); return dataOptions; }, place = function () { var position = (component || element).position(), offset = (component || element).offset(), vertical = options.widgetPositioning.vertical, horizontal = options.widgetPositioning.horizontal, parent; if (options.widgetParent) { parent = options.widgetParent.append(widget); } else if (element.is('input')) { parent = element.after(widget).parent(); } else if (options.inline) { parent = element.append(widget); return; } else { parent = element; element.children().first().after(widget); } // Top and bottom logic if (vertical === 'auto') { if (offset.top + widget.height() * 1.5 >= $(window).height() + $(window).scrollTop() && widget.height() + element.outerHeight() < offset.top) { vertical = 'top'; } else { vertical = 'bottom'; } } // Left and right logic if (horizontal === 'auto') { if (parent.width() < offset.left + widget.outerWidth() / 2 && offset.left + widget.outerWidth() > $(window).width()) { horizontal = 'right'; } else { horizontal = 'left'; } } if (vertical === 'top') { widget.addClass('top').removeClass('bottom'); } else { widget.addClass('bottom').removeClass('top'); } if (horizontal === 'right') { widget.addClass('pull-right'); } else { widget.removeClass('pull-right'); } // find the first parent element that has a non-static css positioning if (parent.css('position') === 'static') { parent = parent.parents().filter(function () { return $(this).css('position') !== 'static'; }).first(); } if (parent.length === 0) { throw new Error('datetimepicker component should be placed within a non-static positioned container'); } widget.css({ top: vertical === 'top' ? 'auto' : position.top + element.outerHeight(), bottom: vertical === 'top' ? parent.outerHeight() - (parent === element ? 0 : position.top) : 'auto', left: horizontal === 'left' ? (parent === element ? 0 : position.left) : 'auto', right: horizontal === 'left' ? 'auto' : parent.outerWidth() - element.outerWidth() - (parent === element ? 0 : position.left) }); }, notifyEvent = function (e) { if (e.type === 'dp.change' && ((e.date && e.date.isSame(e.oldDate)) || (!e.date && !e.oldDate))) { return; } element.trigger(e); }, viewUpdate = function (e) { if (e === 'y') { e = 'YYYY'; } notifyEvent({ type: 'dp.update', change: e, viewDate: viewDate.clone() }); }, showMode = function (dir) { if (!widget) { return; } if (dir) { currentViewMode = Math.max(minViewModeNumber, Math.min(3, currentViewMode + dir)); } widget.find('.datepicker > div').hide().filter('.datepicker-' + datePickerModes[currentViewMode].clsName).show(); }, fillDow = function () { var row = $('<tr>'), currentDate = viewDate.clone().startOf('w').startOf('d'); if (options.calendarWeeks === true) { row.append($('<th>').addClass('cw').text('#')); } while (currentDate.isBefore(viewDate.clone().endOf('w'))) { row.append($('<th>').addClass('dow').text(currentDate.format('dd'))); currentDate.add(1, 'd'); } widget.find('.datepicker-days thead').append(row); }, isInDisabledDates = function (testDate) { return options.disabledDates[testDate.format('YYYY-MM-DD')] === true; }, isInEnabledDates = function (testDate) { return options.enabledDates[testDate.format('YYYY-MM-DD')] === true; }, isInDisabledHours = function (testDate) { return options.disabledHours[testDate.format('H')] === true; }, isInEnabledHours = function (testDate) { return options.enabledHours[testDate.format('H')] === true; }, isValid = function (targetMoment, granularity) { if (!targetMoment.isValid()) { return false; } if (options.disabledDates && granularity === 'd' && isInDisabledDates(targetMoment)) { return false; } if (options.enabledDates && granularity === 'd' && !isInEnabledDates(targetMoment)) { return false; } if (options.minDate && targetMoment.isBefore(options.minDate, granularity)) { return false; } if (options.maxDate && targetMoment.isAfter(options.maxDate, granularity)) { return false; } if (options.daysOfWeekDisabled && granularity === 'd' && options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) { return false; } if (options.disabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && isInDisabledHours(targetMoment)) { return false; } if (options.enabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && !isInEnabledHours(targetMoment)) { return false; } if (options.disabledTimeIntervals && (granularity === 'h' || granularity === 'm' || granularity === 's')) { var found = false; $.each(options.disabledTimeIntervals, function () { if (targetMoment.isBetween(this[0], this[1])) { found = true; return false; } }); if (found) { return false; } } return true; }, fillMonths = function () { var spans = [], monthsShort = viewDate.clone().startOf('y').startOf('d'); while (monthsShort.isSame(viewDate, 'y')) { spans.push($('<span>').attr('data-action', 'selectMonth').addClass('month').text(monthsShort.format('MMM'))); monthsShort.add(1, 'M'); } widget.find('.datepicker-months td').empty().append(spans); }, updateMonths = function () { var monthsView = widget.find('.datepicker-months'), monthsViewHeader = monthsView.find('th'), months = monthsView.find('tbody').find('span'); monthsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevYear); monthsViewHeader.eq(1).attr('title', options.tooltips.selectYear); monthsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextYear); monthsView.find('.disabled').removeClass('disabled'); if (!isValid(viewDate.clone().subtract(1, 'y'), 'y')) { monthsViewHeader.eq(0).addClass('disabled'); } monthsViewHeader.eq(1).text(viewDate.year()); if (!isValid(viewDate.clone().add(1, 'y'), 'y')) { monthsViewHeader.eq(2).addClass('disabled'); } months.removeClass('active'); if (date.isSame(viewDate, 'y') && !unset) { months.eq(date.month()).addClass('active'); } months.each(function (index) { if (!isValid(viewDate.clone().month(index), 'M')) { $(this).addClass('disabled'); } }); }, updateYears = function () { var yearsView = widget.find('.datepicker-years'), yearsViewHeader = yearsView.find('th'), startYear = viewDate.clone().subtract(5, 'y'), endYear = viewDate.clone().add(6, 'y'), html = ''; yearsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevDecade); yearsViewHeader.eq(1).attr('title', options.tooltips.selectDecade); yearsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextDecade); yearsView.find('.disabled').removeClass('disabled'); if (options.minDate && options.minDate.isAfter(startYear, 'y')) { yearsViewHeader.eq(0).addClass('disabled'); } yearsViewHeader.eq(1).text(startYear.year() + '-' + endYear.year()); if (options.maxDate && options.maxDate.isBefore(endYear, 'y')) { yearsViewHeader.eq(2).addClass('disabled'); } while (!startYear.isAfter(endYear, 'y')) { html += '<span data-action="selectYear" class="year' + (startYear.isSame(date, 'y') && !unset ? ' active' : '') + (!isValid(startYear, 'y') ? ' disabled' : '') + '">' + startYear.year() + '</span>'; startYear.add(1, 'y'); } yearsView.find('td').html(html); }, updateDecades = function () { var decadesView = widget.find('.datepicker-decades'), decadesViewHeader = decadesView.find('th'), startDecade = moment({ y: viewDate.year() - (viewDate.year() % 100) - 1 }), endDecade = startDecade.clone().add(100, 'y'), startedAt = startDecade.clone(), minDateDecade = false, maxDateDecade = false, endDecadeYear, html = ''; decadesViewHeader.eq(0).find('span').attr('title', options.tooltips.prevCentury); decadesViewHeader.eq(2).find('span').attr('title', options.tooltips.nextCentury); decadesView.find('.disabled').removeClass('disabled'); if (startDecade.isSame(moment({ y: 1900 })) || (options.minDate && options.minDate.isAfter(startDecade, 'y'))) { decadesViewHeader.eq(0).addClass('disabled'); } decadesViewHeader.eq(1).text(startDecade.year() + '-' + endDecade.year()); if (startDecade.isSame(moment({ y: 2000 })) || (options.maxDate && options.maxDate.isBefore(endDecade, 'y'))) { decadesViewHeader.eq(2).addClass('disabled'); } while (!startDecade.isAfter(endDecade, 'y')) { endDecadeYear = startDecade.year() + 12; minDateDecade = options.minDate && options.minDate.isAfter(startDecade, 'y') && options.minDate.year() <= endDecadeYear; maxDateDecade = options.maxDate && options.maxDate.isAfter(startDecade, 'y') && options.maxDate.year() <= endDecadeYear; html += '<span data-action="selectDecade" class="decade' + (date.isAfter(startDecade) && date.year() <= endDecadeYear ? ' active' : '') + (!isValid(startDecade, 'y') && !minDateDecade && !maxDateDecade ? ' disabled' : '') + '" data-selection="' + (startDecade.year() + 6) + '">' + (startDecade.year() + 1) + ' - ' + (startDecade.year() + 12) + '</span>'; startDecade.add(12, 'y'); } html += '<span></span><span></span><span></span>'; //push the dangling block over, at least this way it's even decadesView.find('td').html(html); decadesViewHeader.eq(1).text((startedAt.year() + 1) + '-' + (startDecade.year())); }, fillDate = function () { var daysView = widget.find('.datepicker-days'), daysViewHeader = daysView.find('th'), currentDate, html = [], row, clsNames = [], i; if (!hasDate()) { return; } daysViewHeader.eq(0).find('span').attr('title', options.tooltips.prevMonth); daysViewHeader.eq(1).attr('title', options.tooltips.selectMonth); daysViewHeader.eq(2).find('span').attr('title', options.tooltips.nextMonth); daysView.find('.disabled').removeClass('disabled'); daysViewHeader.eq(1).text(viewDate.format(options.dayViewHeaderFormat)); if (!isValid(viewDate.clone().subtract(1, 'M'), 'M')) { daysViewHeader.eq(0).addClass('disabled'); } if (!isValid(viewDate.clone().add(1, 'M'), 'M')) { daysViewHeader.eq(2).addClass('disabled'); } currentDate = viewDate.clone().startOf('M').startOf('w').startOf('d'); for (i = 0; i < 42; i++) { //always display 42 days (should show 6 weeks) if (currentDate.weekday() === 0) { row = $('<tr>'); if (options.calendarWeeks) { row.append('<td class="cw">' + currentDate.week() + '</td>'); } html.push(row); } clsNames = ['day']; if (currentDate.isBefore(viewDate, 'M')) { clsNames.push('old'); } if (currentDate.isAfter(viewDate, 'M')) { clsNames.push('new'); } if (currentDate.isSame(date, 'd') && !unset) { clsNames.push('active'); } if (!isValid(currentDate, 'd')) { clsNames.push('disabled'); } if (currentDate.isSame(getMoment(), 'd')) { clsNames.push('today'); } if (currentDate.day() === 0 || currentDate.day() === 6) { clsNames.push('weekend'); } notifyEvent({ type: 'dp.classify', date: currentDate, classNames: clsNames }); row.append('<td data-action="selectDay" data-day="' + currentDate.format('L') + '" class="' + clsNames.join(' ') + '">' + currentDate.date() + '</td>'); currentDate.add(1, 'd'); } daysView.find('tbody').empty().append(html); updateMonths(); updateYears(); updateDecades(); }, fillHours = function () { var table = widget.find('.timepicker-hours table'), currentHour = viewDate.clone().startOf('d'), html = [], row = $('<tr>'); if (viewDate.hour() > 11 && !use24Hours) { currentHour.hour(12); } while (currentHour.isSame(viewDate, 'd') && (use24Hours || (viewDate.hour() < 12 && currentHour.hour() < 12) || viewDate.hour() > 11)) { if (currentHour.hour() % 4 === 0) { row = $('<tr>'); html.push(row); } row.append('<td data-action="selectHour" class="hour' + (!isValid(currentHour, 'h') ? ' disabled' : '') + '">' + currentHour.format(use24Hours ? 'HH' : 'hh') + '</td>'); currentHour.add(1, 'h'); } table.empty().append(html); }, fillMinutes = function () { var table = widget.find('.timepicker-minutes table'), currentMinute = viewDate.clone().startOf('h'), html = [], row = $('<tr>'), step = options.stepping === 1 ? 5 : options.stepping; while (viewDate.isSame(currentMinute, 'h')) { if (currentMinute.minute() % (step * 4) === 0) { row = $('<tr>'); html.push(row); } row.append('<td data-action="selectMinute" class="minute' + (!isValid(currentMinute, 'm') ? ' disabled' : '') + '">' + currentMinute.format('mm') + '</td>'); currentMinute.add(step, 'm'); } table.empty().append(html); }, fillSeconds = function () { var table = widget.find('.timepicker-seconds table'), currentSecond = viewDate.clone().startOf('m'), html = [], row = $('<tr>'); while (viewDate.isSame(currentSecond, 'm')) { if (currentSecond.second() % 20 === 0) { row = $('<tr>'); html.push(row); } row.append('<td data-action="selectSecond" class="second' + (!isValid(currentSecond, 's') ? ' disabled' : '') + '">' + currentSecond.format('ss') + '</td>'); currentSecond.add(5, 's'); } table.empty().append(html); }, fillTime = function () { var toggle, newDate, timeComponents = widget.find('.timepicker span[data-time-component]'); if (!use24Hours) { toggle = widget.find('.timepicker [data-action=togglePeriod]'); newDate = date.clone().add((date.hours() >= 12) ? -12 : 12, 'h'); toggle.text(date.format('A')); if (isValid(newDate, 'h')) { toggle.removeClass('disabled'); } else { toggle.addClass('disabled'); } } timeComponents.filter('[data-time-component=hours]').text(date.format(use24Hours ? 'HH' : 'hh')); timeComponents.filter('[data-time-component=minutes]').text(date.format('mm')); timeComponents.filter('[data-time-component=seconds]').text(date.format('ss')); fillHours(); fillMinutes(); fillSeconds(); }, update = function () { if (!widget) { return; } fillDate(); fillTime(); }, setValue = function (targetMoment) { var oldDate = unset ? null : date; // case of calling setValue(null or false) if (!targetMoment) { unset = true; input.val(''); element.data('date', ''); notifyEvent({ type: 'dp.change', date: false, oldDate: oldDate }); update(); return; } targetMoment = targetMoment.clone().locale(options.locale); if (hasTimeZone()) { targetMoment.tz(options.timeZone); } if (options.stepping !== 1) { targetMoment.minutes((Math.round(targetMoment.minutes() / options.stepping) * options.stepping)).seconds(0); while (options.minDate && targetMoment.isBefore(options.minDate)) { targetMoment.add(options.stepping, 'minutes'); } } if (isValid(targetMoment)) { date = targetMoment; viewDate = date.clone(); input.val(date.format(actualFormat)); element.data('date', date.format(actualFormat)); unset = false; update(); notifyEvent({ type: 'dp.change', date: date.clone(), oldDate: oldDate }); } else { if (!options.keepInvalid) { input.val(unset ? '' : date.format(actualFormat)); } else { notifyEvent({ type: 'dp.change', date: targetMoment, oldDate: oldDate }); } notifyEvent({ type: 'dp.error', date: targetMoment, oldDate: oldDate }); } }, /** * Hides the widget. Possibly will emit dp.hide */ hide = function () { var transitioning = false; if (!widget) { return picker; } // Ignore event if in the middle of a picker transition widget.find('.collapse').each(function () { var collapseData = $(this).data('collapse'); if (collapseData && collapseData.transitioning) { transitioning = true; return false; } return true; }); if (transitioning) { return picker; } if (component && component.hasClass('btn')) { component.toggleClass('active'); } widget.hide(); $(window).off('resize', place); widget.off('click', '[data-action]'); widget.off('mousedown', false); widget.remove(); widget = false; notifyEvent({ type: 'dp.hide', date: date.clone() }); input.blur(); viewDate = date.clone(); return picker; }, clear = function () { setValue(null); }, parseInputDate = function (inputDate) { if (options.parseInputDate === undefined) { if (!moment.isMoment(inputDate) || inputDate instanceof Date) { inputDate = getMoment(inputDate); } } else { inputDate = options.parseInputDate(inputDate); } //inputDate.locale(options.locale); return inputDate; }, /******************************************************************************** * * Widget UI interaction functions * ********************************************************************************/ actions = { next: function () { var navFnc = datePickerModes[currentViewMode].navFnc; viewDate.add(datePickerModes[currentViewMode].navStep, navFnc); fillDate(); viewUpdate(navFnc); }, previous: function () { var navFnc = datePickerModes[currentViewMode].navFnc; viewDate.subtract(datePickerModes[currentViewMode].navStep, navFnc); fillDate(); viewUpdate(navFnc); }, pickerSwitch: function () { showMode(1); }, selectMonth: function (e) { var month = $(e.target).closest('tbody').find('span').index($(e.target)); viewDate.month(month); if (currentViewMode === minViewModeNumber) { setValue(date.clone().year(viewDate.year()).month(viewDate.month())); if (!options.inline) { hide(); } } else { showMode(-1); fillDate(); } viewUpdate('M'); }, selectYear: function (e) { var year = parseInt($(e.target).text(), 10) || 0; viewDate.year(year); if (currentViewMode === minViewModeNumber) { setValue(date.clone().year(viewDate.year())); if (!options.inline) { hide(); } } else { showMode(-1); fillDate(); } viewUpdate('YYYY'); }, selectDecade: function (e) { var year = parseInt($(e.target).data('selection'), 10) || 0; viewDate.year(year); if (currentViewMode === minViewModeNumber) { setValue(date.clone().year(viewDate.year())); if (!options.inline) { hide(); } } else { showMode(-1); fillDate(); } viewUpdate('YYYY'); }, selectDay: function (e) { var day = viewDate.clone(); if ($(e.target).is('.old')) { day.subtract(1, 'M'); } if ($(e.target).is('.new')) { day.add(1, 'M'); } setValue(day.date(parseInt($(e.target).text(), 10))); if (!hasTime() && !options.keepOpen && !options.inline) { hide(); } }, incrementHours: function () { var newDate = date.clone().add(1, 'h'); if (isValid(newDate, 'h')) { setValue(newDate); } }, incrementMinutes: function () { var newDate = date.clone().add(options.stepping, 'm'); if (isValid(newDate, 'm')) { setValue(newDate); } }, incrementSeconds: function () { var newDate = date.clone().add(1, 's'); if (isValid(newDate, 's')) { setValue(newDate); } }, decrementHours: function () { var newDate = date.clone().subtract(1, 'h'); if (isValid(newDate, 'h')) { setValue(newDate); } }, decrementMinutes: function () { var newDate = date.clone().subtract(options.stepping, 'm'); if (isValid(newDate, 'm')) { setValue(newDate); } }, decrementSeconds: function () { var newDate = date.clone().subtract(1, 's'); if (isValid(newDate, 's')) { setValue(newDate); } }, togglePeriod: function () { setValue(date.clone().add((date.hours() >= 12) ? -12 : 12, 'h')); }, togglePicker: function (e) { var $this = $(e.target), $parent = $this.closest('ul'), expanded = $parent.find('.in'), closed = $parent.find('.collapse:not(.in)'), collapseData; if (expanded && expanded.length) { collapseData = expanded.data('collapse'); if (collapseData && collapseData.transitioning) { return; } if (expanded.collapse) { // if collapse plugin is available through bootstrap.js then use it expanded.collapse('hide'); closed.collapse('show'); } else { // otherwise just toggle in class on the two views expanded.removeClass('in'); closed.addClass('in'); } if ($this.is('span')) { $this.toggleClass(options.icons.time + ' ' + options.icons.date); } else { $this.find('span').toggleClass(options.icons.time + ' ' + options.icons.date); } // NOTE: uncomment if toggled state will be restored in show() //if (component) { // component.find('span').toggleClass(options.icons.time + ' ' + options.icons.date); //} } }, showPicker: function () { widget.find('.timepicker > div:not(.timepicker-picker)').hide(); widget.find('.timepicker .timepicker-picker').show(); }, showHours: function () { widget.find('.timepicker .timepicker-picker').hide(); widget.find('.timepicker .timepicker-hours').show(); }, showMinutes: function () { widget.find