From ad6fd5f838e174cc6e354bb9998e1e5bc4e8d4f1 Mon Sep 17 00:00:00 2001 From: fwi-odoo Date: Tue, 22 Nov 2016 13:55:03 +0100 Subject: [PATCH] [REF] web: update bootstrap-datetimepicker lib and adapt datepicker widget from 3.0.3 to 4.17.42 --- .../css/bootstrap-datetimepicker.css | 234 -- .../src/js/bootstrap-datetimepicker.js | 3704 +++++++++++------ .../src/less/_bootstrap-datetimepicker.less | 352 ++ .../less/bootstrap-datetimepicker-build.less | 5 - .../src/less/bootstrap-datetimepicker.less | 290 -- .../web/static/src/js/widgets/date_picker.js | 24 +- addons/web/static/src/less/datepicker.less | 15 + addons/web/static/src/xml/base.xml | 16 +- addons/web/views/webclient_templates.xml | 5 +- 9 files changed, 2874 insertions(+), 1771 deletions(-) delete mode 100644 addons/web/static/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css create mode 100644 addons/web/static/lib/bootstrap-datetimepicker/src/less/_bootstrap-datetimepicker.less delete mode 100644 addons/web/static/lib/bootstrap-datetimepicker/src/less/bootstrap-datetimepicker-build.less delete mode 100755 addons/web/static/lib/bootstrap-datetimepicker/src/less/bootstrap-datetimepicker.less diff --git a/addons/web/static/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css b/addons/web/static/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css deleted file mode 100644 index 69ca89a2e5e..00000000000 --- a/addons/web/static/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css +++ /dev/null @@ -1,234 +0,0 @@ -/*! - * Datetimepicker for Bootstrap v3 -//! version : 3.0.3 - * https://github.com/Eonasdan/bootstrap-datetimepicker/ - */ -.bootstrap-datetimepicker-widget { - top: 0; - left: 0; - width: 250px; - padding: 4px; - margin-top: 1px; - z-index: 99999 !important; - border-radius: 4px; -} -.bootstrap-datetimepicker-widget.timepicker-sbs { - width: 600px; -} -.bootstrap-datetimepicker-widget.bottom:before { - content: ''; - display: inline-block; - border-left: 7px solid transparent; - border-right: 7px solid transparent; - border-bottom: 7px solid #ccc; - border-bottom-color: rgba(0, 0, 0, 0.2); - position: absolute; - top: -7px; - left: 7px; -} -.bootstrap-datetimepicker-widget.bottom:after { - content: ''; - display: inline-block; - border-left: 6px solid transparent; - border-right: 6px solid transparent; - border-bottom: 6px solid white; - position: absolute; - top: -6px; - left: 8px; -} -.bootstrap-datetimepicker-widget.top:before { - content: ''; - display: inline-block; - border-left: 7px solid transparent; - border-right: 7px solid transparent; - border-top: 7px solid #ccc; - border-top-color: rgba(0, 0, 0, 0.2); - position: absolute; - bottom: -7px; - left: 6px; -} -.bootstrap-datetimepicker-widget.top:after { - content: ''; - display: inline-block; - border-left: 6px solid transparent; - border-right: 6px solid transparent; - border-top: 6px solid white; - position: absolute; - bottom: -6px; - left: 7px; -} -.bootstrap-datetimepicker-widget .dow { - width: 14.2857%; -} -.bootstrap-datetimepicker-widget.pull-right:before { - left: auto; - right: 6px; -} -.bootstrap-datetimepicker-widget.pull-right:after { - left: auto; - right: 7px; -} -.bootstrap-datetimepicker-widget > ul { - list-style-type: none; - margin: 0; -} -.bootstrap-datetimepicker-widget a[data-action] { - padding: 6px 0; -} -.bootstrap-datetimepicker-widget .timepicker-hour, -.bootstrap-datetimepicker-widget .timepicker-minute, -.bootstrap-datetimepicker-widget .timepicker-second { - width: 54px; - font-weight: bold; - font-size: 1.2em; - margin: 0; -} -.bootstrap-datetimepicker-widget button[data-action] { - padding: 6px; -} -.bootstrap-datetimepicker-widget table[data-hour-format="12"] .separator { - width: 4px; - padding: 0; - margin: 0; -} -.bootstrap-datetimepicker-widget .datepicker > div { - display: none; -} -.bootstrap-datetimepicker-widget .picker-switch { - text-align: center; -} -.bootstrap-datetimepicker-widget table { - width: 100%; - margin: 0; -} -.bootstrap-datetimepicker-widget td, -.bootstrap-datetimepicker-widget th { - text-align: center; - border-radius: 4px; -} -.bootstrap-datetimepicker-widget td { - height: 54px; - line-height: 54px; - width: 54px; -} -.bootstrap-datetimepicker-widget td.cw { - font-size: 10px; - height: 20px; - line-height: 20px; -} -.bootstrap-datetimepicker-widget td.day { - height: 20px; - line-height: 20px; - width: 20px; -} -.bootstrap-datetimepicker-widget td.day:hover, -.bootstrap-datetimepicker-widget td.hour:hover, -.bootstrap-datetimepicker-widget td.minute:hover, -.bootstrap-datetimepicker-widget td.second:hover { - background: #eeeeee; - cursor: pointer; -} -.bootstrap-datetimepicker-widget td.old, -.bootstrap-datetimepicker-widget td.new { - color: #777777; -} -.bootstrap-datetimepicker-widget td.today { - position: relative; -} -.bootstrap-datetimepicker-widget td.today:before { - content: ''; - display: inline-block; - border-left: 7px solid transparent; - border-bottom: 7px solid #428bca; - border-top-color: rgba(0, 0, 0, 0.2); - position: absolute; - bottom: 4px; - right: 4px; -} -.bootstrap-datetimepicker-widget td.active, -.bootstrap-datetimepicker-widget td.active:hover { - background-color: #428bca; - color: #fff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); -} -.bootstrap-datetimepicker-widget td.active.today:before { - border-bottom-color: #fff; -} -.bootstrap-datetimepicker-widget td.disabled, -.bootstrap-datetimepicker-widget td.disabled:hover { - background: none; - color: #777777; - cursor: not-allowed; -} -.bootstrap-datetimepicker-widget td span { - display: inline-block; - width: 54px; - height: 54px; - line-height: 54px; - margin: 2px 1.5px; - cursor: pointer; - border-radius: 4px; -} -.bootstrap-datetimepicker-widget td span:hover { - background: #eeeeee; -} -.bootstrap-datetimepicker-widget td span.active { - background-color: #428bca; - color: #fff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); -} -.bootstrap-datetimepicker-widget td span.old { - color: #777777; -} -.bootstrap-datetimepicker-widget td span.disabled, -.bootstrap-datetimepicker-widget td span.disabled:hover { - background: none; - color: #777777; - cursor: not-allowed; -} -.bootstrap-datetimepicker-widget th { - height: 20px; - line-height: 20px; - width: 20px; -} -.bootstrap-datetimepicker-widget th.picker-switch { - width: 145px; -} -.bootstrap-datetimepicker-widget th.next, -.bootstrap-datetimepicker-widget th.prev { - font-size: 21px; -} -.bootstrap-datetimepicker-widget th.disabled, -.bootstrap-datetimepicker-widget th.disabled:hover { - background: none; - color: #777777; - cursor: not-allowed; -} -.bootstrap-datetimepicker-widget thead tr:first-child th { - cursor: pointer; -} -.bootstrap-datetimepicker-widget thead tr:first-child th:hover { - background: #eeeeee; -} -.input-group.date .input-group-addon span { - display: block; - cursor: pointer; - width: 16px; - height: 16px; -} -.bootstrap-datetimepicker-widget.left-oriented:before { - left: auto; - right: 6px; -} -.bootstrap-datetimepicker-widget.left-oriented:after { - left: auto; - right: 7px; -} -.bootstrap-datetimepicker-widget ul.list-unstyled li div.timepicker div.timepicker-picker table.table-condensed tbody > tr > td { - padding: 0px !important; -} -@media screen and (max-width: 767px) { - .bootstrap-datetimepicker-widget.timepicker-sbs { - width: 283px; - } -} diff --git a/addons/web/static/lib/bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js b/addons/web/static/lib/bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js index 8619c44a7d6..807bdff70dd 100644 --- a/addons/web/static/lib/bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js +++ b/addons/web/static/lib/bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js @@ -1,1370 +1,2636 @@ +/*! version : 4.17.42 + ========================================================= + bootstrap-datetimejs + https://github.com/Eonasdan/bootstrap-datetimepicker + Copyright (c) 2015 Jonathan Peterson + ========================================================= + */ /* -//! version : 3.0.3 -========================================================= -bootstrap-datetimepicker.js -https://github.com/Eonasdan/bootstrap-datetimepicker -========================================================= -The MIT License (MIT) + The MIT License (MIT) -Copyright (c) 2014 Jonathan Peterson + Copyright (c) 2015 Jonathan Peterson -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -;(function (root, factory) { + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ +/*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 { - // AMD is not used - Attempt to fetch dependencies from scope. - if (!jQuery) { - throw new Error('bootstrap-datetimepicker requires jQuery to be loaded first'); + // Neither AMD nor CommonJS used. Use global variables. + if (typeof jQuery === 'undefined') { + throw 'bootstrap-datetimepicker requires jQuery to be loaded first'; } - if (!moment) { - throw new Error('bootstrap-datetimepicker requires moment.js to be loaded first'); + if (typeof moment === 'undefined') { + throw 'bootstrap-datetimepicker requires Moment.js to be loaded first'; } - factory(root.jQuery, moment); + factory(jQuery, moment); } -}(this, function ($, moment) { +}(function ($, moment) { 'use strict'; - if (typeof moment === 'undefined') { - throw new Error('momentjs is required'); + if (!moment) { + throw new Error('bootstrap-datetimepicker requires Moment.js to be loaded first'); } - var dpgId = 0, + 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 = {}, - DateTimePicker = function (element, options) { - var defaults = $.fn.datetimepicker.defaults, + /******************************************************************************** + * + * Private functions + * + ********************************************************************************/ - icons = { - time: 'glyphicon glyphicon-time', - date: 'glyphicon glyphicon-calendar', - up: 'glyphicon glyphicon-chevron-up', - down: 'glyphicon glyphicon-chevron-down' + hasTimeZone = function () { + return moment.tz !== undefined && options.timeZone !== undefined && options.timeZone !== null && options.timeZone !== ''; }, - picker = this, + getMoment = function (d) { + var returnMoment; - dDate, - - init = function () { - var icon = false, localeData, rInterval; - picker.options = $.extend({}, defaults, options); - picker.options.icons = $.extend({}, icons, picker.options.icons); - - picker.element = $(element); - - dataToOptions(); - - if (!(picker.options.pickTime || picker.options.pickDate)) { - throw new Error('Must choose at least one picker'); - } - - picker.id = dpgId++; - moment.locale(picker.options.language); - picker.date = moment(); - picker.unset = false; - picker.isInput = picker.element.is('input'); - picker.component = false; - - if (picker.element.hasClass('input-group')) { - if (picker.element.find('.datepickerbutton').size() === 0) {//in case there is more then one 'input-group-addon' Issue #48 - picker.component = picker.element.find('[class^="input-group-"]'); - } - else { - picker.component = picker.element.find('.datepickerbutton'); - } - } - picker.format = picker.options.format; - - localeData = moment().localeData(); - - if (!picker.format) { - picker.format = (picker.options.pickDate ? localeData.longDateFormat('L') : ''); - if (picker.options.pickDate && picker.options.pickTime) { - picker.format += ' '; - } - picker.format += (picker.options.pickTime ? localeData.longDateFormat('LT') : ''); - if (picker.options.useSeconds) { - if (localeData.longDateFormat('LT').indexOf(' A') !== -1) { - picker.format = picker.format.split(' A')[0] + ':ss A'; - } - else { - picker.format += ':ss'; - } - } - } - picker.use24hours = picker.format.toLowerCase().indexOf('a') < 1; - - if (picker.component) { - icon = picker.component.find('span'); - } - - if (picker.options.pickTime) { - if (icon) { - icon.addClass(picker.options.icons.time); - } - } - if (picker.options.pickDate) { - if (icon) { - icon.removeClass(picker.options.icons.time); - icon.addClass(picker.options.icons.date); - } - } - - picker.options.widgetParent = - typeof picker.options.widgetParent === 'string' && picker.options.widgetParent || - picker.element.parents().filter(function () { - return 'scroll' === $(this).css('overflow-y'); - }).get(0) || - 'body'; - - picker.widget = $(getTemplate()).appendTo(picker.options.widgetParent); - - picker.minViewMode = picker.options.minViewMode || 0; - if (typeof picker.minViewMode === 'string') { - switch (picker.minViewMode) { - case 'months': - picker.minViewMode = 1; - break; - case 'years': - picker.minViewMode = 2; - break; - default: - picker.minViewMode = 0; - break; - } - } - picker.viewMode = picker.options.viewMode || 0; - if (typeof picker.viewMode === 'string') { - switch (picker.viewMode) { - case 'months': - picker.viewMode = 1; - break; - case 'years': - picker.viewMode = 2; - break; - default: - picker.viewMode = 0; - break; - } - } - - picker.viewMode = Math.max(picker.viewMode, picker.minViewMode); - - picker.options.disabledDates = indexGivenDates(picker.options.disabledDates); - picker.options.enabledDates = indexGivenDates(picker.options.enabledDates); - - picker.startViewMode = picker.viewMode; - picker.setMinDate(picker.options.minDate); - picker.setMaxDate(picker.options.maxDate); - fillDow(); - fillMonths(); - fillHours(); - fillMinutes(); - fillSeconds(); - update(); - showMode(); - attachDatePickerEvents(); - if (picker.options.defaultDate !== '' && getPickerInput().val() === '') { - picker.setValue(picker.options.defaultDate); - } - if (picker.options.minuteStepping !== 1) { - rInterval = picker.options.minuteStepping; - picker.date.minutes((Math.round(picker.date.minutes() / rInterval) * rInterval) % 60).seconds(0); - } - }, - - getPickerInput = function () { - var input; - - if (picker.isInput) { - return picker.element; - } - input = picker.element.find('.datepickerinput'); - if (input.size() === 0) { - input = picker.element.find('input'); - } - else if (!input.is('input')) { - throw new Error('CSS class "datepickerinput" cannot be applied to non input element'); - } - return input; - }, - - dataToOptions = function () { - var eData; - if (picker.element.is('input')) { - eData = picker.element.data(); - } - else { - eData = picker.element.find('input').data(); - } - if (eData.dateFormat !== undefined) { - picker.options.format = eData.dateFormat; - } - if (eData.datePickdate !== undefined) { - picker.options.pickDate = eData.datePickdate; - } - if (eData.datePicktime !== undefined) { - picker.options.pickTime = eData.datePicktime; - } - if (eData.dateUseminutes !== undefined) { - picker.options.useMinutes = eData.dateUseminutes; - } - if (eData.dateUseseconds !== undefined) { - picker.options.useSeconds = eData.dateUseseconds; - } - if (eData.dateUsecurrent !== undefined) { - picker.options.useCurrent = eData.dateUsecurrent; - } - if (eData.calendarWeeks !== undefined) { - picker.options.calendarWeeks = eData.calendarWeeks; - } - if (eData.dateMinutestepping !== undefined) { - picker.options.minuteStepping = eData.dateMinutestepping; - } - if (eData.dateMindate !== undefined) { - picker.options.minDate = eData.dateMindate; - } - if (eData.dateMaxdate !== undefined) { - picker.options.maxDate = eData.dateMaxdate; - } - if (eData.dateShowtoday !== undefined) { - picker.options.showToday = eData.dateShowtoday; - } - if (eData.dateCollapse !== undefined) { - picker.options.collapse = eData.dateCollapse; - } - if (eData.dateLanguage !== undefined) { - picker.options.language = eData.dateLanguage; - } - if (eData.dateDefaultdate !== undefined) { - picker.options.defaultDate = eData.dateDefaultdate; - } - if (eData.dateDisableddates !== undefined) { - picker.options.disabledDates = eData.dateDisableddates; - } - if (eData.dateEnableddates !== undefined) { - picker.options.enabledDates = eData.dateEnableddates; - } - if (eData.dateIcons !== undefined) { - picker.options.icons = eData.dateIcons; - } - if (eData.dateUsestrict !== undefined) { - picker.options.useStrict = eData.dateUsestrict; - } - if (eData.dateDirection !== undefined) { - picker.options.direction = eData.dateDirection; - } - if (eData.dateSidebyside !== undefined) { - picker.options.sideBySide = eData.dateSidebyside; - } - if (eData.dateDaysofweekdisabled !== undefined) { - picker.options.daysOfWeekDisabled = eData.dateDaysofweekdisabled; - } -// $.each(defaults, function (key) { -// var attributeName = 'date' + key.charAt(0).toUpperCase() + key.slice(1); -// if (eData[attributeName] !== undefined) { -// picker.options[key] = eData[attributeName]; -// } -// }); - }, - - place = function () { - var position = 'absolute', - offset = picker.component ? picker.component.offset() : picker.element.offset(), - $window = $(window), - placePosition; - - picker.width = picker.component ? picker.component.outerWidth() : picker.element.outerWidth(); - offset.top = offset.top + picker.element.outerHeight(); - - if (picker.options.direction === 'up') { - placePosition = 'top'; - } else if (picker.options.direction === 'bottom') { - placePosition = 'bottom'; - } else if (picker.options.direction === 'auto') { - if (offset.top + picker.widget.height() > $window.height() + $window.scrollTop() && picker.widget.height() + picker.element.outerHeight() < offset.top) { - placePosition = 'top'; + 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 { - placePosition = 'bottom'; + returnMoment = moment(d, parseFormats, options.useStrict); } - } - if (placePosition === 'top') { - offset.top -= picker.widget.height() + picker.element.outerHeight() + 15; - picker.widget.addClass('top').removeClass('bottom'); - } else { - offset.top += 1; - picker.widget.addClass('bottom').removeClass('top'); - } - if (picker.options.width !== undefined) { - picker.widget.width(picker.options.width); - } - - if (picker.options.orientation === 'left') { - picker.widget.addClass('left-oriented'); - offset.left = offset.left - picker.widget.width() + 20; - } - - if (isInFixed()) { - position = 'fixed'; - offset.top -= $window.scrollTop(); - offset.left -= $window.scrollLeft(); - } - - if ($window.width() < offset.left + picker.widget.outerWidth()) { - offset.right = $window.width() - offset.left - picker.width; - offset.left = 'auto'; - picker.widget.addClass('pull-right'); - } else { - offset.right = 'auto'; - picker.widget.removeClass('pull-right'); - } - - picker.widget.css({ - position: position, - top: offset.top, - left: offset.left, - right: offset.right - }); - }, - - notifyChange = function (oldDate, eventType) { - if (moment(picker.date).isSame(moment(oldDate))) { - return; - } - picker.element.trigger({ - type: 'dp.change', - date: moment(picker.date), - oldDate: moment(oldDate) - }); - - if (eventType !== 'change') { - picker.element.change(); - } - }, - - notifyError = function (date) { - picker.element.trigger({ - type: 'dp.error', - date: moment(date, picker.format, picker.options.useStrict) - }); - }, - - update = function (newDate) { - moment.locale(picker.options.language); - var dateStr = newDate; - if (!dateStr) { - dateStr = getPickerInput().val(); - if (dateStr) { - picker.date = moment(dateStr, picker.format, picker.options.useStrict); + if (hasTimeZone()) { + returnMoment.tz(options.timeZone); } - if (!picker.date) { - picker.date = moment(); - } - } - picker.viewDate = moment(picker.date).startOf('month'); - fillDate(); - fillTime(); - }, - fillDow = function () { - moment.locale(picker.options.language); - var html = $(''), weekdaysMin = moment.weekdaysMin(), i; - if (picker.options.calendarWeeks === true) { - html.append('#'); - } - if (moment().localeData()._week.dow === 0) { // starts on Sunday - for (i = 0; i < 7; i++) { - html.append('' + weekdaysMin[i] + ''); + return returnMoment; + }, + + isEnabled = function (granularity) { + if (typeof granularity !== 'string' || granularity.length > 1) { + throw new TypeError('isEnabled expects a single character string parameter'); } - } else { - for (i = 1; i < 8; i++) { - if (i === 7) { - html.append('' + weekdaysMin[0] + ''); + 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 = $('') + .append($('') + .append($('').addClass('prev').attr('data-action', 'previous') + .append($('').addClass(options.icons.previous)) + ) + .append($('').addClass('picker-switch').attr('data-action', 'pickerSwitch').attr('colspan', (options.calendarWeeks ? '6' : '5'))) + .append($('').addClass('next').attr('data-action', 'next') + .append($('').addClass(options.icons.next)) + ) + ), + contTemplate = $('') + .append($('') + .append($('').attr('colspan', (options.calendarWeeks ? '8' : '7'))) + ); + + return [ + $('
').addClass('datepicker-days') + .append($('').addClass('table-condensed') + .append(headTemplate) + .append($('')) + ), + $('
').addClass('datepicker-months') + .append($('
').addClass('table-condensed') + .append(headTemplate.clone()) + .append(contTemplate.clone()) + ), + $('
').addClass('datepicker-years') + .append($('
').addClass('table-condensed') + .append(headTemplate.clone()) + .append(contTemplate.clone()) + ), + $('
').addClass('datepicker-decades') + .append($('
').addClass('table-condensed') + .append(headTemplate.clone()) + .append(contTemplate.clone()) + ) + ]; + }, + + getTimePickerMainTemplate = function () { + var topRow = $(''), + middleRow = $(''), + bottomRow = $(''); + + if (isEnabled('h')) { + topRow.append($('
') + .append($('').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementHour }).addClass('btn').attr('data-action', 'incrementHours').append($('').addClass(options.icons.up)))); + middleRow.append($('') + .append($('').addClass('timepicker-hour').attr({ 'data-time-component': 'hours', 'title': options.tooltips.pickHour }).attr('data-action', 'showHours'))); + bottomRow.append($('') + .append($('').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementHour }).addClass('btn').attr('data-action', 'decrementHours').append($('').addClass(options.icons.down)))); + } + if (isEnabled('m')) { + if (isEnabled('h')) { + topRow.append($('').addClass('separator')); + middleRow.append($('').addClass('separator').html(':')); + bottomRow.append($('').addClass('separator')); + } + topRow.append($('') + .append($('').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementMinute }).addClass('btn').attr('data-action', 'incrementMinutes') + .append($('').addClass(options.icons.up)))); + middleRow.append($('') + .append($('').addClass('timepicker-minute').attr({ 'data-time-component': 'minutes', 'title': options.tooltips.pickMinute }).attr('data-action', 'showMinutes'))); + bottomRow.append($('') + .append($('').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementMinute }).addClass('btn').attr('data-action', 'decrementMinutes') + .append($('').addClass(options.icons.down)))); + } + if (isEnabled('s')) { + if (isEnabled('m')) { + topRow.append($('').addClass('separator')); + middleRow.append($('').addClass('separator').html(':')); + bottomRow.append($('').addClass('separator')); + } + topRow.append($('') + .append($('').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementSecond }).addClass('btn').attr('data-action', 'incrementSeconds') + .append($('').addClass(options.icons.up)))); + middleRow.append($('') + .append($('').addClass('timepicker-second').attr({ 'data-time-component': 'seconds', 'title': options.tooltips.pickSecond }).attr('data-action', 'showSeconds'))); + bottomRow.append($('') + .append($('').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementSecond }).addClass('btn').attr('data-action', 'decrementSeconds') + .append($('').addClass(options.icons.down)))); + } + + if (!use24Hours) { + topRow.append($('').addClass('separator')); + middleRow.append($('') + .append($('').addClass('separator')); + } + + return $('
').addClass('timepicker-picker') + .append($('').addClass('table-condensed') + .append([topRow, middleRow, bottomRow])); + }, + + getTimePickerTemplate = function () { + var hoursView = $('
').addClass('timepicker-hours') + .append($('
').addClass('table-condensed')), + minutesView = $('
').addClass('timepicker-minutes') + .append($('
').addClass('table-condensed')), + secondsView = $('
').addClass('timepicker-seconds') + .append($('
').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($('
').append($('').attr({ 'data-action': 'today', 'title': options.tooltips.today }).append($('').addClass(options.icons.today)))); + } + if (!options.sideBySide && hasDate() && hasTime()) { + row.push($('').append($('').attr({ 'data-action': 'togglePicker', 'title': options.tooltips.selectTime }).append($('').addClass(options.icons.time)))); + } + if (options.showClear) { + row.push($('').append($('').attr({ 'data-action': 'clear', 'title': options.tooltips.clear }).append($('').addClass(options.icons.clear)))); + } + if (options.showClose) { + row.push($('').append($('').attr({ 'data-action': 'close', 'title': options.tooltips.close }).append($('').addClass(options.icons.close)))); + } + return $('').addClass('table-condensed').append($('').append($('').append(row))); + }, + + getTemplate = function () { + var template = $('
').addClass('bootstrap-datetimepicker-widget dropdown-menu'), + dateView = $('
').addClass('datepicker').append(getDatePickerTemplate()), + timeView = $('
').addClass('timepicker').append(getTimePickerTemplate()), + content = $('
    ').addClass('list-unstyled'), + toolbar = $('
  • ').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( + $('
    ').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($('
  • ').addClass((options.collapse && hasTime() ? 'collapse in' : '')).append(dateView)); + } + if (options.toolbarPlacement === 'default') { + content.append(toolbar); + } + if (hasTime()) { + content.append($('
  • ').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 { - html.append('
'); + vertical = 'bottom'; } } - } - picker.widget.find('.datepicker-days thead').append(html); - }, - fillMonths = function () { - moment.locale(picker.options.language); - var html = '', i, monthsShort = moment.monthsShort(); - for (i = 0; i < 12; i++) { - html += '' + monthsShort[i] + ''; - } - picker.widget.find('.datepicker-months td').append(html); - }, + // 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'; + } + } - fillDate = function () { - if (!picker.options.pickDate) { - return; - } - moment.locale(picker.options.language); - var year = picker.viewDate.year(), - month = picker.viewDate.month(), - startYear = picker.options.minDate.year(), - startMonth = picker.options.minDate.month(), - endYear = picker.options.maxDate.year(), - endMonth = picker.options.maxDate.month(), - currentDate, - prevMonth, nextMonth, html = [], row, clsName, i, days, yearCont, currentYear, months = moment.months(); + if (vertical === 'top') { + widget.addClass('top').removeClass('bottom'); + } else { + widget.addClass('bottom').removeClass('top'); + } - picker.widget.find('.datepicker-days').find('.disabled').removeClass('disabled'); - picker.widget.find('.datepicker-months').find('.disabled').removeClass('disabled'); - picker.widget.find('.datepicker-years').find('.disabled').removeClass('disabled'); + if (horizontal === 'right') { + widget.addClass('pull-right'); + } else { + widget.removeClass('pull-right'); + } - picker.widget.find('.datepicker-days th:eq(1)').text( - months[month] + ' ' + year); + // find the first parent element that has a relative css positioning + if (parent.css('position') !== 'relative') { + parent = parent.parents().filter(function () { + return $(this).css('position') === 'relative'; + }).first(); + } - prevMonth = moment(picker.viewDate, picker.format, picker.options.useStrict).subtract(1, 'months'); - days = prevMonth.daysInMonth(); - prevMonth.date(days).startOf('week'); - if ((year === startYear && month <= startMonth) || year < startYear) { - picker.widget.find('.datepicker-days th:eq(0)').addClass('disabled'); - } - if ((year === endYear && month >= endMonth) || year > endYear) { - picker.widget.find('.datepicker-days th:eq(2)').addClass('disabled'); - } + if (parent.length === 0) { + throw new Error('datetimepicker component should be placed within a relative positioned container'); + } - nextMonth = moment(prevMonth).add(42, 'd'); - while (prevMonth.isBefore(nextMonth)) { - if (prevMonth.weekday() === moment().startOf('week').weekday()) { + 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 = $(''), + currentDate = viewDate.clone().startOf('w').startOf('d'); + + if (options.calendarWeeks === true) { + row.append($(''); + if (options.calendarWeeks) { + row.append(''); + } + 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(''); + 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 = $(''); - html.push(row); - if (picker.options.calendarWeeks === true) { - row.append(''); - } - } - clsName = ''; - if (prevMonth.year() < year || (prevMonth.year() === year && prevMonth.month() < month)) { - clsName += ' old'; - } else if (prevMonth.year() > year || (prevMonth.year() === year && prevMonth.month() > month)) { - clsName += ' new'; - } - if (prevMonth.isSame(moment({y: picker.date.year(), M: picker.date.month(), d: picker.date.date()}))) { - clsName += ' active'; - } - if (isInDisableDates(prevMonth, 'day') || !isInEnableDates(prevMonth)) { - clsName += ' disabled'; - } - if (picker.options.showToday === true) { - if (prevMonth.isSame(moment(), 'day')) { - clsName += ' today'; - } - } - if (picker.options.daysOfWeekDisabled) { - for (i = 0; i < picker.options.daysOfWeekDisabled.length; i++) { - if (prevMonth.day() === picker.options.daysOfWeekDisabled[i]) { - clsName += ' disabled'; - break; - } - } - } - row.append(''); - currentDate = prevMonth.date(); - prevMonth.add(1, 'd'); - - if (currentDate === prevMonth.date()) { - prevMonth.add(1, 'd'); + if (viewDate.hour() > 11 && !use24Hours) { + currentHour.hour(12); } - } - picker.widget.find('.datepicker-days tbody').empty().append(html); - currentYear = picker.date.year(); - months = picker.widget.find('.datepicker-months').find('th:eq(1)').text(year).end().find('span').removeClass('active'); - if (currentYear === year) { - months.eq(picker.date.month()).addClass('active'); - } - if (year - 1 < startYear) { - picker.widget.find('.datepicker-months th:eq(0)').addClass('disabled'); - } - if (year + 1 > endYear) { - picker.widget.find('.datepicker-months th:eq(2)').addClass('disabled'); - } - for (i = 0; i < 12; i++) { - if ((year === startYear && startMonth > i) || (year < startYear)) { - $(months[i]).addClass('disabled'); - } else if ((year === endYear && endMonth < i) || (year > endYear)) { - $(months[i]).addClass('disabled'); - } - } - - html = ''; - year = parseInt(year / 10, 10) * 10; - yearCont = picker.widget.find('.datepicker-years').find( - 'th:eq(1)').text(year + '-' + (year + 9)).parents('table').find('td'); - picker.widget.find('.datepicker-years').find('th').removeClass('disabled'); - if (startYear > year) { - picker.widget.find('.datepicker-years').find('th:eq(0)').addClass('disabled'); - } - if (endYear < year + 9) { - picker.widget.find('.datepicker-years').find('th:eq(2)').addClass('disabled'); - } - year -= 1; - for (i = -1; i < 11; i++) { - html += '' + year + ''; - year += 1; - } - yearCont.html(html); - }, - - fillHours = function () { - moment.locale(picker.options.language); - var table = picker.widget.find('.timepicker .timepicker-hours table'), html = '', current, i, j; - table.parent().hide(); - if (picker.use24hours) { - current = 0; - for (i = 0; i < 6; i += 1) { - html += ''; - for (j = 0; j < 4; j += 1) { - html += ''; - current++; + while (currentHour.isSame(viewDate, 'd') && (use24Hours || (viewDate.hour() < 12 && currentHour.hour() < 12) || viewDate.hour() > 11)) { + if (currentHour.hour() % 4 === 0) { + row = $(''); + html.push(row); } - html += ''; + row.append(''); + currentHour.add(1, 'h'); } - } - else { - current = 1; - for (i = 0; i < 3; i += 1) { - html += ''; - for (j = 0; j < 4; j += 1) { - html += ''; - current++; - } - html += ''; - } - } - table.html(html); - }, + table.empty().append(html); + }, - fillMinutes = function () { - var table = picker.widget.find('.timepicker .timepicker-minutes table'), html = '', current = 0, i, j, step = picker.options.minuteStepping; - table.parent().hide(); - if (step === 1) { - step = 5; - } - for (i = 0; i < Math.ceil(60 / step / 4) ; i++) { - html += ''; - for (j = 0; j < 4; j += 1) { - if (current < 60) { - html += ''; - current += step; + fillMinutes = function () { + var table = widget.find('.timepicker-minutes table'), + currentMinute = viewDate.clone().startOf('h'), + html = [], + row = $(''), + step = options.stepping === 1 ? 5 : options.stepping; + + while (viewDate.isSame(currentMinute, 'h')) { + if (currentMinute.minute() % (step * 4) === 0) { + row = $(''); + html.push(row); + } + row.append(''); + currentMinute.add(step, 'm'); + } + table.empty().append(html); + }, + + fillSeconds = function () { + var table = widget.find('.timepicker-seconds table'), + currentSecond = viewDate.clone().startOf('m'), + html = [], + row = $(''); + + while (viewDate.isSame(currentSecond, 'm')) { + if (currentSecond.second() % 20 === 0) { + row = $(''); + html.push(row); + } + row.append(''); + 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 { - html += ''; + toggle.addClass('disabled'); } } - html += ''; - } - table.html(html); - }, + 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')); - fillSeconds = function () { - var table = picker.widget.find('.timepicker .timepicker-seconds table'), html = '', current = 0, i, j; - table.parent().hide(); - for (i = 0; i < 3; i++) { - html += ''; - for (j = 0; j < 4; j += 1) { - html += ''; - current += 5; + fillHours(); + fillMinutes(); + fillSeconds(); + }, + + update = function () { + if (!widget) { + return; } - html += ''; - } - table.html(html); - }, + fillDate(); + fillTime(); + }, - fillTime = function () { - if (!picker.date) { - return; - } - var timeComponents = picker.widget.find('.timepicker span[data-time-component]'), - hour = picker.date.hours(), - period = picker.date.format('A'); - if (!picker.use24hours) { - if (hour === 0) { - hour = 12; - } else if (hour !== 12) { - hour = hour % 12; + 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; } - picker.widget.find('.timepicker [data-action=togglePeriod]').text(period); - } - timeComponents.filter('[data-time-component=hours]').text(padLeft(hour)); - timeComponents.filter('[data-time-component=minutes]').text(padLeft(picker.date.minutes())); - timeComponents.filter('[data-time-component=seconds]').text(padLeft(picker.date.second())); - }, - click = function (e) { - e.stopPropagation(); - e.preventDefault(); - picker.unset = false; - var target = $(e.target).closest('span, td, th'), month, year, step, day, oldDate = moment(picker.date); - if (target.length === 1) { - if (!target.is('.disabled')) { - switch (target[0].nodeName.toLowerCase()) { - case 'th': - switch (target[0].className) { - case 'picker-switch': - showMode(1); - break; - case 'prev': - case 'next': - step = dpGlobal.modes[picker.viewMode].navStep; - if (target[0].className === 'prev') { - step = step * -1; - } - picker.viewDate.add(step, dpGlobal.modes[picker.viewMode].navFnc); - fillDate(); - break; - } - break; - case 'span': - if (target.is('.month')) { - month = target.parent().find('span').index(target); - picker.viewDate.month(month); - } else { - year = parseInt(target.text(), 10) || 0; - picker.viewDate.year(year); - } - if (picker.viewMode === picker.minViewMode) { - picker.date = moment({ - y: picker.viewDate.year(), - M: picker.viewDate.month(), - d: picker.viewDate.date(), - h: picker.date.hours(), - m: picker.date.minutes(), - s: picker.date.seconds() - }); - set(); - notifyChange(oldDate, e.type); - } - showMode(-1); - fillDate(); - break; - case 'td': - if (target.is('.day')) { - day = parseInt(target.text(), 10) || 1; - month = picker.viewDate.month(); - year = picker.viewDate.year(); - if (target.is('.old')) { - if (month === 0) { - month = 11; - year -= 1; - } else { - month -= 1; - } - } else if (target.is('.new')) { - if (month === 11) { - month = 0; - year += 1; - } else { - month += 1; - } - } - picker.date = moment({ - y: year, - M: month, - d: day, - h: picker.date.hours(), - m: picker.date.minutes(), - s: picker.date.seconds() - } - ); - picker.viewDate = moment({ - y: year, M: month, d: Math.min(28, day) - }); - fillDate(); - set(); - notifyChange(oldDate, e.type); - } - break; + 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'); } } - } - }, - actions = { - incrementHours: function () { - checkDate('add', 'hours', 1); - }, - - incrementMinutes: function () { - checkDate('add', 'minutes', picker.options.minuteStepping); - }, - - incrementSeconds: function () { - checkDate('add', 'seconds', 1); - }, - - decrementHours: function () { - checkDate('subtract', 'hours', 1); - }, - - decrementMinutes: function () { - checkDate('subtract', 'minutes', picker.options.minuteStepping); - }, - - decrementSeconds: function () { - checkDate('subtract', 'seconds', 1); - }, - - togglePeriod: function () { - var hour = picker.date.hours(); - if (hour >= 12) { - hour -= 12; + if (isValid(targetMoment)) { + date = targetMoment; + //viewDate = date.clone(); // TODO this doesn't work right on first use + input.val(date.format(actualFormat)); + element.data('date', date.format(actualFormat)); + unset = false; + update(); + notifyEvent({ + type: 'dp.change', + date: date.clone(), + oldDate: oldDate + }); } else { - hour += 12; + 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 + }); } - picker.date.hours(hour); }, - showPicker: function () { - picker.widget.find('.timepicker > div:not(.timepicker-picker)').hide(); - picker.widget.find('.timepicker .timepicker-picker').show(); + /** + * 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; }, - showHours: function () { - picker.widget.find('.timepicker .timepicker-picker').hide(); - picker.widget.find('.timepicker .timepicker-hours').show(); + clear = function () { + setValue(null); }, - showMinutes: function () { - picker.widget.find('.timepicker .timepicker-picker').hide(); - picker.widget.find('.timepicker .timepicker-minutes').show(); + parseInputDate = function (inputDate) { + if (options.parseInputDate === undefined) { + if (!moment.isMoment(inputDate)) { + inputDate = getMoment(inputDate); + } + } else { + inputDate = options.parseInputDate(inputDate); + } + //inputDate.locale(options.locale); + return inputDate; }, - showSeconds: function () { - picker.widget.find('.timepicker .timepicker-picker').hide(); - picker.widget.find('.timepicker .timepicker-seconds').show(); - }, + /******************************************************************************** + * + * Widget UI interaction functions + * + ********************************************************************************/ + actions = { + next: function () { + var navFnc = datePickerModes[currentViewMode].navFnc; + viewDate.add(datePickerModes[currentViewMode].navStep, navFnc); + fillDate(); + viewUpdate(navFnc); + }, - selectHour: function (e) { - var hour = parseInt($(e.target).text(), 10); - if (!picker.use24hours) { - if (picker.date.hours() >= 12) { - if (hour !== 12) { - hour += 12; + 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 { - if (hour === 12) { - hour = 0; - } + showMode(-1); + fillDate(); } - } - picker.date.hours(hour); - actions.showPicker.call(picker); - }, + viewUpdate('M'); + }, - selectMinute: function (e) { - picker.date.minutes(parseInt($(e.target).text(), 10)); - actions.showPicker.call(picker); - }, + 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'); + }, - selectSecond: function (e) { - picker.date.seconds(parseInt($(e.target).text(), 10)); - actions.showPicker.call(picker); - } - }, + 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'); + }, - doAction = function (e) { - var oldDate = moment(picker.date), - action = $(e.currentTarget).data('action'), - rv = actions[action].apply(picker, arguments); - stopEvent(e); - if (!picker.date) { - picker.date = moment({y: 1970}); - } - set(); - fillTime(); - notifyChange(oldDate, e.type); - return rv; - }, + 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(); + } + }, - stopEvent = function (e) { - e.stopPropagation(); - e.preventDefault(); - }, + incrementHours: function () { + var newDate = date.clone().add(1, 'h'); + if (isValid(newDate, 'h')) { + setValue(newDate); + } + }, - keydown = function (e) { - if (e.keyCode === 27) { // allow escape to hide picker - picker.hide(); - } - }, + incrementMinutes: function () { + var newDate = date.clone().add(options.stepping, 'm'); + if (isValid(newDate, 'm')) { + setValue(newDate); + } + }, - change = function (e) { - moment.locale(picker.options.language); - var input = $(e.target), oldDate = moment(picker.date), newDate = moment(input.val(), picker.format, picker.options.useStrict); - if (newDate.isValid() && !isInDisableDates(newDate) && isInEnableDates(newDate)) { - update(); - picker.setValue(newDate); - notifyChange(oldDate, e.type); - set(); - } - else { - picker.viewDate = oldDate; - picker.unset = true; - notifyChange(oldDate, e.type); - notifyError(newDate); - } - }, + incrementSeconds: function () { + var newDate = date.clone().add(1, 's'); + if (isValid(newDate, 's')) { + setValue(newDate); + } + }, - showMode = function (dir) { - if (dir) { - picker.viewMode = Math.max(picker.minViewMode, Math.min(2, picker.viewMode + dir)); - } - picker.widget.find('.datepicker > div').hide().filter('.datepicker-' + dpGlobal.modes[picker.viewMode].clsName).show(); - }, + decrementHours: function () { + var newDate = date.clone().subtract(1, 'h'); + if (isValid(newDate, 'h')) { + setValue(newDate); + } + }, - attachDatePickerEvents = function () { - var $this, $parent, expanded, closed, collapseData; - picker.widget.on('click', '.datepicker *', $.proxy(click, this)); // this handles date picker clicks - picker.widget.on('click', '[data-action]', $.proxy(doAction, this)); // this handles time picker clicks - picker.widget.on('mousedown', $.proxy(stopEvent, this)); - picker.element.on('keydown', $.proxy(keydown, this)); - if (picker.options.pickDate && picker.options.pickTime) { - picker.widget.on('click.togglePicker', '.accordion-toggle', function (e) { - e.stopPropagation(); - $this = $(this); - $parent = $this.closest('ul'); - expanded = $parent.find('.in'); - closed = $parent.find('.collapse:not(.in)'); + 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; } - expanded.collapse('hide'); - closed.collapse('show'); - $this.find('span').toggleClass(picker.options.icons.time + ' ' + picker.options.icons.date); - if (picker.component) { - picker.component.find('span').toggleClass(picker.options.icons.time + ' ' + picker.options.icons.date); + 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('.timepicker .timepicker-picker').hide(); + widget.find('.timepicker .timepicker-minutes').show(); + }, + + showSeconds: function () { + widget.find('.timepicker .timepicker-picker').hide(); + widget.find('.timepicker .timepicker-seconds').show(); + }, + + selectHour: function (e) { + var hour = parseInt($(e.target).text(), 10); + + if (!use24Hours) { + if (date.hours() >= 12) { + if (hour !== 12) { + hour += 12; + } + } else { + if (hour === 12) { + hour = 0; + } } } - }); - } - if (picker.isInput) { - picker.element.on({ - 'click': $.proxy(picker.show, this), - 'focus': $.proxy(picker.show, this), - 'change': $.proxy(change, this), - 'blur': $.proxy(picker.hide, this) - }); - } else { - picker.element.on({ - 'change': $.proxy(change, this) - }, 'input'); - if (picker.component) { - picker.component.on('click', $.proxy(picker.show, this)); - picker.component.on('mousedown', $.proxy(stopEvent, this)); - } else { - picker.element.on('click', $.proxy(picker.show, this)); + setValue(date.clone().hours(hour)); + actions.showPicker.call(picker); + }, + + selectMinute: function (e) { + setValue(date.clone().minutes(parseInt($(e.target).text(), 10))); + actions.showPicker.call(picker); + }, + + selectSecond: function (e) { + setValue(date.clone().seconds(parseInt($(e.target).text(), 10))); + actions.showPicker.call(picker); + }, + + clear: clear, + + today: function () { + var todaysDate = getMoment(); + if (isValid(todaysDate, 'd')) { + setValue(todaysDate); + } + }, + + close: hide + }, + + doAction = function (e) { + if ($(e.currentTarget).is('.disabled')) { + return false; } - } - }, + actions[$(e.currentTarget).data('action')].apply(picker, arguments); + return false; + }, - attachDatePickerGlobalEvents = function () { - $(window).on( - 'resize.datetimepicker' + picker.id, $.proxy(place, this)); - if (!picker.isInput) { - $(document).on( - 'mousedown.datetimepicker' + picker.id, $.proxy(picker.hide, this)); - } - }, + /** + * Shows the widget. Possibly will emit dp.show and dp.change + */ + show = function () { + var currentMoment, + useCurrentGranularity = { + 'year': function (m) { + return m.month(0).date(1).hours(0).seconds(0).minutes(0); + }, + 'month': function (m) { + return m.date(1).hours(0).seconds(0).minutes(0); + }, + 'day': function (m) { + return m.hours(0).seconds(0).minutes(0); + }, + 'hour': function (m) { + return m.seconds(0).minutes(0); + }, + 'minute': function (m) { + return m.seconds(0); + } + }; - detachDatePickerEvents = function () { - picker.widget.off('click', '.datepicker *', picker.click); - picker.widget.off('click', '[data-action]'); - picker.widget.off('mousedown', picker.stopEvent); - if (picker.options.pickDate && picker.options.pickTime) { - picker.widget.off('click.togglePicker'); - } - if (picker.isInput) { - picker.element.off({ - 'focus': picker.show, - 'change': picker.change, - 'click': picker.show, - 'blur' : picker.hide - }); - } else { - picker.element.off({ - 'change': picker.change - }, 'input'); - if (picker.component) { - picker.component.off('click', picker.show); - picker.component.off('mousedown', picker.stopEvent); - } else { - picker.element.off('click', picker.show); + if (input.prop('disabled') || (!options.ignoreReadonly && input.prop('readonly')) || widget) { + return picker; } - } - }, + if (input.val() !== undefined && input.val().trim().length !== 0) { + setValue(parseInputDate(input.val().trim())); + } else if (unset && options.useCurrent && (options.inline || (input.is('input') && input.val().trim().length === 0))) { + currentMoment = getMoment(); + if (typeof options.useCurrent === 'string') { + currentMoment = useCurrentGranularity[options.useCurrent](currentMoment); + } + setValue(currentMoment); + } + widget = getTemplate(); - detachDatePickerGlobalEvents = function () { - $(window).off('resize.datetimepicker' + picker.id); - if (!picker.isInput) { - $(document).off('mousedown.datetimepicker' + picker.id); - } - }, + fillDow(); + fillMonths(); - isInFixed = function () { - if (picker.element) { - var parents = picker.element.parents(), inFixed = false, i; - for (i = 0; i < parents.length; i++) { - if ($(parents[i]).css('position') === 'fixed') { - inFixed = true; - break; + widget.find('.timepicker-hours').hide(); + widget.find('.timepicker-minutes').hide(); + widget.find('.timepicker-seconds').hide(); + + update(); + showMode(); + + $(window).on('resize', place); + widget.on('click', '[data-action]', doAction); // this handles clicks on the widget + widget.on('mousedown', false); + + if (component && component.hasClass('btn')) { + component.toggleClass('active'); + } + place(); + widget.show(); + if (options.focusOnShow && !input.is(':focus')) { + input.focus(); + } + + notifyEvent({ + type: 'dp.show' + }); + return picker; + }, + + /** + * Shows or hides the widget + */ + toggle = function () { + return (widget ? hide() : show()); + }, + + keydown = function (e) { + var handler = null, + index, + index2, + pressedKeys = [], + pressedModifiers = {}, + currentKey = e.which, + keyBindKeys, + allModifiersPressed, + pressed = 'p'; + + keyState[currentKey] = pressed; + + for (index in keyState) { + if (keyState.hasOwnProperty(index) && keyState[index] === pressed) { + pressedKeys.push(index); + if (parseInt(index, 10) !== currentKey) { + pressedModifiers[index] = true; + } } } - return inFixed; - } else { + + for (index in options.keyBinds) { + if (options.keyBinds.hasOwnProperty(index) && typeof (options.keyBinds[index]) === 'function') { + keyBindKeys = index.split(' '); + if (keyBindKeys.length === pressedKeys.length && keyMap[currentKey] === keyBindKeys[keyBindKeys.length - 1]) { + allModifiersPressed = true; + for (index2 = keyBindKeys.length - 2; index2 >= 0; index2--) { + if (!(keyMap[keyBindKeys[index2]] in pressedModifiers)) { + allModifiersPressed = false; + break; + } + } + if (allModifiersPressed) { + handler = options.keyBinds[index]; + break; + } + } + } + } + + if (handler) { + handler.call(picker, widget); + e.stopPropagation(); + e.preventDefault(); + } + }, + + keyup = function (e) { + keyState[e.which] = 'r'; + e.stopPropagation(); + e.preventDefault(); + }, + + change = function (e) { + var val = $(e.target).val().trim(), + parsedDate = val ? parseInputDate(val) : null; + setValue(parsedDate); + e.stopImmediatePropagation(); return false; - } - }, + }, - set = function () { - moment.locale(picker.options.language); - var formatted = ''; - if (!picker.unset) { - formatted = moment(picker.date).format(picker.format); - } - getPickerInput().val(formatted); - picker.element.data('date', formatted); - if (!picker.options.pickTime) { - picker.hide(); - } - }, + attachDatePickerElementEvents = function () { + input.on({ + 'change': change, + 'blur': options.debug ? '' : hide, + 'keydown': keydown, + 'keyup': keyup, + 'focus': options.allowInputToggle ? show : '' + }); - checkDate = function (direction, unit, amount) { - moment.locale(picker.options.language); - var newDate; - if (direction === 'add') { - newDate = moment(picker.date); - if (newDate.hours() === 23) { - newDate.add(amount, unit); + if (element.is('input')) { + input.on({ + 'focus': show + }); + } else if (component) { + component.on('click', toggle); + component.on('mousedown', false); } - newDate.add(amount, unit); - } - else { - newDate = moment(picker.date).subtract(amount, unit); - } - if (isInDisableDates(moment(newDate.subtract(amount, unit))) || isInDisableDates(newDate)) { - notifyError(newDate.format(picker.format)); - return; - } + }, - if (direction === 'add') { - picker.date.add(amount, unit); - } - else { - picker.date.subtract(amount, unit); - } - picker.unset = false; - }, + detachDatePickerElementEvents = function () { + input.off({ + 'change': change, + 'blur': blur, + 'keydown': keydown, + 'keyup': keyup, + 'focus': options.allowInputToggle ? hide : '' + }); - isInDisableDates = function (date, timeUnit) { - moment.locale(picker.options.language); - var maxDate = moment(picker.options.maxDate, picker.format, picker.options.useStrict), - minDate = moment(picker.options.minDate, picker.format, picker.options.useStrict); - - if (timeUnit) { - maxDate = maxDate.endOf(timeUnit); - minDate = minDate.startOf(timeUnit); - } - - if (date.isAfter(maxDate) || date.isBefore(minDate)) { - return true; - } - if (picker.options.disabledDates === false) { - return false; - } - return picker.options.disabledDates[date.format('YYYY-MM-DD')] === true; - }, - isInEnableDates = function (date) { - moment.locale(picker.options.language); - if (picker.options.enabledDates === false) { - return true; - } - return picker.options.enabledDates[date.format('YYYY-MM-DD')] === true; - }, - - indexGivenDates = function (givenDatesArray) { - // Store given enabledDates and disabledDates as keys. - // This way we can check their existence in O(1) time instead of looping through whole array. - // (for example: picker.options.enabledDates['2014-02-27'] === true) - var givenDatesIndexed = {}, givenDatesCount = 0, i; - for (i = 0; i < givenDatesArray.length; i++) { - if (moment.isMoment(givenDatesArray[i]) || givenDatesArray[i] instanceof Date) { - dDate = moment(givenDatesArray[i]); - } else { - dDate = moment(givenDatesArray[i], picker.format, picker.options.useStrict); + if (element.is('input')) { + input.off({ + 'focus': show + }); + } else if (component) { + component.off('click', toggle); + component.off('mousedown', false); } - if (dDate.isValid()) { - givenDatesIndexed[dDate.format('YYYY-MM-DD')] = true; - givenDatesCount++; + }, + + indexGivenDates = function (givenDatesArray) { + // Store given enabledDates and disabledDates as keys. + // This way we can check their existence in O(1) time instead of looping through whole array. + // (for example: options.enabledDates['2014-02-27'] === true) + var givenDatesIndexed = {}; + $.each(givenDatesArray, function () { + var dDate = parseInputDate(this); + if (dDate.isValid()) { + givenDatesIndexed[dDate.format('YYYY-MM-DD')] = true; + } + }); + return (Object.keys(givenDatesIndexed).length) ? givenDatesIndexed : false; + }, + + indexGivenHours = function (givenHoursArray) { + // Store given enabledHours and disabledHours as keys. + // This way we can check their existence in O(1) time instead of looping through whole array. + // (for example: options.enabledHours['2014-02-27'] === true) + var givenHoursIndexed = {}; + $.each(givenHoursArray, function () { + givenHoursIndexed[this] = true; + }); + return (Object.keys(givenHoursIndexed).length) ? givenHoursIndexed : false; + }, + + initFormatting = function () { + var format = options.format || 'L LT'; + + actualFormat = format.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput) { + var newinput = date.localeData().longDateFormat(formatInput) || formatInput; + return newinput.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput2) { //temp fix for #740 + return date.localeData().longDateFormat(formatInput2) || formatInput2; + }); + }); + + + parseFormats = options.extraFormats ? options.extraFormats.slice() : []; + if (parseFormats.indexOf(format) < 0 && parseFormats.indexOf(actualFormat) < 0) { + parseFormats.push(actualFormat); } - } - if (givenDatesCount > 0) { - return givenDatesIndexed; - } - return false; - }, - padLeft = function (string) { - string = string.toString(); - if (string.length >= 2) { - return string; - } - return '0' + string; - }, + use24Hours = (actualFormat.toLowerCase().indexOf('a') < 1 && actualFormat.replace(/\[.*?\]/g, '').indexOf('h') < 1); - getTemplate = function () { - if (picker.options.pickDate && picker.options.pickTime) { - var ret = ''; - ret = ''; - return ret; - } - if (picker.options.pickTime) { - return ( - '' - ); - } - return ( - '' - ); - }, - - dpGlobal = { - modes: [ - { - clsName: 'days', - navFnc: 'month', - navStep: 1 - }, - { - clsName: 'months', - navFnc: 'year', - navStep: 1 - }, - { - clsName: 'years', - navFnc: 'year', - navStep: 10 + if (isEnabled('M')) { + minViewModeNumber = 1; + } + if (isEnabled('d')) { + minViewModeNumber = 0; } - ], - headTemplate: - '' + - '' + - '' + - '' + - '', - contTemplate: - '' - }, - tpGlobal = { - hourTemplate: '', - minuteTemplate: '', - secondTemplate: '' - }; + currentViewMode = Math.max(minViewModeNumber, currentViewMode); - dpGlobal.template = - '
' + - '
' + weekdaysMin[i] + '
').addClass('cw').text('#')); + } + + while (currentDate.isBefore(viewDate.clone().endOf('w'))) { + row.append($('').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($('').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 += '' + startYear.year() + ''; + 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 += '' + (startDecade.year() + 1) + ' - ' + (startDecade.year() + 12) + ''; + startDecade.add(12, 'y'); + } + html += ''; //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 = $('
' + currentDate.week() + '' + currentDate.date() + '
' + prevMonth.week() + '' + prevMonth.date() + '
' + padLeft(current.toString()) + '
' + currentHour.format(use24Hours ? 'HH' : 'hh') + '
' + padLeft(current.toString()) + '
' + padLeft(current.toString()) + '
' + currentMinute.format('mm') + '
' + currentSecond.format('ss') + '
' + padLeft(current.toString()) + '
‹›
' + dpGlobal.headTemplate + '
' + - '' + - '
' + - '' + dpGlobal.headTemplate + dpGlobal.contTemplate + '
' + - '
' + - '
' + - '' + dpGlobal.headTemplate + dpGlobal.contTemplate + '
' + - '
'; - - tpGlobal.getTemplate = function () { - return ( - '
' + - '' + - '' + - '' + - '' + - '' + - (picker.options.useSeconds ? - '' : '') + - (picker.use24hours ? '' : '') + - '' + - '' + - ' ' + - '' + - ' ' + - (picker.options.useSeconds ? - '' : '') + - (picker.use24hours ? '' : '' + - '') + - '' + - '' + - '' + - '' + - '' + - (picker.options.useSeconds ? - '' : '') + - (picker.use24hours ? '' : '') + - '' + - '
' + (picker.options.useMinutes ? '' : '') + '
' + tpGlobal.hourTemplate + ':' + (picker.options.useMinutes ? tpGlobal.minuteTemplate : '00') + ':' + tpGlobal.secondTemplate + '
' + (picker.options.useMinutes ? '' : '') + '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - (picker.options.useSeconds ? - '
' : '') - ); - }; + if (!unset) { + setValue(date); + } + }; + /******************************************************************************** + * + * Public API functions + * ===================== + * + * Important: Do not expose direct references to private objects or the options + * object to the outer world. Always return a clone when returning values or make + * a clone when setting a private variable. + * + ********************************************************************************/ picker.destroy = function () { - detachDatePickerEvents(); - detachDatePickerGlobalEvents(); - picker.widget.remove(); - picker.element.removeData('DateTimePicker'); - if (picker.component) { - picker.component.removeData('DateTimePicker'); - } + ///Destroys the widget and removes all attached event listeners + hide(); + detachDatePickerElementEvents(); + element.removeData('DateTimePicker'); + element.removeData('date'); }; - picker.show = function (e) { - if (picker.options.useCurrent) { - if (getPickerInput().val() === '') { - if (picker.options.minuteStepping !== 1) { - var mDate = moment(), - rInterval = picker.options.minuteStepping; - mDate.minutes((Math.round(mDate.minutes() / rInterval) * rInterval) % 60).seconds(0); - picker.setValue(mDate.format(picker.format)); - } else { - picker.setValue(moment().format(picker.format)); - } - notifyChange('', e.type); - } - } - // if this is a click event on the input field and picker is already open don't hide it - if (e && e.type === 'click' && picker.isInput && picker.widget.hasClass('picker-open')) { - return; - } - if (picker.widget.hasClass('picker-open')) { - picker.widget.hide(); - picker.widget.removeClass('picker-open'); - } - else { - picker.widget.show(); - picker.widget.addClass('picker-open'); - } - picker.height = picker.component ? picker.component.outerHeight() : picker.element.outerHeight(); - place(); - picker.element.trigger({ - type: 'dp.show', - date: moment(picker.date) - }); - attachDatePickerGlobalEvents(); - if (e) { - stopEvent(e); - } - }; + picker.toggle = toggle; + + picker.show = show; + + picker.hide = hide; picker.disable = function () { - var input = picker.element.find('input'); - if (input.prop('disabled')) { - return; + ///Disables the input element, the component is attached to, by adding a disabled="true" attribute to it. + ///If the widget was visible before that call it is hidden. Possibly emits dp.hide + hide(); + if (component && component.hasClass('btn')) { + component.addClass('disabled'); } input.prop('disabled', true); - detachDatePickerEvents(); + return picker; }; picker.enable = function () { - var input = picker.element.find('input'); - if (!input.prop('disabled')) { - return; + ///Enables the input element, the component is attached to, by removing disabled attribute from it. + if (component && component.hasClass('btn')) { + component.removeClass('disabled'); } input.prop('disabled', false); - attachDatePickerEvents(); + return picker; }; - picker.hide = function () { - // Ignore event if in the middle of a picker transition - var collapse = picker.widget.find('.collapse'), i, collapseData; - for (i = 0; i < collapse.length; i++) { - collapseData = collapse.eq(i).data('collapse'); - if (collapseData && collapseData.transitioning) { - return; + picker.ignoreReadonly = function (ignoreReadonly) { + if (arguments.length === 0) { + return options.ignoreReadonly; + } + if (typeof ignoreReadonly !== 'boolean') { + throw new TypeError('ignoreReadonly () expects a boolean parameter'); + } + options.ignoreReadonly = ignoreReadonly; + return picker; + }; + + picker.options = function (newOptions) { + if (arguments.length === 0) { + return $.extend(true, {}, options); + } + + if (!(newOptions instanceof Object)) { + throw new TypeError('options() options parameter should be an object'); + } + $.extend(true, options, newOptions); + $.each(options, function (key, value) { + if (picker[key] !== undefined) { + picker[key](value); + } else { + throw new TypeError('option ' + key + ' is not recognized!'); + } + }); + return picker; + }; + + picker.date = function (newDate) { + /// + ///Returns the component's model current date, a moment object or null if not set. + ///date.clone() + /// + /// + ///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. + ///Takes string, Date, moment, null parameter. + /// + if (arguments.length === 0) { + if (unset) { + return null; + } + return date.clone(); + } + + if (newDate !== null && typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) { + throw new TypeError('date() parameter must be one of [null, string, moment or Date]'); + } + + setValue(newDate === null ? null : parseInputDate(newDate)); + return picker; + }; + + picker.format = function (newFormat) { + ///test su + ///info about para + ///returns foo + if (arguments.length === 0) { + return options.format; + } + + if ((typeof newFormat !== 'string') && ((typeof newFormat !== 'boolean') || (newFormat !== false))) { + throw new TypeError('format() expects a string or boolean:false parameter ' + newFormat); + } + + options.format = newFormat; + if (actualFormat) { + initFormatting(); // reinit formatting + } + return picker; + }; + + picker.timeZone = function (newZone) { + if (arguments.length === 0) { + return options.timeZone; + } + + if (typeof newZone !== 'string') { + throw new TypeError('newZone() expects a string parameter'); + } + + options.timeZone = newZone; + + return picker; + }; + + picker.dayViewHeaderFormat = function (newFormat) { + if (arguments.length === 0) { + return options.dayViewHeaderFormat; + } + + if (typeof newFormat !== 'string') { + throw new TypeError('dayViewHeaderFormat() expects a string parameter'); + } + + options.dayViewHeaderFormat = newFormat; + return picker; + }; + + picker.extraFormats = function (formats) { + if (arguments.length === 0) { + return options.extraFormats; + } + + if (formats !== false && !(formats instanceof Array)) { + throw new TypeError('extraFormats() expects an array or false parameter'); + } + + options.extraFormats = formats; + if (parseFormats) { + initFormatting(); // reinit formatting + } + return picker; + }; + + picker.disabledDates = function (dates) { + /// + ///Returns an array with the currently set disabled dates on the component. + ///options.disabledDates + /// + /// + ///Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of + ///options.enabledDates if such exist. + ///Takes an [ string or Date or moment ] of values and allows the user to select only from those days. + /// + if (arguments.length === 0) { + return (options.disabledDates ? $.extend({}, options.disabledDates) : options.disabledDates); + } + + if (!dates) { + options.disabledDates = false; + update(); + return picker; + } + if (!(dates instanceof Array)) { + throw new TypeError('disabledDates() expects an array parameter'); + } + options.disabledDates = indexGivenDates(dates); + options.enabledDates = false; + update(); + return picker; + }; + + picker.enabledDates = function (dates) { + /// + ///Returns an array with the currently set enabled dates on the component. + ///options.enabledDates + /// + /// + ///Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledDates if such exist. + ///Takes an [ string or Date or moment ] of values and allows the user to select only from those days. + /// + if (arguments.length === 0) { + return (options.enabledDates ? $.extend({}, options.enabledDates) : options.enabledDates); + } + + if (!dates) { + options.enabledDates = false; + update(); + return picker; + } + if (!(dates instanceof Array)) { + throw new TypeError('enabledDates() expects an array parameter'); + } + options.enabledDates = indexGivenDates(dates); + options.disabledDates = false; + update(); + return picker; + }; + + picker.daysOfWeekDisabled = function (daysOfWeekDisabled) { + if (arguments.length === 0) { + return options.daysOfWeekDisabled.splice(0); + } + + if ((typeof daysOfWeekDisabled === 'boolean') && !daysOfWeekDisabled) { + options.daysOfWeekDisabled = false; + update(); + return picker; + } + + if (!(daysOfWeekDisabled instanceof Array)) { + throw new TypeError('daysOfWeekDisabled() expects an array parameter'); + } + options.daysOfWeekDisabled = daysOfWeekDisabled.reduce(function (previousValue, currentValue) { + currentValue = parseInt(currentValue, 10); + if (currentValue > 6 || currentValue < 0 || isNaN(currentValue)) { + return previousValue; + } + if (previousValue.indexOf(currentValue) === -1) { + previousValue.push(currentValue); + } + return previousValue; + }, []).sort(); + if (options.useCurrent && !options.keepInvalid) { + var tries = 0; + while (!isValid(date, 'd')) { + date.add(1, 'd'); + if (tries === 31) { + throw 'Tried 31 times to find a valid date'; + } + tries++; + } + setValue(date); + } + update(); + return picker; + }; + + picker.maxDate = function (maxDate) { + if (arguments.length === 0) { + return options.maxDate ? options.maxDate.clone() : options.maxDate; + } + + if ((typeof maxDate === 'boolean') && maxDate === false) { + options.maxDate = false; + update(); + return picker; + } + + if (typeof maxDate === 'string') { + if (maxDate === 'now' || maxDate === 'moment') { + maxDate = getMoment(); } } - picker.widget.hide(); - picker.widget.removeClass('picker-open'); - picker.viewMode = picker.startViewMode; + + var parsedDate = parseInputDate(maxDate); + + if (!parsedDate.isValid()) { + throw new TypeError('maxDate() Could not parse date parameter: ' + maxDate); + } + if (options.minDate && parsedDate.isBefore(options.minDate)) { + throw new TypeError('maxDate() date parameter is before options.minDate: ' + parsedDate.format(actualFormat)); + } + options.maxDate = parsedDate; + if (options.useCurrent && !options.keepInvalid && date.isAfter(maxDate)) { + setValue(options.maxDate); + } + if (viewDate.isAfter(parsedDate)) { + viewDate = parsedDate.clone().subtract(options.stepping, 'm'); + } + update(); + return picker; + }; + + picker.minDate = function (minDate) { + if (arguments.length === 0) { + return options.minDate ? options.minDate.clone() : options.minDate; + } + + if ((typeof minDate === 'boolean') && minDate === false) { + options.minDate = false; + update(); + return picker; + } + + if (typeof minDate === 'string') { + if (minDate === 'now' || minDate === 'moment') { + minDate = getMoment(); + } + } + + var parsedDate = parseInputDate(minDate); + + if (!parsedDate.isValid()) { + throw new TypeError('minDate() Could not parse date parameter: ' + minDate); + } + if (options.maxDate && parsedDate.isAfter(options.maxDate)) { + throw new TypeError('minDate() date parameter is after options.maxDate: ' + parsedDate.format(actualFormat)); + } + options.minDate = parsedDate; + if (options.useCurrent && !options.keepInvalid && date.isBefore(minDate)) { + setValue(options.minDate); + } + if (viewDate.isBefore(parsedDate)) { + viewDate = parsedDate.clone().add(options.stepping, 'm'); + } + update(); + return picker; + }; + + picker.defaultDate = function (defaultDate) { + /// + ///Returns a moment with the options.defaultDate option configuration or false if not set + ///date.clone() + /// + /// + ///Will set the picker's inital date. If a boolean:false value is passed the options.defaultDate parameter is cleared. + ///Takes a string, Date, moment, boolean:false + /// + if (arguments.length === 0) { + return options.defaultDate ? options.defaultDate.clone() : options.defaultDate; + } + if (!defaultDate) { + options.defaultDate = false; + return picker; + } + + if (typeof defaultDate === 'string') { + if (defaultDate === 'now' || defaultDate === 'moment') { + defaultDate = getMoment(); + } else { + defaultDate = getMoment(defaultDate); + } + } + + var parsedDate = parseInputDate(defaultDate); + if (!parsedDate.isValid()) { + throw new TypeError('defaultDate() Could not parse date parameter: ' + defaultDate); + } + if (!isValid(parsedDate)) { + throw new TypeError('defaultDate() date passed is invalid according to component setup validations'); + } + + options.defaultDate = parsedDate; + + if ((options.defaultDate && options.inline) || input.val().trim() === '') { + setValue(options.defaultDate); + } + return picker; + }; + + picker.locale = function (locale) { + if (arguments.length === 0) { + return options.locale; + } + + if (!moment.localeData(locale)) { + throw new TypeError('locale() locale ' + locale + ' is not loaded from moment locales!'); + } + + options.locale = locale; + date.locale(options.locale); + viewDate.locale(options.locale); + + if (actualFormat) { + initFormatting(); // reinit formatting + } + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.stepping = function (stepping) { + if (arguments.length === 0) { + return options.stepping; + } + + stepping = parseInt(stepping, 10); + if (isNaN(stepping) || stepping < 1) { + stepping = 1; + } + options.stepping = stepping; + return picker; + }; + + picker.useCurrent = function (useCurrent) { + var useCurrentOptions = ['year', 'month', 'day', 'hour', 'minute']; + if (arguments.length === 0) { + return options.useCurrent; + } + + if ((typeof useCurrent !== 'boolean') && (typeof useCurrent !== 'string')) { + throw new TypeError('useCurrent() expects a boolean or string parameter'); + } + if (typeof useCurrent === 'string' && useCurrentOptions.indexOf(useCurrent.toLowerCase()) === -1) { + throw new TypeError('useCurrent() expects a string parameter of ' + useCurrentOptions.join(', ')); + } + options.useCurrent = useCurrent; + return picker; + }; + + picker.collapse = function (collapse) { + if (arguments.length === 0) { + return options.collapse; + } + + if (typeof collapse !== 'boolean') { + throw new TypeError('collapse() expects a boolean parameter'); + } + if (options.collapse === collapse) { + return picker; + } + options.collapse = collapse; + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.icons = function (icons) { + if (arguments.length === 0) { + return $.extend({}, options.icons); + } + + if (!(icons instanceof Object)) { + throw new TypeError('icons() expects parameter to be an Object'); + } + $.extend(options.icons, icons); + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.tooltips = function (tooltips) { + if (arguments.length === 0) { + return $.extend({}, options.tooltips); + } + + if (!(tooltips instanceof Object)) { + throw new TypeError('tooltips() expects parameter to be an Object'); + } + $.extend(options.tooltips, tooltips); + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.useStrict = function (useStrict) { + if (arguments.length === 0) { + return options.useStrict; + } + + if (typeof useStrict !== 'boolean') { + throw new TypeError('useStrict() expects a boolean parameter'); + } + options.useStrict = useStrict; + return picker; + }; + + picker.sideBySide = function (sideBySide) { + if (arguments.length === 0) { + return options.sideBySide; + } + + if (typeof sideBySide !== 'boolean') { + throw new TypeError('sideBySide() expects a boolean parameter'); + } + options.sideBySide = sideBySide; + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.viewMode = function (viewMode) { + if (arguments.length === 0) { + return options.viewMode; + } + + if (typeof viewMode !== 'string') { + throw new TypeError('viewMode() expects a string parameter'); + } + + if (viewModes.indexOf(viewMode) === -1) { + throw new TypeError('viewMode() parameter must be one of (' + viewModes.join(', ') + ') value'); + } + + options.viewMode = viewMode; + currentViewMode = Math.max(viewModes.indexOf(viewMode), minViewModeNumber); + showMode(); - picker.element.trigger({ - type: 'dp.hide', - date: moment(picker.date) - }); - detachDatePickerGlobalEvents(); + return picker; }; - picker.setValue = function (newDate) { - moment.locale(picker.options.language); + picker.toolbarPlacement = function (toolbarPlacement) { + if (arguments.length === 0) { + return options.toolbarPlacement; + } + + if (typeof toolbarPlacement !== 'string') { + throw new TypeError('toolbarPlacement() expects a string parameter'); + } + if (toolbarPlacements.indexOf(toolbarPlacement) === -1) { + throw new TypeError('toolbarPlacement() parameter must be one of (' + toolbarPlacements.join(', ') + ') value'); + } + options.toolbarPlacement = toolbarPlacement; + + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.widgetPositioning = function (widgetPositioning) { + if (arguments.length === 0) { + return $.extend({}, options.widgetPositioning); + } + + if (({}).toString.call(widgetPositioning) !== '[object Object]') { + throw new TypeError('widgetPositioning() expects an object variable'); + } + if (widgetPositioning.horizontal) { + if (typeof widgetPositioning.horizontal !== 'string') { + throw new TypeError('widgetPositioning() horizontal variable must be a string'); + } + widgetPositioning.horizontal = widgetPositioning.horizontal.toLowerCase(); + if (horizontalModes.indexOf(widgetPositioning.horizontal) === -1) { + throw new TypeError('widgetPositioning() expects horizontal parameter to be one of (' + horizontalModes.join(', ') + ')'); + } + options.widgetPositioning.horizontal = widgetPositioning.horizontal; + } + if (widgetPositioning.vertical) { + if (typeof widgetPositioning.vertical !== 'string') { + throw new TypeError('widgetPositioning() vertical variable must be a string'); + } + widgetPositioning.vertical = widgetPositioning.vertical.toLowerCase(); + if (verticalModes.indexOf(widgetPositioning.vertical) === -1) { + throw new TypeError('widgetPositioning() expects vertical parameter to be one of (' + verticalModes.join(', ') + ')'); + } + options.widgetPositioning.vertical = widgetPositioning.vertical; + } + update(); + return picker; + }; + + picker.calendarWeeks = function (calendarWeeks) { + if (arguments.length === 0) { + return options.calendarWeeks; + } + + if (typeof calendarWeeks !== 'boolean') { + throw new TypeError('calendarWeeks() expects parameter to be a boolean value'); + } + + options.calendarWeeks = calendarWeeks; + update(); + return picker; + }; + + picker.showTodayButton = function (showTodayButton) { + if (arguments.length === 0) { + return options.showTodayButton; + } + + if (typeof showTodayButton !== 'boolean') { + throw new TypeError('showTodayButton() expects a boolean parameter'); + } + + options.showTodayButton = showTodayButton; + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.showClear = function (showClear) { + if (arguments.length === 0) { + return options.showClear; + } + + if (typeof showClear !== 'boolean') { + throw new TypeError('showClear() expects a boolean parameter'); + } + + options.showClear = showClear; + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.widgetParent = function (widgetParent) { + if (arguments.length === 0) { + return options.widgetParent; + } + + if (typeof widgetParent === 'string') { + widgetParent = $(widgetParent); + } + + if (widgetParent !== null && (typeof widgetParent !== 'string' && !(widgetParent instanceof $))) { + throw new TypeError('widgetParent() expects a string or a jQuery object parameter'); + } + + options.widgetParent = widgetParent; + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.keepOpen = function (keepOpen) { + if (arguments.length === 0) { + return options.keepOpen; + } + + if (typeof keepOpen !== 'boolean') { + throw new TypeError('keepOpen() expects a boolean parameter'); + } + + options.keepOpen = keepOpen; + return picker; + }; + + picker.focusOnShow = function (focusOnShow) { + if (arguments.length === 0) { + return options.focusOnShow; + } + + if (typeof focusOnShow !== 'boolean') { + throw new TypeError('focusOnShow() expects a boolean parameter'); + } + + options.focusOnShow = focusOnShow; + return picker; + }; + + picker.inline = function (inline) { + if (arguments.length === 0) { + return options.inline; + } + + if (typeof inline !== 'boolean') { + throw new TypeError('inline() expects a boolean parameter'); + } + + options.inline = inline; + return picker; + }; + + picker.clear = function () { + clear(); + return picker; + }; + + picker.keyBinds = function (keyBinds) { + if (arguments.length === 0) { + return options.keyBinds; + } + + options.keyBinds = keyBinds; + return picker; + }; + + picker.getMoment = function (d) { + return getMoment(d); + }; + + picker.debug = function (debug) { + if (typeof debug !== 'boolean') { + throw new TypeError('debug() expects a boolean parameter'); + } + + options.debug = debug; + return picker; + }; + + picker.allowInputToggle = function (allowInputToggle) { + if (arguments.length === 0) { + return options.allowInputToggle; + } + + if (typeof allowInputToggle !== 'boolean') { + throw new TypeError('allowInputToggle() expects a boolean parameter'); + } + + options.allowInputToggle = allowInputToggle; + return picker; + }; + + picker.showClose = function (showClose) { + if (arguments.length === 0) { + return options.showClose; + } + + if (typeof showClose !== 'boolean') { + throw new TypeError('showClose() expects a boolean parameter'); + } + + options.showClose = showClose; + return picker; + }; + + picker.keepInvalid = function (keepInvalid) { + if (arguments.length === 0) { + return options.keepInvalid; + } + + if (typeof keepInvalid !== 'boolean') { + throw new TypeError('keepInvalid() expects a boolean parameter'); + } + options.keepInvalid = keepInvalid; + return picker; + }; + + picker.datepickerInput = function (datepickerInput) { + if (arguments.length === 0) { + return options.datepickerInput; + } + + if (typeof datepickerInput !== 'string') { + throw new TypeError('datepickerInput() expects a string parameter'); + } + + options.datepickerInput = datepickerInput; + return picker; + }; + + picker.parseInputDate = function (parseInputDate) { + if (arguments.length === 0) { + return options.parseInputDate; + } + + if (typeof parseInputDate !== 'function') { + throw new TypeError('parseInputDate() sholud be as function'); + } + + options.parseInputDate = parseInputDate; + + return picker; + }; + + picker.disabledTimeIntervals = function (disabledTimeIntervals) { + /// + ///Returns an array with the currently set disabled dates on the component. + ///options.disabledTimeIntervals + /// + /// + ///Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of + ///options.enabledDates if such exist. + ///Takes an [ string or Date or moment ] of values and allows the user to select only from those days. + /// + if (arguments.length === 0) { + return (options.disabledTimeIntervals ? $.extend({}, options.disabledTimeIntervals) : options.disabledTimeIntervals); + } + + if (!disabledTimeIntervals) { + options.disabledTimeIntervals = false; + update(); + return picker; + } + if (!(disabledTimeIntervals instanceof Array)) { + throw new TypeError('disabledTimeIntervals() expects an array parameter'); + } + options.disabledTimeIntervals = disabledTimeIntervals; + update(); + return picker; + }; + + picker.disabledHours = function (hours) { + /// + ///Returns an array with the currently set disabled hours on the component. + ///options.disabledHours + /// + /// + ///Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of + ///options.enabledHours if such exist. + ///Takes an [ int ] of values and disallows the user to select only from those hours. + /// + if (arguments.length === 0) { + return (options.disabledHours ? $.extend({}, options.disabledHours) : options.disabledHours); + } + + if (!hours) { + options.disabledHours = false; + update(); + return picker; + } + if (!(hours instanceof Array)) { + throw new TypeError('disabledHours() expects an array parameter'); + } + options.disabledHours = indexGivenHours(hours); + options.enabledHours = false; + if (options.useCurrent && !options.keepInvalid) { + var tries = 0; + while (!isValid(date, 'h')) { + date.add(1, 'h'); + if (tries === 24) { + throw 'Tried 24 times to find a valid date'; + } + tries++; + } + setValue(date); + } + update(); + return picker; + }; + + picker.enabledHours = function (hours) { + /// + ///Returns an array with the currently set enabled hours on the component. + ///options.enabledHours + /// + /// + ///Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledHours if such exist. + ///Takes an [ int ] of values and allows the user to select only from those hours. + /// + if (arguments.length === 0) { + return (options.enabledHours ? $.extend({}, options.enabledHours) : options.enabledHours); + } + + if (!hours) { + options.enabledHours = false; + update(); + return picker; + } + if (!(hours instanceof Array)) { + throw new TypeError('enabledHours() expects an array parameter'); + } + options.enabledHours = indexGivenHours(hours); + options.disabledHours = false; + if (options.useCurrent && !options.keepInvalid) { + var tries = 0; + while (!isValid(date, 'h')) { + date.add(1, 'h'); + if (tries === 24) { + throw 'Tried 24 times to find a valid date'; + } + tries++; + } + setValue(date); + } + update(); + return picker; + }; + /** + * Returns the component's model current viewDate, a moment object or null if not set. 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. + * @param {Takes string, viewDate, moment, null parameter.} newDate + * @returns {viewDate.clone()} + */ + picker.viewDate = function (newDate) { + if (arguments.length === 0) { + return viewDate.clone(); + } + if (!newDate) { - picker.unset = true; - set(); + viewDate = date.clone(); + return picker; + } + + if (typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) { + throw new TypeError('viewDate() parameter must be one of [string, moment or Date]'); + } + + viewDate = parseInputDate(newDate); + viewUpdate(); + return picker; + }; + + // initializing element and component attributes + if (element.is('input')) { + input = element; + } else { + input = element.find(options.datepickerInput); + if (input.length === 0) { + input = element.find('input'); + } else if (!input.is('input')) { + throw new Error('CSS class "' + options.datepickerInput + '" cannot be applied to non input element'); + } + } + + if (element.hasClass('input-group')) { + // in case there is more then one 'input-group-addon' Issue #48 + if (element.find('.datepickerbutton').length === 0) { + component = element.find('.input-group-addon'); } else { - picker.unset = false; + component = element.find('.datepickerbutton'); } - if (!moment.isMoment(newDate)) { - newDate = (newDate instanceof Date) ? moment(newDate) : moment(newDate, picker.format, picker.options.useStrict); - } else { - newDate = newDate.locale(picker.options.language); - } - if (newDate.isValid()) { - picker.date = newDate; - set(); - picker.viewDate = moment({y: picker.date.year(), M: picker.date.month()}); - fillDate(); - fillTime(); - } - else { - notifyError(newDate); - } - }; + } - picker.getDate = function () { - if (picker.unset) { - return null; - } - return moment(picker.date); - }; + if (!options.inline && !input.is('input')) { + throw new Error('Could not initialize DateTimePicker without an input element'); + } - picker.setDate = function (date) { - var oldDate = moment(picker.date); - if (!date) { - picker.setValue(null); - } else { - picker.setValue(date); - } - notifyChange(oldDate, 'function'); - }; + // Set defaults for date here now instead of in var declaration + date = getMoment(); + viewDate = date.clone(); - picker.setDisabledDates = function (dates) { - picker.options.disabledDates = indexGivenDates(dates); - if (picker.viewDate) { - update(); - } - }; + $.extend(true, options, dataToOptions()); - picker.setEnabledDates = function (dates) { - picker.options.enabledDates = indexGivenDates(dates); - if (picker.viewDate) { - update(); - } - }; + picker.options(options); - picker.setMaxDate = function (date) { - if (date === undefined) { - return; - } - if (moment.isMoment(date) || date instanceof Date) { - picker.options.maxDate = moment(date); - } else { - picker.options.maxDate = moment(date, picker.format, picker.options.useStrict); - } - if (picker.viewDate) { - update(); - } - }; + initFormatting(); - picker.setMinDate = function (date) { - if (date === undefined) { - return; - } - if (moment.isMoment(date) || date instanceof Date) { - picker.options.minDate = moment(date); - } else { - picker.options.minDate = moment(date, picker.format, picker.options.useStrict); - } - if (picker.viewDate) { - update(); - } - }; + attachDatePickerElementEvents(); - init(); + if (input.prop('disabled')) { + picker.disable(); + } + if (input.is('input') && input.val().trim().length !== 0) { + setValue(parseInputDate(input.val().trim())); + } + else if (options.defaultDate && input.attr('placeholder') === undefined) { + setValue(options.defaultDate); + } + if (options.inline) { + show(); + } + return picker; }; + /******************************************************************************** + * + * jQuery plugin constructor and defaults object + * + ********************************************************************************/ + + /** + * See (http://jquery.com/). + * @name jQuery + * @class + * See the jQuery Library (http://jquery.com/) for full details. This just + * documents the function and classes that are added to jQuery by this plug-in. + */ + /** + * See (http://jquery.com/) + * @name fn + * @class + * See the jQuery Library (http://jquery.com/) for full details. This just + * documents the function and classes that are added to jQuery by this plug-in. + * @memberOf jQuery + */ + /** + * Show comments + * @class datetimepicker + * @memberOf jQuery.fn + */ $.fn.datetimepicker = function (options) { - return this.each(function () { - var $this = $(this), - data = $this.data('DateTimePicker'); - if (!data) { - $this.data('DateTimePicker', new DateTimePicker(this, options)); + options = options || {}; + + var args = Array.prototype.slice.call(arguments, 1), + isInstance = true, + thisMethods = ['destroy', 'hide', 'show', 'toggle'], + returnValue; + + if (typeof options === 'object') { + return this.each(function () { + var $this = $(this), + _options; + if (!$this.data('DateTimePicker')) { + // create a private copy of the defaults object + _options = $.extend(true, {}, $.fn.datetimepicker.defaults, options); + $this.data('DateTimePicker', dateTimePicker($this, _options)); + } + }); + } else if (typeof options === 'string') { + this.each(function () { + var $this = $(this), + instance = $this.data('DateTimePicker'); + if (!instance) { + throw new Error('bootstrap-datetimepicker("' + options + '") method was called on an element that is not using DateTimePicker'); + } + + returnValue = instance[options].apply(instance, args); + isInstance = returnValue === instance; + }); + + if (isInstance || $.inArray(options, thisMethods) > -1) { + return this; } - }); + + return returnValue; + } + + throw new TypeError('Invalid arguments for DateTimePicker: ' + options); }; $.fn.datetimepicker.defaults = { + timeZone: '', format: false, - pickDate: true, - pickTime: true, - useMinutes: true, - useSeconds: false, + dayViewHeaderFormat: 'MMMM YYYY', + extraFormats: false, + stepping: 1, + minDate: false, + maxDate: false, useCurrent: true, - calendarWeeks: false, - minuteStepping: 1, - minDate: moment({y: 1900}), - maxDate: moment().add(100, 'y'), - showToday: true, collapse: true, - language: moment.locale(), - defaultDate: '', + locale: moment.locale(), + defaultDate: false, disabledDates: false, enabledDates: false, - icons: {}, + icons: { + time: 'glyphicon glyphicon-time', + date: 'glyphicon glyphicon-calendar', + up: 'glyphicon glyphicon-chevron-up', + down: 'glyphicon glyphicon-chevron-down', + previous: 'glyphicon glyphicon-chevron-left', + next: 'glyphicon glyphicon-chevron-right', + today: 'glyphicon glyphicon-screenshot', + clear: 'glyphicon glyphicon-trash', + close: 'glyphicon glyphicon-remove' + }, + tooltips: { + today: 'Go to today', + clear: 'Clear selection', + close: 'Close the picker', + selectMonth: 'Select Month', + prevMonth: 'Previous Month', + nextMonth: 'Next Month', + selectYear: 'Select Year', + prevYear: 'Previous Year', + nextYear: 'Next Year', + selectDecade: 'Select Decade', + prevDecade: 'Previous Decade', + nextDecade: 'Next Decade', + prevCentury: 'Previous Century', + nextCentury: 'Next Century', + pickHour: 'Pick Hour', + incrementHour: 'Increment Hour', + decrementHour: 'Decrement Hour', + pickMinute: 'Pick Minute', + incrementMinute: 'Increment Minute', + decrementMinute: 'Decrement Minute', + pickSecond: 'Pick Second', + incrementSecond: 'Increment Second', + decrementSecond: 'Decrement Second', + togglePeriod: 'Toggle Period', + selectTime: 'Select Time' + }, useStrict: false, - direction: 'auto', sideBySide: false, - daysOfWeekDisabled: [], - widgetParent: false + daysOfWeekDisabled: false, + calendarWeeks: false, + viewMode: 'days', + toolbarPlacement: 'default', + showTodayButton: false, + showClear: false, + showClose: false, + widgetPositioning: { + horizontal: 'auto', + vertical: 'auto' + }, + widgetParent: null, + ignoreReadonly: false, + keepOpen: false, + focusOnShow: true, + inline: false, + keepInvalid: false, + datepickerInput: '.datepickerinput', + keyBinds: { + up: function (widget) { + if (!widget) { + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().subtract(7, 'd')); + } else { + this.date(d.clone().add(this.stepping(), 'm')); + } + }, + down: function (widget) { + if (!widget) { + this.show(); + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().add(7, 'd')); + } else { + this.date(d.clone().subtract(this.stepping(), 'm')); + } + }, + 'control up': function (widget) { + if (!widget) { + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().subtract(1, 'y')); + } else { + this.date(d.clone().add(1, 'h')); + } + }, + 'control down': function (widget) { + if (!widget) { + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().add(1, 'y')); + } else { + this.date(d.clone().subtract(1, 'h')); + } + }, + left: function (widget) { + if (!widget) { + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().subtract(1, 'd')); + } + }, + right: function (widget) { + if (!widget) { + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().add(1, 'd')); + } + }, + pageUp: function (widget) { + if (!widget) { + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().subtract(1, 'M')); + } + }, + pageDown: function (widget) { + if (!widget) { + return; + } + var d = this.date() || this.getMoment(); + if (widget.find('.datepicker').is(':visible')) { + this.date(d.clone().add(1, 'M')); + } + }, + enter: function () { + this.hide(); + }, + escape: function () { + this.hide(); + }, + //tab: function (widget) { //this break the flow of the form. disabling for now + // var toggle = widget.find('.picker-switch a[data-action="togglePicker"]'); + // if(toggle.length > 0) toggle.click(); + //}, + 'control space': function (widget) { + if (!widget) { + return; + } + if (widget.find('.timepicker').is(':visible')) { + widget.find('.btn[data-action="togglePeriod"]').click(); + } + }, + t: function () { + this.date(this.getMoment()); + }, + 'delete': function () { + this.clear(); + } + }, + debug: false, + allowInputToggle: false, + disabledTimeIntervals: false, + disabledHours: false, + enabledHours: false, + viewDate: false }; + + return $.fn.datetimepicker; })); diff --git a/addons/web/static/lib/bootstrap-datetimepicker/src/less/_bootstrap-datetimepicker.less b/addons/web/static/lib/bootstrap-datetimepicker/src/less/_bootstrap-datetimepicker.less new file mode 100644 index 00000000000..a92cd378abe --- /dev/null +++ b/addons/web/static/lib/bootstrap-datetimepicker/src/less/_bootstrap-datetimepicker.less @@ -0,0 +1,352 @@ +/*! + * Datetimepicker for Bootstrap 3 + * version : 4.17.42 + * https://github.com/Eonasdan/bootstrap-datetimepicker/ + */ +@bs-datetimepicker-timepicker-font-size: 1.2em; +@bs-datetimepicker-active-bg: @btn-primary-bg; +@bs-datetimepicker-active-color: @btn-primary-color; +@bs-datetimepicker-border-radius: @border-radius-base; +@bs-datetimepicker-btn-hover-bg: @gray-lighter; +@bs-datetimepicker-disabled-color: @gray-light; +@bs-datetimepicker-alternate-color: @gray-light; +@bs-datetimepicker-secondary-border-color: #ccc; +@bs-datetimepicker-secondary-border-color-rgba: rgba(0, 0, 0, 0.2); +@bs-datetimepicker-primary-border-color: white; +@bs-datetimepicker-text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + +.bootstrap-datetimepicker-widget { + list-style: none; + + &.dropdown-menu { + margin: 2px 0; + padding: 4px; + width: 19em; + + &.timepicker-sbs { + @media (min-width: @screen-sm-min) { + width: 38em; + } + + @media (min-width: @screen-md-min) { + width: 38em; + } + + @media (min-width: @screen-lg-min) { + width: 38em; + } + } + + &:before, &:after { + content: ''; + display: inline-block; + position: absolute; + } + + &.bottom { + &:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid @bs-datetimepicker-secondary-border-color; + border-bottom-color: @bs-datetimepicker-secondary-border-color-rgba; + top: -7px; + left: 7px; + } + + &:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid @bs-datetimepicker-primary-border-color; + top: -6px; + left: 8px; + } + } + + &.top { + &:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-top: 7px solid @bs-datetimepicker-secondary-border-color; + border-top-color: @bs-datetimepicker-secondary-border-color-rgba; + bottom: -7px; + left: 6px; + } + + &:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid @bs-datetimepicker-primary-border-color; + bottom: -6px; + left: 7px; + } + } + + &.pull-right { + &:before { + left: auto; + right: 6px; + } + + &:after { + left: auto; + right: 7px; + } + } + } + + .list-unstyled { + margin: 0; + } + + a[data-action] { + padding: 6px 0; + } + + a[data-action]:active { + box-shadow: none; + } + + .timepicker-hour, .timepicker-minute, .timepicker-second { + width: 54px; + font-weight: bold; + font-size: @bs-datetimepicker-timepicker-font-size; + margin: 0; + } + + button[data-action] { + padding: 6px; + } + + .btn[data-action="incrementHours"]::after { + .sr-only(); + content: "Increment Hours"; + } + + .btn[data-action="incrementMinutes"]::after { + .sr-only(); + content: "Increment Minutes"; + } + + .btn[data-action="decrementHours"]::after { + .sr-only(); + content: "Decrement Hours"; + } + + .btn[data-action="decrementMinutes"]::after { + .sr-only(); + content: "Decrement Minutes"; + } + + .btn[data-action="showHours"]::after { + .sr-only(); + content: "Show Hours"; + } + + .btn[data-action="showMinutes"]::after { + .sr-only(); + content: "Show Minutes"; + } + + .btn[data-action="togglePeriod"]::after { + .sr-only(); + content: "Toggle AM/PM"; + } + + .btn[data-action="clear"]::after { + .sr-only(); + content: "Clear the picker"; + } + + .btn[data-action="today"]::after { + .sr-only(); + content: "Set the date to today"; + } + + .picker-switch { + text-align: center; + + &::after { + .sr-only(); + content: "Toggle Date and Time Screens"; + } + + td { + padding: 0; + margin: 0; + height: auto; + width: auto; + line-height: inherit; + + span { + line-height: 2.5; + height: 2.5em; + width: 100%; + } + } + } + + table { + width: 100%; + margin: 0; + + + & td, + & th { + text-align: center; + border-radius: @bs-datetimepicker-border-radius; + } + + & th { + height: 20px; + line-height: 20px; + width: 20px; + + &.picker-switch { + width: 145px; + } + + &.disabled, + &.disabled:hover { + background: none; + color: @bs-datetimepicker-disabled-color; + cursor: not-allowed; + } + + &.prev::after { + .sr-only(); + content: "Previous Month"; + } + + &.next::after { + .sr-only(); + content: "Next Month"; + } + } + + & thead tr:first-child th { + cursor: pointer; + + &:hover { + background: @bs-datetimepicker-btn-hover-bg; + } + } + + & td { + height: 54px; + line-height: 54px; + width: 54px; + + &.cw { + font-size: .8em; + height: 20px; + line-height: 20px; + color: @bs-datetimepicker-alternate-color; + } + + &.day { + height: 20px; + line-height: 20px; + width: 20px; + } + + &.day:hover, + &.hour:hover, + &.minute:hover, + &.second:hover { + background: @bs-datetimepicker-btn-hover-bg; + cursor: pointer; + } + + &.old, + &.new { + color: @bs-datetimepicker-alternate-color; + } + + &.today { + position: relative; + + &:before { + content: ''; + display: inline-block; + border: solid transparent; + border-width: 0 0 7px 7px; + border-bottom-color: @bs-datetimepicker-active-bg; + border-top-color: @bs-datetimepicker-secondary-border-color-rgba; + position: absolute; + bottom: 4px; + right: 4px; + } + } + + &.active, + &.active:hover { + background-color: @bs-datetimepicker-active-bg; + color: @bs-datetimepicker-active-color; + text-shadow: @bs-datetimepicker-text-shadow; + } + + &.active.today:before { + border-bottom-color: #fff; + } + + &.disabled, + &.disabled:hover { + background: none; + color: @bs-datetimepicker-disabled-color; + cursor: not-allowed; + } + + span { + display: inline-block; + width: 54px; + height: 54px; + line-height: 54px; + margin: 2px 1.5px; + cursor: pointer; + border-radius: @bs-datetimepicker-border-radius; + + &:hover { + background: @bs-datetimepicker-btn-hover-bg; + } + + &.active { + background-color: @bs-datetimepicker-active-bg; + color: @bs-datetimepicker-active-color; + text-shadow: @bs-datetimepicker-text-shadow; + } + + &.old { + color: @bs-datetimepicker-alternate-color; + } + + &.disabled, + &.disabled:hover { + background: none; + color: @bs-datetimepicker-disabled-color; + cursor: not-allowed; + } + } + } + } + + &.usetwentyfour { + td.hour { + height: 27px; + line-height: 27px; + } + } + + &.wider { + width: 21em; + } + + & .datepicker-decades .decade { + line-height: 1.8em !important; + } +} + +.input-group.date { + & .input-group-addon { + cursor: pointer; + } +} diff --git a/addons/web/static/lib/bootstrap-datetimepicker/src/less/bootstrap-datetimepicker-build.less b/addons/web/static/lib/bootstrap-datetimepicker/src/less/bootstrap-datetimepicker-build.less deleted file mode 100644 index 4a17370153b..00000000000 --- a/addons/web/static/lib/bootstrap-datetimepicker/src/less/bootstrap-datetimepicker-build.less +++ /dev/null @@ -1,5 +0,0 @@ -// Import boostrap variables including default color palette and fonts -@import "../../node_modules/bootstrap/less/variables.less"; - -// Import datepicker component -@import "bootstrap-datetimepicker.less"; diff --git a/addons/web/static/lib/bootstrap-datetimepicker/src/less/bootstrap-datetimepicker.less b/addons/web/static/lib/bootstrap-datetimepicker/src/less/bootstrap-datetimepicker.less deleted file mode 100755 index 49fbc15d300..00000000000 --- a/addons/web/static/lib/bootstrap-datetimepicker/src/less/bootstrap-datetimepicker.less +++ /dev/null @@ -1,290 +0,0 @@ -/*! - * Datetimepicker for Bootstrap v3 -//! version : 3.0.3 - * https://github.com/Eonasdan/bootstrap-datetimepicker/ - */ -.bootstrap-datetimepicker-widget { - top: 0; - left: 0; - width: 250px; - padding: 4px; - margin-top: 1px; - z-index: 99999 !important; - border-radius: @border-radius-base; - - &.timepicker-sbs { - width: 600px; - } - - &.bottom { - &:before { - content: ''; - display: inline-block; - border-left: 7px solid transparent; - border-right: 7px solid transparent; - border-bottom: 7px solid #ccc; - border-bottom-color: rgba(0,0,0,.2); - position: absolute; - top: -7px; - left: 7px; - } - - &:after { - content: ''; - display: inline-block; - border-left: 6px solid transparent; - border-right: 6px solid transparent; - border-bottom: 6px solid white; - position: absolute; - top: -6px; - left: 8px; - } - } - - &.top { - &:before { - content: ''; - display: inline-block; - border-left: 7px solid transparent; - border-right: 7px solid transparent; - border-top: 7px solid #ccc; - border-top-color: rgba(0,0,0,.2); - position: absolute; - bottom: -7px; - left: 6px; - } - - &:after { - content: ''; - display: inline-block; - border-left: 6px solid transparent; - border-right: 6px solid transparent; - border-top: 6px solid white; - position: absolute; - bottom: -6px; - left: 7px; - } - } - - & .dow { - width: 14.2857%; - } - - &.pull-right { - &:before { - left: auto; - right: 6px; - } - - &:after { - left: auto; - right: 7px; - } - } - - >ul { - list-style-type: none; - margin: 0; - } - - a[data-action] { - padding: 6px 0; - } - - .timepicker-hour, .timepicker-minute, .timepicker-second { - width: 54px; - font-weight: bold; - font-size: 1.2em; - margin: 0; - } - - button[data-action] { - padding: 6px; - } - - table[data-hour-format="12"] .separator { - width: 4px; - padding: 0; - margin: 0; - } - - .datepicker > div { - display: none; - } - - .picker-switch { - text-align: center; - } - - table { - width: 100%; - margin: 0; - } - - td, - th { - text-align: center; - border-radius: @border-radius-base; - } - - td { - height: 54px; - line-height: 54px; - width: 54px; - - &.cw - { - font-size: 10px; - height: 20px; - line-height: 20px; - color: @gray-light; - } - - &.day - { - height: 20px; - line-height: 20px; - width: 20px; - } - - &.day:hover, - &.hour:hover, - &.minute:hover, - &.second:hover { - background: @gray-lighter; - cursor: pointer; - } - - &.old, - &.new { - color: @gray-light; - } - - &.today { - position: relative; - - &:before { - content: ''; - display: inline-block; - border-left: 7px solid transparent; - border-bottom: 7px solid @btn-primary-bg; - border-top-color: rgba(0, 0, 0, 0.2); - position: absolute; - bottom: 4px; - right: 4px; - } - } - - &.active, - &.active:hover { - background-color: @btn-primary-bg; - color: #fff; - text-shadow: 0 -1px 0 rgba(0,0,0,.25); - } - - &.active.today:before { - border-bottom-color: #fff; - } - - &.disabled, - &.disabled:hover { - background: none; - color: @gray-light; - cursor: not-allowed; - } - - span { - display: inline-block; - width: 54px; - height: 54px; - line-height: 54px; - margin: 2px 1.5px; - cursor: pointer; - border-radius: @border-radius-base; - - &:hover { - background: @gray-lighter; - } - - &.active { - background-color: @btn-primary-bg; - color: #fff; - text-shadow: 0 -1px 0 rgba(0,0,0,.25); - } - - &.old { - color: @gray-light; - } - - &.disabled, - &.disabled:hover { - background: none; - color: @gray-light; - cursor: not-allowed; - } - } - } - - th { - height: 20px; - line-height: 20px; - width: 20px; - - &.picker-switch { - width: 145px; - } - - &.next, - &.prev { - font-size: @font-size-base * 1.5; - } - - &.disabled, - &.disabled:hover { - background: none; - color: @gray-light; - cursor: not-allowed; - } - } - - thead tr:first-child th { - cursor: pointer; - - &:hover { - background: @gray-lighter; - } - } -} - -.input-group { - &.date { - .input-group-addon span { - display: block; - cursor: pointer; - width: 16px; - height: 16px; - } - } -} - -.bootstrap-datetimepicker-widget.left-oriented { - &:before { - left: auto; - right: 6px; - } - - &:after { - left: auto; - right: 7px; - } -} - -.bootstrap-datetimepicker-widget ul.list-unstyled li div.timepicker div.timepicker-picker table.table-condensed tbody > tr > td { - padding: 0px !important; -} - -@media screen and (max-width: 767px) { - .bootstrap-datetimepicker-widget.timepicker-sbs { - width: 283px; - } -} diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js index 7f622f10820..74286da6df3 100644 --- a/addons/web/static/src/js/widgets/date_picker.js +++ b/addons/web/static/src/js/widgets/date_picker.js @@ -23,19 +23,20 @@ var DateWidget = Widget.extend({ this.name = parent.name; this.options = _.defaults(options || {}, { - pickTime: this.type_of_date === 'datetime', - useSeconds: this.type_of_date === 'datetime', - startDate: moment({ y: 1900 }), - endDate: moment().add(200, "y"), + format : time.strftime_to_moment_format((this.type_of_date === 'datetime')? (l10n.date_format + ' ' + l10n.time_format) : l10n.date_format), + minDate: moment({ y: 1900 }), + maxDate: moment().add(200, "y"), calendarWeeks: true, icons: { time: 'fa fa-clock-o', date: 'fa fa-calendar', + next: 'fa fa-chevron-right', + previous: 'fa fa-chevron-left', up: 'fa fa-chevron-up', - down: 'fa fa-chevron-down' + down: 'fa fa-chevron-down', }, - language : moment.locale(), - format : time.strftime_to_moment_format((this.type_of_date === 'datetime')? (l10n.date_format + ' ' + l10n.time_format) : l10n.date_format), + locale : moment.locale(), + allowInputToggle: true, }); }, start: function() { @@ -48,7 +49,7 @@ var DateWidget = Widget.extend({ set_value: function(value) { this.set({'value': value}); this.$input.val((value)? this.format_client(value) : ''); - this.picker.setValue(this.format_client(value)); + this.picker.date(this.format_client(value)); }, get_value: function() { return this.get('value'); @@ -88,12 +89,7 @@ var DateWidget = Widget.extend({ value = this.$input.val(); } - // temporarily set pickTime to true to bypass datetimepicker hiding on setValue - // see https://github.com/Eonasdan/bootstrap-datetimepicker/issues/603 - var saved_picktime = this.picker.options.pickTime; - this.picker.options.pickTime = true; - this.picker.setValue(value); - this.picker.options.pickTime = saved_picktime; + this.picker.date(value); }, change_datetime: function(e) { if(this.is_valid()) { diff --git a/addons/web/static/src/less/datepicker.less b/addons/web/static/src/less/datepicker.less index 271cd357d15..29ae830280c 100644 --- a/addons/web/static/src/less/datepicker.less +++ b/addons/web/static/src/less/datepicker.less @@ -1,6 +1,21 @@ @odoo-datepicker-week-color: #8f8f8f; @odoo-datepicker-week-bg-color: lighten(@odoo-datepicker-week-color, 30%); +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; +} + +.o_bootstrap_datepicker_parent{ + position: relative; +} + .o_datepicker { position: relative; diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 108bf1c0aac..8abdf9160ed 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -829,13 +829,17 @@ -
- - +
+
+
+ + +
+ diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index e1f49eca005..9cb88403ac9 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -21,8 +21,8 @@ - - + + @@ -92,7 +92,6 @@ -