diff --git a/addons/purchase/controllers/portal.py b/addons/purchase/controllers/portal.py index 9726f729f31..0371266727f 100644 --- a/addons/purchase/controllers/portal.py +++ b/addons/purchase/controllers/portal.py @@ -167,7 +167,7 @@ class CustomerPortal(portal.CustomerPortal): return request.render("purchase.portal_my_purchase_order_update_date", values) return request.render("purchase.portal_my_purchase_order", values) - @http.route(['/my/purchase//update'], type='http', methods=['POST'], auth="public", website=True) + @http.route(['/my/purchase//update'], type='json', auth="public", website=True) def portal_my_purchase_order_update_dates(self, order_id=None, access_token=None, **kw): """User update scheduled date on purchase order line. """ diff --git a/addons/purchase/static/src/js/purchase_datetimepicker.js b/addons/purchase/static/src/js/purchase_datetimepicker.js index de1be92f6df..25baf927a5b 100644 --- a/addons/purchase/static/src/js/purchase_datetimepicker.js +++ b/addons/purchase/static/src/js/purchase_datetimepicker.js @@ -1,6 +1,26 @@ -$(function () { - $('input.o-purchase-datetimepicker').datetimepicker(); - $('input.o-purchase-datetimepicker').on("hide.datetimepicker", function () { - $(this).parents('form').submit(); - }); -}) +/** @odoo-module */ +import PublicWidget from "@web/legacy/js/public/public_widget"; + +export const PurchaseDatePicker = PublicWidget.Widget.extend({ + selector: ".o-purchase-datetimepicker", + start() { + this.call("datetime_picker", "create", { + target: this.el, + onChange: (newDate) => { + const { accessToken, orderId, lineId } = this.el.dataset; + this._rpc({ + route: `/my/purchase/${orderId}/update?access_token=${accessToken}`, + params: { + [lineId]: newDate.toISODate(), + }, + }); + }, + pickerProps: { + type: "date", + value: luxon.DateTime.fromISO(this.el.dataset.value), + }, + }).enable(); + }, +}); + +PublicWidget.registry.PurchaseDatePicker = PurchaseDatePicker; diff --git a/addons/purchase/views/portal_templates.xml b/addons/purchase/views/portal_templates.xml index 23cbc00500a..6091f0b4135 100644 --- a/addons/purchase/views/portal_templates.xml +++ b/addons/purchase/views/portal_templates.xml @@ -262,7 +262,6 @@ Products Quantity Scheduled Date - Update Here Unit Price Taxes @@ -296,19 +295,20 @@ - -
-
- +
-
maxDate) || + (minDate.isValid && date < minDate) + ) { errors[questionId] = validationErrorMsg; } } @@ -691,11 +696,15 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa params[this.name] = this.value; break; case 'date': - params = self._prepareSubmitDates(params, this.name, this.value, false); - break; - case 'datetime': - params = self._prepareSubmitDates(params, this.name, this.value, true); + case 'datetime':{ + const [parse, serialize] = + $(this).data("questionType") === "date" + ? [parseDate, serializeDate] + : [parseDateTime, serializeDateTime]; + const date = parse(this.value); + params[this.name] = date ? serialize(date) : ""; break; + } case 'simple_choice_radio': case 'multiple_choice': params = self._prepareSubmitChoices(params, $(this), $(this).data('name')); @@ -706,19 +715,6 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa } }); }, - - /** - * Prepare date answer before submitting form. - * Convert date value from client current timezone to UTC Date to correspond to the server format. - * return params = { 'dateQuestionId' : '2019-05-23', 'datetimeQuestionId' : '2019-05-23 14:05:12' } - */ - _prepareSubmitDates: function (params, questionId, value, isDateTime) { - var momentDate = isDateTime ? parseDateTime(value, null, {timezone: true}) : parseDate(value); - var formattedDate = momentDate ? momentDate.toJSON() : ''; - params[questionId] = formattedDate; - return params; - }, - /** * Prepare choice answer before submitting form. * If the answer is not the 'comment selection' (=Other), calls the _prepareSubmitAnswer method to add the answer to the params @@ -919,77 +915,6 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa } }, - /** - * Initialize datetimepicker in correct format and with constraints - */ - _initDateTimePicker: function ($dateGroup) { - var disabledDates = []; - var questionType = $dateGroup.find('input').data('questionType'); - var minDateData = $dateGroup.data('mindate'); - var maxDateData = $dateGroup.data('maxdate'); - - var datetimepickerFormat = questionType === 'datetime' ? time.getLangDatetimeFormat() : time.getLangDateFormat(); - - var minDate = minDateData - ? this._formatDateTime(minDateData, datetimepickerFormat) - : moment({ y: 1000 }); - - var maxDate = maxDateData - ? this._formatDateTime(maxDateData, datetimepickerFormat) - : moment().add(200, "y"); - - if (questionType === 'date') { - // Include min and max date in selectable values - maxDate = moment(maxDate).add(1, "d"); - minDate = moment(minDate).subtract(1, "d"); - disabledDates = [minDate, maxDate]; - } - - $dateGroup.datetimepicker({ - format: datetimepickerFormat, - minDate: minDate, - maxDate: maxDate, - disabledDates: disabledDates, - useCurrent: false, - viewDate: moment(new Date()).hours(minDate.hours()).minutes(minDate.minutes()).seconds(minDate.seconds()).milliseconds(minDate.milliseconds()), - calendarWeeks: true, - icons: { - time: 'fa fa-clock-o', - date: 'fa fa-calendar', - next: 'oi oi-chevron-right', - previous: 'oi oi-chevron-left', - up: 'oi oi-chevron-up', - down: 'oi oi-chevron-down', - }, - locale: Settings.defaultLocale, - allowInputToggle: true, - }); - $dateGroup.on('error.datetimepicker', (err) => { - if (err.date) { - if (err.date < minDate) { - this.call("dialog", "add", AlertDialog, { - body: - _t("The date you selected is lower than the minimum date: ") + - minDate.format(datetimepickerFormat), - }); - } - - if (err.date > maxDate) { - this.call("dialog", "add", AlertDialog, { - body: - _t("The date you selected is greater than the maximum date: ") + - maxDate.format(datetimepickerFormat), - }); - } - } - return false; - }); - }, - - _formatDateTime: function (datetimeValue, format) { - return moment(formatDateTime(moment(datetimeValue), null, {timezone: true}), format); - }, - _initResultWidget: function () { var $result = this.$('.o_survey_result'); if ($result.length) { diff --git a/addons/survey/static/src/scss/survey_templates_form.scss b/addons/survey/static/src/scss/survey_templates_form.scss index 57754f4ca33..f468123419d 100644 --- a/addons/survey/static/src/scss/survey_templates_form.scss +++ b/addons/survey/static/src/scss/survey_templates_form.scss @@ -114,12 +114,6 @@ _::-webkit-full-page-media, _:future, :root .o_survey_wrap { } } - .o_survey_form_date [data-toggle="datetimepicker"] { - right: 0; - bottom: 5px; - top: auto; - } - .o_survey_choice_btn { transition: background-color 0.3s ease; flex: 1 0 300px; diff --git a/addons/survey/views/survey_templates.xml b/addons/survey/views/survey_templates.xml index 416393c6903..56957489ec7 100644 --- a/addons/survey/views/survey_templates.xml +++ b/addons/survey/views/survey_templates.xml @@ -371,28 +371,28 @@ diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py index c0f9ef64849..533a21923e2 100644 --- a/addons/web/__manifest__.py +++ b/addons/web/__manifest__.py @@ -182,8 +182,6 @@ This module provides the core of the Odoo Web Client. ('include', 'web._assets_bootstrap_frontend'), - 'web/static/src/legacy/scss/tempusdominus_overridden.scss', - 'web/static/lib/tempusdominus/tempusdominus.scss', 'web/static/lib/jquery.ui/jquery-ui.css', 'web/static/src/libs/fontawesome/css/font-awesome.css', 'web/static/lib/odoo_ui_icons/*', @@ -234,7 +232,6 @@ This module provides the core of the Odoo Web Client. 'web/static/lib/bootstrap/js/dist/scrollspy.js', 'web/static/lib/bootstrap/js/dist/tab.js', 'web/static/lib/bootstrap/js/dist/toast.js', - 'web/static/lib/tempusdominus/tempusdominus.js', 'web/static/lib/select2/select2.js', 'web/static/lib/clipboard/clipboard.js', 'web/static/lib/jSignature/jSignatureCustom.js', @@ -268,6 +265,7 @@ This module provides the core of the Odoo Web Client. ('remove', 'web/static/src/core/file_viewer/file_viewer.dark.scss'), ('remove', 'web/static/src/core/emoji_picker/emoji_data.js'), 'web/static/src/public/error_notifications.js', + 'web/static/src/public/datetime_picker_widget.js', 'web/static/src/libs/pdfjs.js', 'web/static/src/legacy/utils.js', @@ -512,7 +510,6 @@ This module provides the core of the Odoo Web Client. ('remove', 'web/static/tests/webclient/**/helpers.js'), # Legacy - 'web/static/lib/tempusdominus/tempusdominus.js', 'web/static/tests/legacy/**/*.js', ('remove', 'web/static/tests/legacy/helpers/**/*.js'), ('remove', 'web/static/tests/legacy/legacy_setup.js'), diff --git a/addons/web/static/lib/tempusdominus/LICENSE b/addons/web/static/lib/tempusdominus/LICENSE deleted file mode 100644 index d7ea8a7ec0d..00000000000 --- a/addons/web/static/lib/tempusdominus/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016 Tempus Dominus - -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 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. diff --git a/addons/web/static/lib/tempusdominus/tempusdominus.js b/addons/web/static/lib/tempusdominus/tempusdominus.js deleted file mode 100644 index 821f2c1d188..00000000000 --- a/addons/web/static/lib/tempusdominus/tempusdominus.js +++ /dev/null @@ -1,2811 +0,0 @@ -/*@preserve - * Tempus Dominus Bootstrap4 v5.0.1 (https://tempusdominus.github.io/bootstrap-4/) - * Copyright 2016-2018 Jonathan Peterson - * Licensed under MIT (https://github.com/tempusdominus/bootstrap-3/blob/master/LICENSE) - */ - -if (typeof jQuery === 'undefined') { - throw new Error('Tempus Dominus Bootstrap4\'s requires jQuery. jQuery must be included before Tempus Dominus Bootstrap4\'s JavaScript.'); -} - -+function ($) { - var version = $.fn.jquery.split(' ')[0].split('.'); - if ((version[0] < 2 && version[1] < 9) || (version[0] === 1 && version[1] === 9 && version[2] < 1) || (version[0] >= 4)) { - throw new Error('Tempus Dominus Bootstrap4\'s requires at least jQuery v3.0.0 but less than v4.0.0'); - } -}(jQuery); - - -if (typeof moment === 'undefined') { - throw new Error('Tempus Dominus Bootstrap4\'s requires moment.js. Moment.js must be included before Tempus Dominus Bootstrap4\'s JavaScript.'); -} - -var version = moment.version.split('.') -if ((version[0] <= 2 && version[1] < 17) || (version[0] >= 3)) { - throw new Error('Tempus Dominus Bootstrap4\'s requires at least moment.js v2.17.0 but less than v3.0.0'); -} - -+function () { - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -// ReSharper disable once InconsistentNaming -var DateTimePicker = function ($, moment) { - // ReSharper disable InconsistentNaming - var NAME = 'datetimepicker', - DATA_KEY = '' + NAME, - EVENT_KEY = '.' + DATA_KEY, - DATA_API_KEY = '.data-api', - Selector = { - // /!\ ODOO PATCH: ensure the datetimepickers can be toggled only after - // the Odoo lazy loader finished loading all lazy scripts. Another - // solution could have been to temporarily removing the data-toggle - // attributes during lazyloading but that would not have been stable as - // custom code could search for data-toggle elements while the lazy - // loading is being performed. Without this, clicking too soon on a - // datetimepicker would not use the right format and UI options. - DATA_TOGGLE: 'body:not(.o_lazy_js_waiting) [data-toggle="' + DATA_KEY + '"]' - }, - ClassName = { - INPUT: NAME + '-input' - }, - Event = { - CHANGE: 'change' + EVENT_KEY, - BLUR: 'blur' + EVENT_KEY, - KEYUP: 'keyup' + EVENT_KEY, - KEYDOWN: 'keydown' + EVENT_KEY, - FOCUS: 'focus' + EVENT_KEY, - CLICK_DATA_API: 'click' + EVENT_KEY + DATA_API_KEY, - //emitted - UPDATE: 'update' + EVENT_KEY, - ERROR: 'error' + EVENT_KEY, - HIDE: 'hide' + EVENT_KEY, - SHOW: 'show' + EVENT_KEY - }, - DatePickerModes = [{ - CLASS_NAME: 'days', - NAV_FUNCTION: 'M', - NAV_STEP: 1 - }, { - CLASS_NAME: 'months', - NAV_FUNCTION: 'y', - NAV_STEP: 1 - }, { - CLASS_NAME: 'years', - NAV_FUNCTION: 'y', - NAV_STEP: 10 - }, { - CLASS_NAME: 'decades', - NAV_FUNCTION: 'y', - NAV_STEP: 100 - }], - 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' - }, - ViewModes = ['times', 'days', 'months', 'years', 'decades'], - keyState = {}, - keyPressHandled = {}; - - var MinViewModeNumber = 0, - Default = { - timeZone: '', - format: false, - dayViewHeaderFormat: 'MMMM YYYY', - extraFormats: false, - stepping: 1, - minDate: false, - maxDate: false, - useCurrent: true, - collapse: true, - locale: moment.locale(), - defaultDate: false, - disabledDates: false, - enabledDates: false, - icons: { - time: 'fa fa-clock-o', - date: 'fa fa-calendar', - up: 'oi oi-arrow-up', - down: 'oi oi-arrow-down', - previous: 'oi oi-chevron-left', - next: 'oi oi-chevron-right', - today: 'fa fa-calendar-check-o', - clear: 'fa fa-trash', - close: 'fa fa-times' - }, - 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', - selectDate: 'Select Date' - }, - useStrict: false, - sideBySide: false, - daysOfWeekDisabled: false, - calendarWeeks: false, - viewMode: 'days', - toolbarPlacement: 'default', - buttons: { - showToday: false, - showClear: false, - showClose: false - }, - widgetPositioning: { - horizontal: 'auto', - vertical: 'auto' - }, - widgetParent: null, - ignoreReadonly: false, - keepOpen: false, - focusOnShow: true, - inline: false, - keepInvalid: false, - keyBinds: { - up: function up() { - if (!this.widget) { - return false; - } - var d = this._dates[0] || this.getMoment(); - if (this.widget.find('.datepicker').is(':visible')) { - this.date(d.clone().subtract(7, 'd')); - } else { - this.date(d.clone().add(this.stepping(), 'm')); - } - return true; - }, - down: function down() { - if (!this.widget) { - this.show(); - return false; - } - var d = this._dates[0] || this.getMoment(); - if (this.widget.find('.datepicker').is(':visible')) { - this.date(d.clone().add(7, 'd')); - } else { - this.date(d.clone().subtract(this.stepping(), 'm')); - } - return true; - }, - 'control up': function controlUp() { - if (!this.widget) { - return false; - } - var d = this._dates[0] || this.getMoment(); - if (this.widget.find('.datepicker').is(':visible')) { - this.date(d.clone().subtract(1, 'y')); - } else { - this.date(d.clone().add(1, 'h')); - } - return true; - }, - 'control down': function controlDown() { - if (!this.widget) { - return false; - } - var d = this._dates[0] || this.getMoment(); - if (this.widget.find('.datepicker').is(':visible')) { - this.date(d.clone().add(1, 'y')); - } else { - this.date(d.clone().subtract(1, 'h')); - } - return true; - }, - left: function left() { - if (!this.widget) { - return false; - } - var d = this._dates[0] || this.getMoment(); - if (this.widget.find('.datepicker').is(':visible')) { - this.date(d.clone().subtract(1, 'd')); - } - return true; - }, - right: function right() { - if (!this.widget) { - return false; - } - var d = this._dates[0] || this.getMoment(); - if (this.widget.find('.datepicker').is(':visible')) { - this.date(d.clone().add(1, 'd')); - } - return true; - }, - pageUp: function pageUp() { - if (!this.widget) { - return false; - } - var d = this._dates[0] || this.getMoment(); - if (this.widget.find('.datepicker').is(':visible')) { - this.date(d.clone().subtract(1, 'M')); - } - return true; - }, - pageDown: function pageDown() { - if (!this.widget) { - return false; - } - var d = this._dates[0] || this.getMoment(); - if (this.widget.find('.datepicker').is(':visible')) { - this.date(d.clone().add(1, 'M')); - } - return true; - }, - enter: function enter() { - if (!this.widget) { - return false; - } - this.hide(); - return true; - }, - escape: function escape() { - if (!this.widget) { - return false; - } - this.hide(); - return true; - }, - 'control space': function controlSpace() { - if (!this.widget) { - return false; - } - if (this.widget.find('.timepicker').is(':visible')) { - this.widget.find('.btn[data-action="togglePeriod"]').click(); - } - return true; - }, - t: function t() { - if (!this.widget) { - return false; - } - this.date(this.getMoment()); - return true; - }, - 'delete': function _delete() { - if (!this.widget) { - return false; - } - this.clear(); - return true; - } - }, - debug: false, - allowInputToggle: false, - disabledTimeIntervals: false, - disabledHours: false, - enabledHours: false, - viewDate: false, - allowMultidate: false, - multidateSeparator: ',' - }; - - // ReSharper restore InconsistentNaming - - // ReSharper disable once DeclarationHides - // ReSharper disable once InconsistentNaming - - var DateTimePicker = function () { - /** @namespace eData.dateOptions */ - /** @namespace moment.tz */ - - function DateTimePicker(element, options) { - _classCallCheck(this, DateTimePicker); - - this._options = this._getOptions(options); - this._element = element; - this._dates = []; - this._datesFormatted = []; - this._viewDate = null; - this.unset = true; - this.component = false; - this.widget = false; - this.use24Hours = null; - this.actualFormat = null; - this.parseFormats = null; - this.currentViewMode = null; - - this._int(); - } - - /** - * @return {string} - */ - - - //private - - DateTimePicker.prototype._int = function _int() { - var targetInput = this._element.data('target-input'); - if (this._element.is('input')) { - this.input = this._element; - } else if (targetInput !== undefined) { - if (targetInput === 'nearest') { - this.input = this._element.find('input'); - } else { - this.input = $(targetInput); - } - } - - this._dates = []; - this._dates[0] = this.getMoment(); - this._viewDate = this.getMoment().clone(); - - $.extend(true, this._options, this._dataToOptions()); - - this.options(this._options); - - this._initFormatting(); - - if (this.input !== undefined && this.input.is('input') && this.input.val().trim().length !== 0) { - this._setValue(this._parseInputDate(this.input.val().trim()), 0); - } else if (this._options.defaultDate && this.input !== undefined && this.input.attr('placeholder') === undefined) { - this._setValue(this._options.defaultDate, 0); - } - if (this._options.inline) { - this.show(); - } - }; - - DateTimePicker.prototype._update = function _update() { - if (!this.widget) { - return; - } - this._fillDate(); - this._fillTime(); - }; - - DateTimePicker.prototype._setValue = function _setValue(targetMoment, index) { - var oldDate = this.unset ? null : this._dates[index]; - var outpValue = ''; - // case of calling setValue(null or false) - if (!targetMoment) { - if (!this._options.allowMultidate || this._dates.length === 1) { - this.unset = true; - this._dates = []; - this._datesFormatted = []; - } else { - outpValue = this._element.data('date') + ','; - outpValue = outpValue.replace(oldDate.format(this.actualFormat) + ',', '').replace(',,', '').replace(/,\s*$/, ''); - this._dates.splice(index, 1); - this._datesFormatted.splice(index, 1); - } - if (this.input !== undefined) { - this.input.val(outpValue); - this.input.trigger('input'); - } - this._element.data('date', outpValue); - this._notifyEvent({ - type: DateTimePicker.Event.CHANGE, - date: false, - oldDate: oldDate - }); - this._update(); - return; - } - - targetMoment = targetMoment.clone().locale(this._options.locale); - - if (this._hasTimeZone()) { - targetMoment.tz(this._options.timeZone); - } - - if (this._options.stepping !== 1) { - targetMoment.minutes(Math.round(targetMoment.minutes() / this._options.stepping) * this._options.stepping).seconds(0); - } - - if (this._isValid(targetMoment)) { - this._dates[index] = targetMoment; - this._datesFormatted[index] = targetMoment.format('YYYY-MM-DD'); - this._viewDate = targetMoment.clone(); - if (this._options.allowMultidate && this._dates.length > 1) { - for (var i = 0; i < this._dates.length; i++) { - outpValue += '' + this._dates[i].format(this.actualFormat) + this._options.multidateSeparator; - } - outpValue = outpValue.replace(/,\s*$/, ''); - } else { - outpValue = this._dates[index].format(this.actualFormat); - } - if (this.input !== undefined) { - this.input.val(outpValue); - this.input.trigger('input'); - } - this._element.data('date', outpValue); - - this.unset = false; - this._update(); - this._notifyEvent({ - type: DateTimePicker.Event.CHANGE, - date: this._dates[index].clone(), - oldDate: oldDate - }); - } else { - if (!this._options.keepInvalid) { - if (this.input !== undefined) { - this.input.val('' + (this.unset ? '' : this._dates[index].format(this.actualFormat))); - this.input.trigger('input'); - } - } else { - this._notifyEvent({ - type: DateTimePicker.Event.CHANGE, - date: targetMoment, - oldDate: oldDate - }); - } - this._notifyEvent({ - type: DateTimePicker.Event.ERROR, - date: targetMoment, - oldDate: oldDate - }); - } - }; - - DateTimePicker.prototype._change = function _change(e) { - var val = $(e.target).val().trim(), - parsedDate = val ? this._parseInputDate(val) : null; - this._setValue(parsedDate, 0); // Odoo FIX: if a valid date is replaced by an invalid one, lib will crash, see https://github.com/tempusdominus/bootstrap-4/issues/223 - e.stopImmediatePropagation(); - return false; - }; - - //noinspection JSMethodCanBeStatic - - - DateTimePicker.prototype._getOptions = function _getOptions(options) { - options = $.extend(true, {}, Default, options); - return options; - }; - - DateTimePicker.prototype._hasTimeZone = function _hasTimeZone() { - return moment.tz !== undefined && this._options.timeZone !== undefined && this._options.timeZone !== null && this._options.timeZone !== ''; - }; - - DateTimePicker.prototype._isEnabled = function _isEnabled(granularity) { - if (typeof granularity !== 'string' || granularity.length > 1) { - throw new TypeError('isEnabled expects a single character string parameter'); - } - switch (granularity) { - case 'y': - return this.actualFormat.indexOf('Y') !== -1; - case 'M': - return this.actualFormat.indexOf('M') !== -1; - case 'd': - return this.actualFormat.toLowerCase().indexOf('d') !== -1; - case 'h': - case 'H': - return this.actualFormat.toLowerCase().indexOf('h') !== -1; - case 'm': - return this.actualFormat.indexOf('m') !== -1; - case 's': - return this.actualFormat.indexOf('s') !== -1; - case 'a': - case 'A': - return this.actualFormat.toLowerCase().indexOf('a') !== -1; - default: - return false; - } - }; - - DateTimePicker.prototype._hasTime = function _hasTime() { - return this._isEnabled('h') || this._isEnabled('m') || this._isEnabled('s'); - }; - - DateTimePicker.prototype._hasDate = function _hasDate() { - return this._isEnabled('y') || this._isEnabled('M') || this._isEnabled('d'); - }; - - DateTimePicker.prototype._dataToOptions = function _dataToOptions() { - var eData = this._element.data(); - var dataOptions = {}; - - if (eData.dateOptions && eData.dateOptions instanceof Object) { - dataOptions = $.extend(true, dataOptions, eData.dateOptions); - } - - $.each(this._options, function (key) { - var attributeName = 'date' + key.charAt(0).toUpperCase() + key.slice(1); //todo data api key - if (eData[attributeName] !== undefined) { - dataOptions[key] = eData[attributeName]; - } else { - delete dataOptions[key]; - } - }); - return dataOptions; - }; - - DateTimePicker.prototype._notifyEvent = function _notifyEvent(e) { - // /!\ ODOO FIX: these next conditions have been modified by odoo - // FIXME should write a test about the tricky case this handles - if (e.type === DateTimePicker.Event.CHANGE) { - if (!e.date && !e.oldDate) { - return; - } - // check _isUTC flag to ensure that we are not comparing apples and oranges - var bothUTC = e.date && e.oldDate && e.date._isUTC === e.oldDate._isUTC; - if (bothUTC && e.date.isSame(e.oldDate)) { - return; - } - } - this._element.trigger(e); - }; - - DateTimePicker.prototype._viewUpdate = function _viewUpdate(e) { - if (e === 'y') { - e = 'YYYY'; - } - this._notifyEvent({ - type: DateTimePicker.Event.UPDATE, - change: e, - viewDate: this._viewDate.clone() - }); - }; - - DateTimePicker.prototype._showMode = function _showMode(dir) { - if (!this.widget) { - return; - } - if (dir) { - this.currentViewMode = Math.max(MinViewModeNumber, Math.min(3, this.currentViewMode + dir)); - } - this.widget.find('.datepicker > div').hide().filter('.datepicker-' + DatePickerModes[this.currentViewMode].CLASS_NAME).show(); - }; - - DateTimePicker.prototype._isInDisabledDates = function _isInDisabledDates(testDate) { - return this._options.disabledDates[testDate.format('YYYY-MM-DD')] === true; - }; - - DateTimePicker.prototype._isInEnabledDates = function _isInEnabledDates(testDate) { - return this._options.enabledDates[testDate.format('YYYY-MM-DD')] === true; - }; - - DateTimePicker.prototype._isInDisabledHours = function _isInDisabledHours(testDate) { - return this._options.disabledHours[testDate.format('H')] === true; - }; - - DateTimePicker.prototype._isInEnabledHours = function _isInEnabledHours(testDate) { - return this._options.enabledHours[testDate.format('H')] === true; - }; - - DateTimePicker.prototype._isValid = function _isValid(targetMoment, granularity) { - if (!targetMoment.isValid()) { - return false; - } - if (this._options.disabledDates && granularity === 'd' && this._isInDisabledDates(targetMoment)) { - return false; - } - if (this._options.enabledDates && granularity === 'd' && !this._isInEnabledDates(targetMoment)) { - return false; - } - if (this._options.minDate && targetMoment.isBefore(this._options.minDate, granularity)) { - return false; - } - if (this._options.maxDate && targetMoment.isAfter(this._options.maxDate, granularity)) { - return false; - } - if (this._options.daysOfWeekDisabled && granularity === 'd' && this._options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) { - return false; - } - if (this._options.disabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && this._isInDisabledHours(targetMoment)) { - return false; - } - if (this._options.enabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && !this._isInEnabledHours(targetMoment)) { - return false; - } - if (this._options.disabledTimeIntervals && (granularity === 'h' || granularity === 'm' || granularity === 's')) { - var found = false; - $.each(this._options.disabledTimeIntervals, function () { - if (targetMoment.isBetween(this[0], this[1])) { - found = true; - return false; - } - }); - if (found) { - return false; - } - } - return true; - }; - - DateTimePicker.prototype._parseInputDate = function _parseInputDate(inputDate) { - if (this._options.parseInputDate === undefined) { - if (!moment.isMoment(inputDate)) { - inputDate = this.getMoment(inputDate); - } - } else { - inputDate = this._options.parseInputDate(inputDate); - } - //inputDate.locale(this.options.locale); - return inputDate; - }; - - DateTimePicker.prototype._keydown = function _keydown(e) { - var handler = null, - index = void 0, - index2 = void 0, - keyBindKeys = void 0, - allModifiersPressed = void 0; - var pressedKeys = [], - pressedModifiers = {}, - currentKey = e.which, - 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; - } - } - } - - for (index in this._options.keyBinds) { - if (this._options.keyBinds.hasOwnProperty(index) && typeof this._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 = this._options.keyBinds[index]; - break; - } - } - } - } - - if (handler) { - if (handler.call(this)) { - e.stopPropagation(); - e.preventDefault(); - } - } - }; - - //noinspection JSMethodCanBeStatic,SpellCheckingInspection - - - DateTimePicker.prototype._keyup = function _keyup(e) { - keyState[e.which] = 'r'; - if (keyPressHandled[e.which]) { - keyPressHandled[e.which] = false; - e.stopPropagation(); - e.preventDefault(); - } - }; - - DateTimePicker.prototype._indexGivenDates = function _indexGivenDates(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 = {}, - self = this; - $.each(givenDatesArray, function () { - var dDate = self._parseInputDate(this); - if (dDate.isValid()) { - givenDatesIndexed[dDate.format('YYYY-MM-DD')] = true; - } - }); - return Object.keys(givenDatesIndexed).length ? givenDatesIndexed : false; - }; - - DateTimePicker.prototype._indexGivenHours = function _indexGivenHours(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; - }; - - DateTimePicker.prototype._initFormatting = function _initFormatting() { - var format = this._options.format || 'L LT', - self = this; - - this.actualFormat = format.replace(/(\[[^\[]*])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput) { - return self._dates[0].localeData().longDateFormat(formatInput) || formatInput; //todo taking the first date should be ok - }); - - this.parseFormats = this._options.extraFormats ? this._options.extraFormats.slice() : []; - if (this.parseFormats.indexOf(format) < 0 && this.parseFormats.indexOf(this.actualFormat) < 0) { - this.parseFormats.push(this.actualFormat); - } - - this.use24Hours = this.actualFormat.toLowerCase().indexOf('a') < 1 && this.actualFormat.replace(/\[.*?]/g, '').indexOf('h') < 1; - - if (this._isEnabled('y')) { - MinViewModeNumber = 2; - } - if (this._isEnabled('M')) { - MinViewModeNumber = 1; - } - if (this._isEnabled('d')) { - MinViewModeNumber = 0; - } - - this.currentViewMode = Math.max(MinViewModeNumber, this.currentViewMode); - - if (!this.unset) { - this._setValue(this._dates[0], 0); - } - }; - - DateTimePicker.prototype._getLastPickedDate = function _getLastPickedDate() { - return this._dates[this._getLastPickedDateIndex()] || this.getMoment(); // FIXME changed by Odoo - }; - - DateTimePicker.prototype._getLastPickedDateIndex = function _getLastPickedDateIndex() { - return this._dates.length - 1; - }; - - //public - - - DateTimePicker.prototype.getMoment = function getMoment(d, f) { // ODOO FIX: give optional format - var returnMoment = void 0; - - // ODOO FIX: default to original `parseFormats` attribute - if (!f) { - f = this.parseFormats; - } - - if (d === undefined || d === null) { - returnMoment = moment(); //TODO should this use format? and locale? - } else if (this._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, f, this._options.locale, this._options.useStrict, this._options.timeZone); // ODOO FIX: use format argument - } else { - returnMoment = moment(d, f, this._options.locale, this._options.useStrict); // ODOO FIX: use format argument - } - - if (this._hasTimeZone()) { - returnMoment.tz(this._options.timeZone); - } - - return returnMoment; - }; - - DateTimePicker.prototype.toggle = function toggle() { - return this.widget ? this.hide() : this.show(); - }; - - DateTimePicker.prototype.ignoreReadonly = function ignoreReadonly(_ignoreReadonly) { - if (arguments.length === 0) { - return this._options.ignoreReadonly; - } - if (typeof _ignoreReadonly !== 'boolean') { - throw new TypeError('ignoreReadonly () expects a boolean parameter'); - } - this._options.ignoreReadonly = _ignoreReadonly; - }; - - DateTimePicker.prototype.options = function options(newOptions) { - if (arguments.length === 0) { - return $.extend(true, {}, this._options); - } - - if (!(newOptions instanceof Object)) { - throw new TypeError('options() this.options parameter should be an object'); - } - $.extend(true, this._options, newOptions); - var self = this; - $.each(this._options, function (key, value) { - if (self[key] !== undefined) { - self[key](value); - } - }); - }; - - DateTimePicker.prototype.date = function date(newDate, index) { - index = index || 0; - if (arguments.length === 0) { - if (this.unset) { - return null; - } - if (this._options.allowMultidate) { - return this._dates.join(this._options.multidateSeparator); - } else { - return this._dates[index].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]'); - } - - this._setValue(newDate === null ? null : this._parseInputDate(newDate), index); - }; - - DateTimePicker.prototype.format = function format(newFormat) { - if (arguments.length === 0) { - return this._options.format; - } - - if (typeof newFormat !== 'string' && (typeof newFormat !== 'boolean' || newFormat !== false)) { - throw new TypeError('format() expects a string or boolean:false parameter ' + newFormat); - } - - this._options.format = newFormat; - if (this.actualFormat) { - this._initFormatting(); // reinitialize formatting - } - }; - - DateTimePicker.prototype.timeZone = function timeZone(newZone) { - if (arguments.length === 0) { - return this._options.timeZone; - } - - if (typeof newZone !== 'string') { - throw new TypeError('newZone() expects a string parameter'); - } - - this._options.timeZone = newZone; - }; - - DateTimePicker.prototype.dayViewHeaderFormat = function dayViewHeaderFormat(newFormat) { - if (arguments.length === 0) { - return this._options.dayViewHeaderFormat; - } - - if (typeof newFormat !== 'string') { - throw new TypeError('dayViewHeaderFormat() expects a string parameter'); - } - - this._options.dayViewHeaderFormat = newFormat; - }; - - DateTimePicker.prototype.extraFormats = function extraFormats(formats) { - if (arguments.length === 0) { - return this._options.extraFormats; - } - - if (formats !== false && !(formats instanceof Array)) { - throw new TypeError('extraFormats() expects an array or false parameter'); - } - - this._options.extraFormats = formats; - if (this.parseFormats) { - this._initFormatting(); // reinit formatting - } - }; - - DateTimePicker.prototype.disabledDates = function disabledDates(dates) { - if (arguments.length === 0) { - return this._options.disabledDates ? $.extend({}, this._options.disabledDates) : this._options.disabledDates; - } - - if (!dates) { - this._options.disabledDates = false; - this._update(); - return true; - } - if (!(dates instanceof Array)) { - throw new TypeError('disabledDates() expects an array parameter'); - } - this._options.disabledDates = this._indexGivenDates(dates); - this._options.enabledDates = false; - this._update(); - }; - - DateTimePicker.prototype.enabledDates = function enabledDates(dates) { - if (arguments.length === 0) { - return this._options.enabledDates ? $.extend({}, this._options.enabledDates) : this._options.enabledDates; - } - - if (!dates) { - this._options.enabledDates = false; - this._update(); - return true; - } - if (!(dates instanceof Array)) { - throw new TypeError('enabledDates() expects an array parameter'); - } - this._options.enabledDates = this._indexGivenDates(dates); - this._options.disabledDates = false; - this._update(); - }; - - DateTimePicker.prototype.daysOfWeekDisabled = function daysOfWeekDisabled(_daysOfWeekDisabled) { - if (arguments.length === 0) { - return this._options.daysOfWeekDisabled.splice(0); - } - - if (typeof _daysOfWeekDisabled === 'boolean' && !_daysOfWeekDisabled) { - this._options.daysOfWeekDisabled = false; - this._update(); - return true; - } - - if (!(_daysOfWeekDisabled instanceof Array)) { - throw new TypeError('daysOfWeekDisabled() expects an array parameter'); - } - this._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 (this._options.useCurrent && !this._options.keepInvalid) { - for (var i = 0; i < this._dates.length; i++) { - var tries = 0; - while (!this._isValid(this._dates[i], 'd')) { - this._dates[i].add(1, 'd'); - if (tries === 31) { - throw 'Tried 31 times to find a valid date'; - } - tries++; - } - this._setValue(this._dates[i], i); - } - } - this._update(); - }; - - DateTimePicker.prototype.maxDate = function maxDate(_maxDate) { - if (arguments.length === 0) { - return this._options.maxDate ? this._options.maxDate.clone() : this._options.maxDate; - } - - if (typeof _maxDate === 'boolean' && _maxDate === false) { - this._options.maxDate = false; - this._update(); - return true; - } - - if (typeof _maxDate === 'string') { - if (_maxDate === 'now' || _maxDate === 'moment') { - _maxDate = this.getMoment(); - } - } - - var parsedDate = this._parseInputDate(_maxDate); - - if (!parsedDate.isValid()) { - throw new TypeError('maxDate() Could not parse date parameter: ' + _maxDate); - } - if (this._options.minDate && parsedDate.isBefore(this._options.minDate)) { - throw new TypeError('maxDate() date parameter is before this.options.minDate: ' + parsedDate.format(this.actualFormat)); - } - this._options.maxDate = parsedDate; - for (var i = 0; i < this._dates.length; i++) { - if (this._options.useCurrent && !this._options.keepInvalid && this._dates[i].isAfter(_maxDate)) { - this._setValue(this._options.maxDate, i); - } - } - if (this._viewDate.isAfter(parsedDate)) { - this._viewDate = parsedDate.clone().subtract(this._options.stepping, 'm'); - } - this._update(); - }; - - DateTimePicker.prototype.minDate = function minDate(_minDate) { - if (arguments.length === 0) { - return this._options.minDate ? this._options.minDate.clone() : this._options.minDate; - } - - if (typeof _minDate === 'boolean' && _minDate === false) { - this._options.minDate = false; - this._update(); - return true; - } - - if (typeof _minDate === 'string') { - if (_minDate === 'now' || _minDate === 'moment') { - _minDate = this.getMoment(); - } - } - - var parsedDate = this._parseInputDate(_minDate); - - if (!parsedDate.isValid()) { - throw new TypeError('minDate() Could not parse date parameter: ' + _minDate); - } - if (this._options.maxDate && parsedDate.isAfter(this._options.maxDate)) { - throw new TypeError('minDate() date parameter is after this.options.maxDate: ' + parsedDate.format(this.actualFormat)); - } - this._options.minDate = parsedDate; - for (var i = 0; i < this._dates.length; i++) { - if (this._options.useCurrent && !this._options.keepInvalid && this._dates[i].isBefore(_minDate)) { - this._setValue(this._options.minDate, i); - } - } - if (this._viewDate.isBefore(parsedDate)) { - this._viewDate = parsedDate.clone().add(this._options.stepping, 'm'); - } - this._update(); - }; - - DateTimePicker.prototype.defaultDate = function defaultDate(_defaultDate) { - if (arguments.length === 0) { - return this._options.defaultDate ? this._options.defaultDate.clone() : this._options.defaultDate; - } - if (!_defaultDate) { - this._options.defaultDate = false; - return true; - } - - if (typeof _defaultDate === 'string') { - if (_defaultDate === 'now' || _defaultDate === 'moment') { - _defaultDate = this.getMoment(); - } else { - _defaultDate = this.getMoment(_defaultDate); - } - } - - var parsedDate = this._parseInputDate(_defaultDate); - if (!parsedDate.isValid()) { - throw new TypeError('defaultDate() Could not parse date parameter: ' + _defaultDate); - } - if (!this._isValid(parsedDate)) { - throw new TypeError('defaultDate() date passed is invalid according to component setup validations'); - } - - this._options.defaultDate = parsedDate; - - if (this._options.defaultDate && this._options.inline || this.input !== undefined && this.input.val().trim() === '') { - this._setValue(this._options.defaultDate, 0); - } - }; - - DateTimePicker.prototype.locale = function locale(_locale) { - if (arguments.length === 0) { - return this._options.locale; - } - - if (!moment.localeData(_locale)) { - throw new TypeError('locale() locale ' + _locale + ' is not loaded from moment locales!'); - } - - this._options.locale = _locale; - - for (var i = 0; i < this._dates.length; i++) { - this._dates[i].locale(this._options.locale); - } - this._viewDate.locale(this._options.locale); - - if (this.actualFormat) { - this._initFormatting(); // reinitialize formatting - } - if (this.widget) { - this.hide(); - this.show(); - } - }; - - DateTimePicker.prototype.stepping = function stepping(_stepping) { - if (arguments.length === 0) { - return this._options.stepping; - } - - _stepping = parseInt(_stepping, 10); - if (isNaN(_stepping) || _stepping < 1) { - _stepping = 1; - } - this._options.stepping = _stepping; - }; - - DateTimePicker.prototype.useCurrent = function useCurrent(_useCurrent) { - var useCurrentOptions = ['year', 'month', 'day', 'hour', 'minute']; - if (arguments.length === 0) { - return this._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(', ')); - } - this._options.useCurrent = _useCurrent; - }; - - DateTimePicker.prototype.collapse = function collapse(_collapse) { - if (arguments.length === 0) { - return this._options.collapse; - } - - if (typeof _collapse !== 'boolean') { - throw new TypeError('collapse() expects a boolean parameter'); - } - if (this._options.collapse === _collapse) { - return true; - } - this._options.collapse = _collapse; - if (this.widget) { - this.hide(); - this.show(); - } - }; - - DateTimePicker.prototype.icons = function icons(_icons) { - if (arguments.length === 0) { - return $.extend({}, this._options.icons); - } - - if (!(_icons instanceof Object)) { - throw new TypeError('icons() expects parameter to be an Object'); - } - - $.extend(this._options.icons, _icons); - - if (this.widget) { - this.hide(); - this.show(); - } - }; - - DateTimePicker.prototype.tooltips = function tooltips(_tooltips) { - if (arguments.length === 0) { - return $.extend({}, this._options.tooltips); - } - - if (!(_tooltips instanceof Object)) { - throw new TypeError('tooltips() expects parameter to be an Object'); - } - $.extend(this._options.tooltips, _tooltips); - if (this.widget) { - this.hide(); - this.show(); - } - }; - - DateTimePicker.prototype.useStrict = function useStrict(_useStrict) { - if (arguments.length === 0) { - return this._options.useStrict; - } - - if (typeof _useStrict !== 'boolean') { - throw new TypeError('useStrict() expects a boolean parameter'); - } - this._options.useStrict = _useStrict; - }; - - DateTimePicker.prototype.sideBySide = function sideBySide(_sideBySide) { - if (arguments.length === 0) { - return this._options.sideBySide; - } - - if (typeof _sideBySide !== 'boolean') { - throw new TypeError('sideBySide() expects a boolean parameter'); - } - this._options.sideBySide = _sideBySide; - if (this.widget) { - this.hide(); - this.show(); - } - }; - - DateTimePicker.prototype.viewMode = function viewMode(_viewMode) { - if (arguments.length === 0) { - return this._options.viewMode; - } - - if (typeof _viewMode !== 'string') { - throw new TypeError('viewMode() expects a string parameter'); - } - - if (DateTimePicker.ViewModes.indexOf(_viewMode) === -1) { - throw new TypeError('viewMode() parameter must be one of (' + DateTimePicker.ViewModes.join(', ') + ') value'); - } - - this._options.viewMode = _viewMode; - this.currentViewMode = Math.max(DateTimePicker.ViewModes.indexOf(_viewMode) - 1, DateTimePicker.MinViewModeNumber); - - this._showMode(); - }; - - DateTimePicker.prototype.calendarWeeks = function calendarWeeks(_calendarWeeks) { - if (arguments.length === 0) { - return this._options.calendarWeeks; - } - - if (typeof _calendarWeeks !== 'boolean') { - throw new TypeError('calendarWeeks() expects parameter to be a boolean value'); - } - - this._options.calendarWeeks = _calendarWeeks; - this._update(); - }; - - DateTimePicker.prototype.buttons = function buttons(_buttons) { - if (arguments.length === 0) { - return $.extend({}, this._options.buttons); - } - - if (!(_buttons instanceof Object)) { - throw new TypeError('buttons() expects parameter to be an Object'); - } - - $.extend(this._options.buttons, _buttons); - - if (typeof this._options.buttons.showToday !== 'boolean') { - throw new TypeError('buttons.showToday expects a boolean parameter'); - } - if (typeof this._options.buttons.showClear !== 'boolean') { - throw new TypeError('buttons.showClear expects a boolean parameter'); - } - if (typeof this._options.buttons.showClose !== 'boolean') { - throw new TypeError('buttons.showClose expects a boolean parameter'); - } - - if (this.widget) { - this.hide(); - this.show(); - } - }; - - DateTimePicker.prototype.keepOpen = function keepOpen(_keepOpen) { - if (arguments.length === 0) { - return this._options.keepOpen; - } - - if (typeof _keepOpen !== 'boolean') { - throw new TypeError('keepOpen() expects a boolean parameter'); - } - - this._options.keepOpen = _keepOpen; - }; - - DateTimePicker.prototype.focusOnShow = function focusOnShow(_focusOnShow) { - if (arguments.length === 0) { - return this._options.focusOnShow; - } - - if (typeof _focusOnShow !== 'boolean') { - throw new TypeError('focusOnShow() expects a boolean parameter'); - } - - this._options.focusOnShow = _focusOnShow; - }; - - DateTimePicker.prototype.inline = function inline(_inline) { - if (arguments.length === 0) { - return this._options.inline; - } - - if (typeof _inline !== 'boolean') { - throw new TypeError('inline() expects a boolean parameter'); - } - - this._options.inline = _inline; - }; - - DateTimePicker.prototype.clear = function clear() { - this._setValue(null); //todo - }; - - DateTimePicker.prototype.keyBinds = function keyBinds(_keyBinds) { - if (arguments.length === 0) { - return this._options.keyBinds; - } - - this._options.keyBinds = _keyBinds; - }; - - DateTimePicker.prototype.debug = function debug(_debug) { - if (typeof _debug !== 'boolean') { - throw new TypeError('debug() expects a boolean parameter'); - } - - this._options.debug = _debug; - }; - - DateTimePicker.prototype.allowInputToggle = function allowInputToggle(_allowInputToggle) { - if (arguments.length === 0) { - return this._options.allowInputToggle; - } - - if (typeof _allowInputToggle !== 'boolean') { - throw new TypeError('allowInputToggle() expects a boolean parameter'); - } - - this._options.allowInputToggle = _allowInputToggle; - }; - - DateTimePicker.prototype.keepInvalid = function keepInvalid(_keepInvalid) { - if (arguments.length === 0) { - return this._options.keepInvalid; - } - - if (typeof _keepInvalid !== 'boolean') { - throw new TypeError('keepInvalid() expects a boolean parameter'); - } - this._options.keepInvalid = _keepInvalid; - }; - - DateTimePicker.prototype.datepickerInput = function datepickerInput(_datepickerInput) { - if (arguments.length === 0) { - return this._options.datepickerInput; - } - - if (typeof _datepickerInput !== 'string') { - throw new TypeError('datepickerInput() expects a string parameter'); - } - - this._options.datepickerInput = _datepickerInput; - }; - - DateTimePicker.prototype.parseInputDate = function parseInputDate(_parseInputDate2) { - if (arguments.length === 0) { - return this._options.parseInputDate; - } - - if (typeof _parseInputDate2 !== 'function') { - throw new TypeError('parseInputDate() should be as function'); - } - - this._options.parseInputDate = _parseInputDate2; - }; - - DateTimePicker.prototype.disabledTimeIntervals = function disabledTimeIntervals(_disabledTimeIntervals) { - if (arguments.length === 0) { - return this._options.disabledTimeIntervals ? $.extend({}, this._options.disabledTimeIntervals) : this._options.disabledTimeIntervals; - } - - if (!_disabledTimeIntervals) { - this._options.disabledTimeIntervals = false; - this._update(); - return true; - } - if (!(_disabledTimeIntervals instanceof Array)) { - throw new TypeError('disabledTimeIntervals() expects an array parameter'); - } - this._options.disabledTimeIntervals = _disabledTimeIntervals; - this._update(); - }; - - DateTimePicker.prototype.disabledHours = function disabledHours(hours) { - if (arguments.length === 0) { - return this._options.disabledHours ? $.extend({}, this._options.disabledHours) : this._options.disabledHours; - } - - if (!hours) { - this._options.disabledHours = false; - this._update(); - return true; - } - if (!(hours instanceof Array)) { - throw new TypeError('disabledHours() expects an array parameter'); - } - this._options.disabledHours = this._indexGivenHours(hours); - this._options.enabledHours = false; - if (this._options.useCurrent && !this._options.keepInvalid) { - for (var i = 0; i < this._dates.length; i++) { - var tries = 0; - while (!this._isValid(this._dates[i], 'h')) { - this._dates[i].add(1, 'h'); - if (tries === 24) { - throw 'Tried 24 times to find a valid date'; - } - tries++; - } - this._setValue(this._dates[i], i); - } - } - this._update(); - }; - - DateTimePicker.prototype.enabledHours = function enabledHours(hours) { - if (arguments.length === 0) { - return this._options.enabledHours ? $.extend({}, this._options.enabledHours) : this._options.enabledHours; - } - - if (!hours) { - this._options.enabledHours = false; - this._update(); - return true; - } - if (!(hours instanceof Array)) { - throw new TypeError('enabledHours() expects an array parameter'); - } - this._options.enabledHours = this._indexGivenHours(hours); - this._options.disabledHours = false; - if (this._options.useCurrent && !this._options.keepInvalid) { - for (var i = 0; i < this._dates.length; i++) { - var tries = 0; - while (!this._isValid(this._dates[i], 'h')) { - this._dates[i].add(1, 'h'); - if (tries === 24) { - throw 'Tried 24 times to find a valid date'; - } - tries++; - } - this._setValue(this._dates[i], i); - } - } - this._update(); - }; - - DateTimePicker.prototype.viewDate = function viewDate(newDate) { - if (arguments.length === 0) { - return this._viewDate.clone(); - } - - if (!newDate) { - this._viewDate = (this._dates[0] || this.getMoment()).clone(); - return true; - } - - if (typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) { - throw new TypeError('viewDate() parameter must be one of [string, moment or Date]'); - } - - this._viewDate = this._parseInputDate(newDate); - this._viewUpdate(); - }; - - DateTimePicker.prototype.allowMultidate = function allowMultidate(_allowMultidate) { - if (typeof _allowMultidate !== 'boolean') { - throw new TypeError('allowMultidate() expects a boolean parameter'); - } - - this._options.allowMultidate = _allowMultidate; - }; - - DateTimePicker.prototype.multidateSeparator = function multidateSeparator(_multidateSeparator) { - if (arguments.length === 0) { - return this._options.multidateSeparator; - } - - if (typeof _multidateSeparator !== 'string' || _multidateSeparator.length > 1) { - throw new TypeError('multidateSeparator expects a single character string parameter'); - } - - this._options.multidateSeparator = _multidateSeparator; - }; - - _createClass(DateTimePicker, null, [{ - key: 'NAME', - get: function get() { - return NAME; - } - - /** - * @return {string} - */ - - }, { - key: 'DATA_KEY', - get: function get() { - return DATA_KEY; - } - - /** - * @return {string} - */ - - }, { - key: 'EVENT_KEY', - get: function get() { - return EVENT_KEY; - } - - /** - * @return {string} - */ - - }, { - key: 'DATA_API_KEY', - get: function get() { - return DATA_API_KEY; - } - }, { - key: 'DatePickerModes', - get: function get() { - return DatePickerModes; - } - }, { - key: 'ViewModes', - get: function get() { - return ViewModes; - } - - /** - * @return {number} - */ - - }, { - key: 'MinViewModeNumber', - get: function get() { - return MinViewModeNumber; - } - }, { - key: 'Event', - get: function get() { - return Event; - } - }, { - key: 'Selector', - get: function get() { - return Selector; - } - }, { - key: 'Default', - get: function get() { - return Default; - }, - set: function set(value) { - Default = value; - } - }, { - key: 'ClassName', - get: function get() { - return ClassName; - } - }]); - - return DateTimePicker; - }(); - - return DateTimePicker; -}(jQuery, moment); - -//noinspection JSUnusedGlobalSymbols -/* global DateTimePicker */ -var TempusDominusBootstrap4 = function ($) { - // eslint-disable-line no-unused-vars - // ReSharper disable once InconsistentNaming - var JQUERY_NO_CONFLICT = $.fn[DateTimePicker.NAME], - verticalModes = ['top', 'bottom', 'auto'], - horizontalModes = ['left', 'right', 'auto'], - toolbarPlacements = ['default', 'top', 'bottom'], - getSelectorFromElement = function getSelectorFromElement($element) { - var selector = $element.data('target'), - $selector = void 0; - - if (!selector) { - selector = $element.attr('href') || ''; - selector = /^#[a-z]/i.test(selector) ? selector : null; - } - $selector = $(selector); - if ($selector.length === 0) { - return $selector; - } - - if (!$selector.data(DateTimePicker.DATA_KEY)) { - $.extend({}, $selector.data(), $(this).data()); - } - - return $selector; - }; - - // ReSharper disable once InconsistentNaming - - var TempusDominusBootstrap4 = function (_DateTimePicker) { - _inherits(TempusDominusBootstrap4, _DateTimePicker); - - function TempusDominusBootstrap4(element, options) { - _classCallCheck(this, TempusDominusBootstrap4); - - var _this = _possibleConstructorReturn(this, _DateTimePicker.call(this, element, options)); - - _this._init(); - return _this; - } - - TempusDominusBootstrap4.prototype._init = function _init() { - if (this._element.hasClass('input-group')) { - var datepickerButton = this._element.find('.datepickerbutton'); - if (datepickerButton.length === 0) { - this.component = this._element.find('[data-toggle="datetimepicker"]'); - } else { - this.component = datepickerButton; - } - } - }; - - TempusDominusBootstrap4.prototype._getDatePickerTemplate = function _getDatePickerTemplate() { - var headTemplate = $('').append($('').append($('').addClass('prev').attr('data-action', 'previous').append($('').addClass(this._options.icons.previous))).append($('').addClass('picker-switch').attr('data-action', 'pickerSwitch').attr('colspan', '' + (this._options.calendarWeeks ? '6' : '5'))).append($('').addClass('next').attr('data-action', 'next').append($('').addClass(this._options.icons.next)))), - contTemplate = $('').append($('').append($('').attr('colspan', '' + (this._options.calendarWeeks ? '8' : '7')))); - - return [$('
').addClass('datepicker-days').append($('').addClass('table table-sm').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()))]; - }; - - TempusDominusBootstrap4.prototype._getTimePickerMainTemplate = function _getTimePickerMainTemplate() { - var topRow = $(''), - middleRow = $(''), - bottomRow = $(''); - - if (this._isEnabled('h')) { - topRow.append($('
').append($('').attr({ - href: '#', - tabindex: '-1', - 'title': this._options.tooltips.incrementHour - }).addClass('btn').attr('data-action', 'incrementHours').append($('').addClass(this._options.icons.up)))); - middleRow.append($('').append($('').addClass('timepicker-hour').attr({ - 'data-time-component': 'hours', - 'title': this._options.tooltips.pickHour - }).attr('data-action', 'showHours'))); - bottomRow.append($('').append($('').attr({ - href: '#', - tabindex: '-1', - 'title': this._options.tooltips.decrementHour - }).addClass('btn').attr('data-action', 'decrementHours').append($('').addClass(this._options.icons.down)))); - } - if (this._isEnabled('m')) { - if (this._isEnabled('h')) { - topRow.append($('').addClass('separator')); - middleRow.append($('').addClass('separator').html(':')); - bottomRow.append($('').addClass('separator')); - } - topRow.append($('').append($('').attr({ - href: '#', - tabindex: '-1', - 'title': this._options.tooltips.incrementMinute - }).addClass('btn').attr('data-action', 'incrementMinutes').append($('').addClass(this._options.icons.up)))); - middleRow.append($('').append($('').addClass('timepicker-minute').attr({ - 'data-time-component': 'minutes', - 'title': this._options.tooltips.pickMinute - }).attr('data-action', 'showMinutes'))); - bottomRow.append($('').append($('').attr({ - href: '#', - tabindex: '-1', - 'title': this._options.tooltips.decrementMinute - }).addClass('btn').attr('data-action', 'decrementMinutes').append($('').addClass(this._options.icons.down)))); - } - if (this._isEnabled('s')) { - if (this._isEnabled('m')) { - topRow.append($('').addClass('separator')); - middleRow.append($('').addClass('separator').html(':')); - bottomRow.append($('').addClass('separator')); - } - topRow.append($('').append($('').attr({ - href: '#', - tabindex: '-1', - 'title': this._options.tooltips.incrementSecond - }).addClass('btn').attr('data-action', 'incrementSeconds').append($('').addClass(this._options.icons.up)))); - middleRow.append($('').append($('').addClass('timepicker-second').attr({ - 'data-time-component': 'seconds', - 'title': this._options.tooltips.pickSecond - }).attr('data-action', 'showSeconds'))); - bottomRow.append($('').append($('').attr({ - href: '#', - tabindex: '-1', - 'title': this._options.tooltips.decrementSecond - }).addClass('btn').attr('data-action', 'decrementSeconds').append($('').addClass(this._options.icons.down)))); - } - - if (!this.use24Hours) { - topRow.append($('').addClass('separator')); - middleRow.append($('').append($('').addClass('separator')); - } - - return $('
').addClass('timepicker-picker').append($('').addClass('table-condensed').append([topRow, middleRow, bottomRow])); - }; - - TempusDominusBootstrap4.prototype._getTimePickerTemplate = function _getTimePickerTemplate() { - 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 = [this._getTimePickerMainTemplate()]; - - if (this._isEnabled('h')) { - ret.push(hoursView); - } - if (this._isEnabled('m')) { - ret.push(minutesView); - } - if (this._isEnabled('s')) { - ret.push(secondsView); - } - - return ret; - }; - - TempusDominusBootstrap4.prototype._getToolbar = function _getToolbar() { - var row = []; - if (this._options.buttons.showToday) { - row.push($('
').append($('').attr({ - href: '#', - tabindex: '-1', - 'data-action': 'today', - 'title': this._options.tooltips.today - }).append($('').addClass(this._options.icons.today)))); - } - if (!this._options.sideBySide && this._hasDate() && this._hasTime()) { - var title = void 0, - icon = void 0; - if (this._options.viewMode === 'times') { - title = this._options.tooltips.selectDate; - icon = this._options.icons.date; - } else { - title = this._options.tooltips.selectTime; - icon = this._options.icons.time; - } - row.push($('').append($('').attr({ - href: '#', - tabindex: '-1', - 'data-action': 'togglePicker', - 'title': title - }).append($('').addClass(icon)))); - } - if (this._options.buttons.showClear) { - row.push($('').append($('').attr({ - href: '#', - tabindex: '-1', - 'data-action': 'clear', - 'title': this._options.tooltips.clear - }).append($('').addClass(this._options.icons.clear)))); - } - if (this._options.buttons.showClose) { - row.push($('').append($('').attr({ - href: '#', - tabindex: '-1', - 'data-action': 'close', - 'title': this._options.tooltips.close - }).append($('').addClass(this._options.icons.close)))); - } - return row.length === 0 ? '' : $('').addClass('table-condensed').append($('').append($('').append(row))); - }; - - TempusDominusBootstrap4.prototype._getTemplate = function _getTemplate() { - var template = $('
').addClass('bootstrap-datetimepicker-widget dropdown-menu'), - dateView = $('
').addClass('datepicker').append(this._getDatePickerTemplate()), - timeView = $('
').addClass('timepicker').append(this._getTimePickerTemplate()), - content = $('
    ').addClass('list-unstyled'), - toolbar = $('
  • ').addClass('picker-switch' + (this._options.collapse ? ' accordion-toggle' : '')).append(this._getToolbar()); - - if (this._options.inline) { - template.removeClass('dropdown-menu'); - } - - if (this.use24Hours) { - template.addClass('usetwentyfour'); - } - if (this._isEnabled('s') && !this.use24Hours) { - template.addClass('wider'); - } - - if (this._options.sideBySide && this._hasDate() && this._hasTime()) { - template.addClass('timepicker-sbs'); - if (this._options.toolbarPlacement === 'top') { - template.append(toolbar); - } - template.append($('
    ').addClass('row').append(dateView.addClass('col-md-6')).append(timeView.addClass('col-md-6'))); - if (this._options.toolbarPlacement === 'bottom' || this._options.toolbarPlacement === 'default') { - template.append(toolbar); - } - return template; - } - - if (this._options.toolbarPlacement === 'top') { - content.append(toolbar); - } - if (this._hasDate()) { - content.append($('
  • ').addClass(this._options.collapse && this._hasTime() ? 'collapse' : '').addClass(this._options.collapse && this._hasTime() && this._options.viewMode === 'times' ? '' : 'show').append(dateView)); - } - if (this._options.toolbarPlacement === 'default') { - content.append(toolbar); - } - if (this._hasTime()) { - content.append($('
  • ').addClass(this._options.collapse && this._hasDate() ? 'collapse' : '').addClass(this._options.collapse && this._hasDate() && this._options.viewMode === 'times' ? 'show' : '').append(timeView)); - } - if (this._options.toolbarPlacement === 'bottom') { - content.append(toolbar); - } - return template.append(content); - }; - - TempusDominusBootstrap4.prototype._place = function _place(e) { - var self = e && e.data && e.data.picker || this, - vertical = self._options.widgetPositioning.vertical, - horizontal = self._options.widgetPositioning.horizontal, - parent = void 0; - var position = (self.component && self.component.length ? self.component : self._element).position(), - offset = (self.component && self.component.length ? self.component : self._element).offset(); - if (self._options.widgetParent) { - parent = self._options.widgetParent.append(self.widget); - } else if (self._element.is('input')) { - parent = self._element.after(self.widget).parent(); - } else if (self._options.inline) { - parent = self._element.append(self.widget); - return; - } else { - parent = self._element; - self._element.children().first().after(self.widget); - } - - // /!\ ODOO FIX: the 3 next lines have been *added* by odoo - var parentOffset = parent.offset(); - position.top = offset.top - parentOffset.top; - position.left = offset.left - parentOffset.left; - - // Top and bottom logic - if (vertical === 'auto') { - //noinspection JSValidateTypes - if (offset.top + self.widget.height() * 1.5 >= $(window).height() + $(window).scrollTop() && self.widget.height() + self._element.outerHeight() < offset.top) { - vertical = 'top'; - } else { - vertical = 'bottom'; - } - } - - // Left and right logic - if (horizontal === 'auto') { - if (parent.width() < offset.left + self.widget.outerWidth() / 2 && offset.left + self.widget.outerWidth() > $(window).width()) { - horizontal = 'right'; - } else { - horizontal = 'left'; - } - } - - if (vertical === 'top') { - self.widget.addClass('top').removeClass('bottom'); - } else { - self.widget.addClass('bottom').removeClass('top'); - } - - if (horizontal === 'right') { - self.widget.addClass('float-right'); - } else { - self.widget.removeClass('float-right'); - } - - // 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(); - } - - if (parent.length === 0) { - throw new Error('datetimepicker component should be placed within a relative positioned container'); - } - - self.widget.css({ - top: vertical === 'top' ? 'auto' : position.top + self._element.outerHeight() + 'px', - bottom: vertical === 'top' ? parent.outerHeight() - (parent === self._element ? 0 : position.top) + 'px' : 'auto', - left: horizontal === 'left' ? (parent === self._element ? 0 : position.left) + 'px' : 'auto', - right: horizontal === 'left' ? 'auto' : parent.outerWidth() - self._element.outerWidth() - (parent === self._element ? 0 : position.left) + 'px' - }); - }; - - TempusDominusBootstrap4.prototype._fillDow = function _fillDow() { - var row = $('
'), - currentDate = this._viewDate.clone().startOf('w').startOf('d'); - - if (this._options.calendarWeeks === true) { - row.append($(''); - if (this._options.calendarWeeks) { - row.append(''); - } - html.push(row); - } - clsName = ''; - if (currentDate.isBefore(this._viewDate, 'M')) { - clsName += ' old'; - } - if (currentDate.isAfter(this._viewDate, 'M')) { - clsName += ' new'; - } - if (this._options.allowMultidate) { - var index = this._datesFormatted.indexOf(currentDate.format('YYYY-MM-DD')); - if (index !== -1) { - if (currentDate.isSame(this._datesFormatted[index], 'd') && !this.unset) { - clsName += ' active'; - } - } - } else { - if (currentDate.isSame(this._getLastPickedDate(), 'd') && !this.unset) { - clsName += ' active'; - } - } - if (!this._isValid(currentDate, 'd')) { - clsName += ' disabled'; - } - // !! ODOO FIX START !! - if (currentDate.date() === now.date() && currentDate.month() === now.month() && currentDate.year() === now.year()) { - // !! ODOO FIX END !! - clsName += ' today'; - } - if (currentDate.day() === 0 || currentDate.day() === 6) { - clsName += ' weekend'; - } - row.append(''); - currentDate.add(1, 'd'); - } - - daysView.find('tbody').empty().append(html); - - this._updateMonths(); - - this._updateYears(); - - this._updateDecades(); - }; - - TempusDominusBootstrap4.prototype._fillHours = function _fillHours() { - var table = this.widget.find('.timepicker-hours table'), - currentHour = this._viewDate.clone().startOf('d'), - html = []; - var row = $(''); - - if (this._viewDate.hour() > 11 && !this.use24Hours) { - currentHour.hour(12); - } - while (currentHour.isSame(this._viewDate, 'd') && (this.use24Hours || this._viewDate.hour() < 12 && currentHour.hour() < 12 || this._viewDate.hour() > 11)) { - if (currentHour.hour() % 4 === 0) { - row = $(''); - html.push(row); - } - row.append(''); - currentHour.add(1, 'h'); - } - table.empty().append(html); - }; - - TempusDominusBootstrap4.prototype._fillMinutes = function _fillMinutes() { - var table = this.widget.find('.timepicker-minutes table'), - currentMinute = this._viewDate.clone().startOf('h'), - html = [], - step = this._options.stepping === 1 ? 5 : this._options.stepping; - var row = $(''); - - while (this._viewDate.isSame(currentMinute, 'h')) { - if (currentMinute.minute() % (step * 4) === 0) { - row = $(''); - html.push(row); - } - row.append(''); - currentMinute.add(step, 'm'); - } - table.empty().append(html); - }; - - TempusDominusBootstrap4.prototype._fillSeconds = function _fillSeconds() { - var table = this.widget.find('.timepicker-seconds table'), - currentSecond = this._viewDate.clone().startOf('m'), - html = []; - var row = $(''); - - while (this._viewDate.isSame(currentSecond, 'm')) { - if (currentSecond.second() % 20 === 0) { - row = $(''); - html.push(row); - } - row.append(''); - currentSecond.add(5, 's'); - } - - table.empty().append(html); - }; - - TempusDominusBootstrap4.prototype._fillTime = function _fillTime() { - var toggle = void 0, - newDate = void 0; - var timeComponents = this.widget.find('.timepicker span[data-time-component]'); - - if (!this.use24Hours) { - toggle = this.widget.find('.timepicker [data-action=togglePeriod]'); - newDate = this._getLastPickedDate().clone().add(this._getLastPickedDate().hours() >= 12 ? -12 : 12, 'h'); - - toggle.text(this._getLastPickedDate().format('A')); - - if (this._isValid(newDate, 'h')) { - toggle.removeClass('disabled'); - } else { - toggle.addClass('disabled'); - } - } - timeComponents.filter('[data-time-component=hours]').text(this._getLastPickedDate().format('' + (this.use24Hours ? 'HH' : 'hh'))); - timeComponents.filter('[data-time-component=minutes]').text(this._getLastPickedDate().format('mm')); - timeComponents.filter('[data-time-component=seconds]').text(this._getLastPickedDate().format('ss')); - - this._fillHours(); - this._fillMinutes(); - this._fillSeconds(); - }; - - TempusDominusBootstrap4.prototype._doAction = function _doAction(e, action) { - var lastPicked = this._getLastPickedDate(); - if ($(e.currentTarget).is('.disabled')) { - return false; - } - action = action || $(e.currentTarget).data('action'); - switch (action) { - case 'next': - { - var navFnc = DateTimePicker.DatePickerModes[this.currentViewMode].NAV_FUNCTION; - this._viewDate.add(DateTimePicker.DatePickerModes[this.currentViewMode].NAV_STEP, navFnc); - this._fillDate(); - this._viewUpdate(navFnc); - break; - } - case 'previous': - { - var _navFnc = DateTimePicker.DatePickerModes[this.currentViewMode].NAV_FUNCTION; - this._viewDate.subtract(DateTimePicker.DatePickerModes[this.currentViewMode].NAV_STEP, _navFnc); - this._fillDate(); - this._viewUpdate(_navFnc); - break; - } - case 'pickerSwitch': - this._showMode(1); - break; - case 'selectMonth': - { - var month = $(e.target).closest('tbody').find('span').index($(e.target)); - this._viewDate.month(month); - if (this.currentViewMode === DateTimePicker.MinViewModeNumber) { - this._setValue(lastPicked.clone().year(this._viewDate.year()).month(this._viewDate.month()), this._getLastPickedDateIndex()); - if (!this._options.inline) { - this.hide(); - } - } else { - this._showMode(-1); - this._fillDate(); - } - this._viewUpdate('M'); - break; - } - case 'selectYear': - { - var year = parseInt($(e.target).text(), 10) || 0; - this._viewDate.year(year); - if (this.currentViewMode === DateTimePicker.MinViewModeNumber) { - this._setValue(lastPicked.clone().year(this._viewDate.year()), this._getLastPickedDateIndex()); - if (!this._options.inline) { - this.hide(); - } - } else { - this._showMode(-1); - this._fillDate(); - } - this._viewUpdate('YYYY'); - break; - } - case 'selectDecade': - { - var _year = parseInt($(e.target).data('selection'), 10) || 0; - this._viewDate.year(_year); - if (this.currentViewMode === DateTimePicker.MinViewModeNumber) { - this._setValue(lastPicked.clone().year(this._viewDate.year()), this._getLastPickedDateIndex()); - if (!this._options.inline) { - this.hide(); - } - } else { - this._showMode(-1); - this._fillDate(); - } - this._viewUpdate('YYYY'); - break; - } - case 'selectDay': - { - var day = this._viewDate.clone(); - if ($(e.target).is('.old')) { - day.subtract(1, 'M'); - } - if ($(e.target).is('.new')) { - day.add(1, 'M'); - } - this._setValue(day.date(parseInt($(e.target).text(), 10)), this._getLastPickedDateIndex()); - if (!this._hasTime() && !this._options.keepOpen && !this._options.inline) { - this.hide(); - } - break; - } - case 'incrementHours': - { - var newDate = lastPicked.clone().add(1, 'h'); - if (this._isValid(newDate, 'h')) { - this._setValue(newDate, this._getLastPickedDateIndex()); - } - break; - } - case 'incrementMinutes': - { - var _newDate = lastPicked.clone().add(this._options.stepping, 'm'); - if (this._isValid(_newDate, 'm')) { - this._setValue(_newDate, this._getLastPickedDateIndex()); - } - break; - } - case 'incrementSeconds': - { - var _newDate2 = lastPicked.clone().add(1, 's'); - if (this._isValid(_newDate2, 's')) { - this._setValue(_newDate2, this._getLastPickedDateIndex()); - } - break; - } - case 'decrementHours': - { - var _newDate3 = lastPicked.clone().subtract(1, 'h'); - if (this._isValid(_newDate3, 'h')) { - this._setValue(_newDate3, this._getLastPickedDateIndex()); - } - break; - } - case 'decrementMinutes': - { - var _newDate4 = lastPicked.clone().subtract(this._options.stepping, 'm'); - if (this._isValid(_newDate4, 'm')) { - this._setValue(_newDate4, this._getLastPickedDateIndex()); - } - break; - } - case 'decrementSeconds': - { - var _newDate5 = lastPicked.clone().subtract(1, 's'); - if (this._isValid(_newDate5, 's')) { - this._setValue(_newDate5, this._getLastPickedDateIndex()); - } - break; - } - case 'togglePeriod': - { - this._setValue(lastPicked.clone().add(lastPicked.hours() >= 12 ? -12 : 12, 'h'), this._getLastPickedDateIndex()); - break; - } - case 'togglePicker': - { - var $this = $(e.target), - $link = $this.closest('a'), - $parent = $this.closest('ul'), - expanded = $parent.find('.show'), - closed = $parent.find('.collapse:not(.show)'), - $span = $this.is('span') ? $this : $this.find('span'); - var collapseData = void 0; - - if (expanded && expanded.length) { - collapseData = expanded.data('collapse'); - if (collapseData && collapseData.transitioning) { - return true; - } - 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('show'); - closed.addClass('show'); - } - $span.toggleClass(this._options.icons.time + ' ' + this._options.icons.date); - - if ($span.hasClass(this._options.icons.date)) { - $link.attr('title', this._options.tooltips.selectDate); - } else { - $link.attr('title', this._options.tooltips.selectTime); - } - } - } - break; - case 'showPicker': - this.widget.find('.timepicker > div:not(.timepicker-picker)').hide(); - this.widget.find('.timepicker .timepicker-picker').show(); - break; - case 'showHours': - this.widget.find('.timepicker .timepicker-picker').hide(); - this.widget.find('.timepicker .timepicker-hours').show(); - break; - case 'showMinutes': - this.widget.find('.timepicker .timepicker-picker').hide(); - this.widget.find('.timepicker .timepicker-minutes').show(); - break; - case 'showSeconds': - this.widget.find('.timepicker .timepicker-picker').hide(); - this.widget.find('.timepicker .timepicker-seconds').show(); - break; - case 'selectHour': - { - var hour = this.getMoment($(e.target).text(), this.use24Hours ? 'HH' : 'hh').hour(); // ODOO FIX: use moment format to get the proper value (not necessarily latn numbers) - - if (!this.use24Hours) { - if (lastPicked.hours() >= 12) { - if (hour !== 12) { - hour += 12; - } - } else { - if (hour === 12) { - hour = 0; - } - } - } - this._setValue(lastPicked.clone().hours(hour), this._getLastPickedDateIndex()); - if (!this._isEnabled('a') && !this._isEnabled('m') && !this._options.keepOpen && !this._options.inline) { - this.hide(); - } else { - this._doAction(e, 'showPicker'); - } - break; - } - case 'selectMinute': - this._setValue(lastPicked.clone().minutes(this.getMoment($(e.target).text(), 'mm').minute()), this._getLastPickedDateIndex()); // ODOO FIX: use moment format to get the proper value (not necessarily latn numbers) - if (!this._isEnabled('a') && !this._isEnabled('s') && !this._options.keepOpen && !this._options.inline) { - this.hide(); - } else { - this._doAction(e, 'showPicker'); - } - break; - case 'selectSecond': - this._setValue(lastPicked.clone().seconds(this.getMoment($(e.target).text(), 'ss').second()), this._getLastPickedDateIndex()); // ODOO FIX: use moment format to get the proper value (not necessarily latn numbers) - if (!this._isEnabled('a') && !this._options.keepOpen && !this._options.inline) { - this.hide(); - } else { - this._doAction(e, 'showPicker'); - } - break; - case 'clear': - this.clear(); - break; - case 'close': - this.hide(); - break; - case 'today': - { - var todaysDate = this.getMoment(); - if (this._isValid(todaysDate, 'd')) { - this._setValue(todaysDate, this._getLastPickedDateIndex()); - } - break; - } - } - return false; - }; - - //public - - - TempusDominusBootstrap4.prototype.hide = function hide() { - var transitioning = false; - if (!this.widget) { - return; - } - // Ignore event if in the middle of a picker transition - this.widget.find('.collapse').each(function () { - var collapseData = $(this).data('collapse'); - if (collapseData && collapseData.transitioning) { - transitioning = true; - return false; - } - return true; - }); - if (transitioning) { - return; - } - if (this.component && this.component.hasClass('btn')) { - this.component.toggleClass('active'); - } - this.widget.hide(); - - $(window).off('resize', this._place()); - this.widget.off('click', '[data-action]'); - this.widget.off('mousedown', false); - - this.widget.remove(); - this.widget = false; - - this._notifyEvent({ - type: DateTimePicker.Event.HIDE, - date: this._getLastPickedDate().clone() - }); - - if (this.input !== undefined) { - this.input.blur(); - } - - this._viewDate = this._getLastPickedDate().clone(); - }; - - TempusDominusBootstrap4.prototype.show = function show() { - var currentMoment = void 0; - var useCurrentGranularity = { - 'year': function year(m) { - return m.month(0).date(1).hours(0).seconds(0).minutes(0); - }, - 'month': function month(m) { - return m.date(1).hours(0).seconds(0).minutes(0); - }, - 'day': function day(m) { - return m.hours(0).seconds(0).minutes(0); - }, - 'hour': function hour(m) { - return m.seconds(0).minutes(0); - }, - 'minute': function minute(m) { - return m.seconds(0); - } - }; - - if (this.input !== undefined) { - if (this.input.prop('disabled') || !this._options.ignoreReadonly && this.input.prop('readonly') || this.widget) { - return; - } - if (this.input.val() !== undefined && this.input.val().trim().length !== 0) { - this._setValue(this._parseInputDate(this.input.val().trim()), 0); - } else if (this.unset && this._options.useCurrent) { - currentMoment = this.getMoment(); - if (typeof this._options.useCurrent === 'string') { - currentMoment = useCurrentGranularity[this._options.useCurrent](currentMoment); - } - this._setValue(currentMoment, 0); - } - } else if (this.unset && this._options.useCurrent) { - currentMoment = this.getMoment(); - if (typeof this._options.useCurrent === 'string') { - currentMoment = useCurrentGranularity[this._options.useCurrent](currentMoment); - } - this._setValue(currentMoment, 0); - } - - this.widget = this._getTemplate(); - - this._fillDow(); - this._fillMonths(); - - this.widget.find('.timepicker-hours').hide(); - this.widget.find('.timepicker-minutes').hide(); - this.widget.find('.timepicker-seconds').hide(); - - this._update(); - this._showMode(); - - $(window).on('resize', { picker: this }, this._place); - this.widget.on('click', '[data-action]', $.proxy(this._doAction, this)); // this handles clicks on the widget - this.widget.on('mousedown', false); - - if (this.component && this.component.hasClass('btn')) { - this.component.toggleClass('active'); - } - this._place(); - this.widget.show(); - if (this.input !== undefined && this._options.focusOnShow && !this.input.is(':focus')) { - this.input.focus(); - } - - this._notifyEvent({ - type: DateTimePicker.Event.SHOW - }); - }; - - TempusDominusBootstrap4.prototype.destroy = function destroy() { - this.hide(); - //todo doc off? - this._element.removeData(DateTimePicker.DATA_KEY); - this._element.removeData('date'); - }; - - TempusDominusBootstrap4.prototype.disable = function disable() { - this.hide(); - if (this.component && this.component.hasClass('btn')) { - this.component.addClass('disabled'); - } - if (this.input !== undefined) { - this.input.prop('disabled', true); //todo disable this/comp if input is null - } - }; - - TempusDominusBootstrap4.prototype.enable = function enable() { - if (this.component && this.component.hasClass('btn')) { - this.component.removeClass('disabled'); - } - if (this.input !== undefined) { - this.input.prop('disabled', false); //todo enable comp/this if input is null - } - }; - - TempusDominusBootstrap4.prototype.toolbarPlacement = function toolbarPlacement(_toolbarPlacement) { - if (arguments.length === 0) { - return this._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'); - } - this._options.toolbarPlacement = _toolbarPlacement; - - if (this.widget) { - this.hide(); - this.show(); - } - }; - - TempusDominusBootstrap4.prototype.widgetPositioning = function widgetPositioning(_widgetPositioning) { - if (arguments.length === 0) { - return $.extend({}, this._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(', ') + ')'); - } - this._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(', ') + ')'); - } - this._options.widgetPositioning.vertical = _widgetPositioning.vertical; - } - this._update(); - }; - - TempusDominusBootstrap4.prototype.widgetParent = function widgetParent(_widgetParent) { - if (arguments.length === 0) { - return this._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'); - } - - this._options.widgetParent = _widgetParent; - if (this.widget) { - this.hide(); - this.show(); - } - }; - - //static - - - TempusDominusBootstrap4._jQueryHandleThis = function _jQueryHandleThis(me, option, argument) { - var data = $(me).data(DateTimePicker.DATA_KEY); - if ((typeof option === 'undefined' ? 'undefined' : _typeof(option)) === 'object') { - $.extend({}, DateTimePicker.Default, option); - } - - if (!data) { - data = new TempusDominusBootstrap4($(me), option); - $(me).data(DateTimePicker.DATA_KEY, data); - } - - if (typeof option === 'string') { - if (data[option] === undefined) { - throw new Error('No method named "' + option + '"'); - } - if (argument === undefined) { - return data[option](); - } else { - return data[option](argument); - } - } - }; - - TempusDominusBootstrap4._jQueryInterface = function _jQueryInterface(option, argument) { - if (this.length === 1) { - return TempusDominusBootstrap4._jQueryHandleThis(this[0], option, argument); - } - return this.each(function () { - TempusDominusBootstrap4._jQueryHandleThis(this, option, argument); - }); - }; - - return TempusDominusBootstrap4; - }(DateTimePicker); - - /** - * ------------------------------------------------------------------------ - * jQuery - * ------------------------------------------------------------------------ - */ - - - $(document).on(DateTimePicker.Event.CLICK_DATA_API, DateTimePicker.Selector.DATA_TOGGLE, function () { - var $target = getSelectorFromElement($(this)); - if ($target.length === 0) { - return; - } - TempusDominusBootstrap4._jQueryInterface.call($target, 'toggle'); - }).on(DateTimePicker.Event.CHANGE, '.' + DateTimePicker.ClassName.INPUT, function (event) { - var $target = getSelectorFromElement($(this)); - if ($target.length === 0) { - return; - } - TempusDominusBootstrap4._jQueryInterface.call($target, '_change', event); - }).on(DateTimePicker.Event.BLUR, '.' + DateTimePicker.ClassName.INPUT, function (event) { - var $target = getSelectorFromElement($(this)), - config = $target.data(DateTimePicker.DATA_KEY); - if ($target.length === 0) { - return; - } - // /!\ ODOO FIX: check on 'config' existence added by odoo - if (config && config._options.debug || window.debug) { - return; - } - TempusDominusBootstrap4._jQueryInterface.call($target, 'hide', event); - }).on(DateTimePicker.Event.KEYDOWN, '.' + DateTimePicker.ClassName.INPUT, function (event) { - var $target = getSelectorFromElement($(this)); - if ($target.length === 0) { - return; - } - TempusDominusBootstrap4._jQueryInterface.call($target, '_keydown', event); - }).on(DateTimePicker.Event.KEYUP, '.' + DateTimePicker.ClassName.INPUT, function (event) { - var $target = getSelectorFromElement($(this)); - if ($target.length === 0) { - return; - } - TempusDominusBootstrap4._jQueryInterface.call($target, '_keyup', event); - }).on(DateTimePicker.Event.FOCUS, '.' + DateTimePicker.ClassName.INPUT, function (event) { - var $target = getSelectorFromElement($(this)), - config = $target.data(DateTimePicker.DATA_KEY); - if ($target.length === 0) { - return; - } - // /!\ ODOO FIX: check on 'config' existence added by odoo - if (!(config && config._options.allowInputToggle)) { - return; - } - TempusDominusBootstrap4._jQueryInterface.call($target, 'show', event); - }); - - $.fn[DateTimePicker.NAME] = TempusDominusBootstrap4._jQueryInterface; - $.fn[DateTimePicker.NAME].Constructor = TempusDominusBootstrap4; - $.fn[DateTimePicker.NAME].noConflict = function () { - $.fn[DateTimePicker.NAME] = JQUERY_NO_CONFLICT; - return TempusDominusBootstrap4._jQueryInterface; - }; - - return TempusDominusBootstrap4; -}(jQuery); - -}(); diff --git a/addons/web/static/lib/tempusdominus/tempusdominus.scss b/addons/web/static/lib/tempusdominus/tempusdominus.scss deleted file mode 100644 index b5de3afbdf3..00000000000 --- a/addons/web/static/lib/tempusdominus/tempusdominus.scss +++ /dev/null @@ -1,342 +0,0 @@ -$bs-datetimepicker-timepicker-font-size: var(--bs-datetimepicker-timepicker-font-size, 1.2em) !default; -$bs-datetimepicker-active-bg: var(--bs-datetimepicker-active-bg, #{$blue}) !default; -$bs-datetimepicker-active-color: var(--bs-datetimepicker-active-color, #{$white}) !default; -$bs-datetimepicker-border-radius: var(--bs-datetimepicker-border-radius, #{$border-radius}) !default; -$bs-datetimepicker-btn-hover-bg: var(--bs-datetimepicker-btn-hover-bg, #{$gray-200}) !default; -$bs-datetimepicker-disabled-color: var(--bs-datetimepicker-disabled-color, #{$gray-600}) !default; -$bs-datetimepicker-alternate-color: var(--bs-datetimepicker-alternate-color, #{$gray-600}) !default; -$bs-datetimepicker-secondary-border-color: var(--bs-datetimepicker-secondary-border-color, #ccc) !default; -$bs-datetimepicker-secondary-border-color-rgba: var(--bs-datetimepicker-secondary-border-color-rgba, rgba(0, 0, 0, 0.2)) !default; -$bs-datetimepicker-primary-border-color: var(--bs-datetimepicker-primary-border-color, white) !default; -$bs-datetimepicker-text-shadow: var(--bs-datetimepicker-text-shadow, 0 -1px 0 rgba(0, 0, 0, 0.25)) !default; - -.bootstrap-datetimepicker-widget { - list-style: none; - - &.dropdown-menu { - display: block; - margin: 2px 0; - padding: 4px; - width: 14rem; - - &.timepicker-sbs { - @media (min-width: map-get($grid-breakpoints, 'sm')) { - width: 38em; - } - - @media (min-width: map-get($grid-breakpoints, 'md')) { - width: 38em; - } - - @media (min-width: map-get($grid-breakpoints, 'lg')) { - 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; - } - } - - &.float-right { - &:before { - left: auto; - right: 6px; - } - - &:after { - left: auto; - right: 7px; - } - } - - &.wider { - width: 16rem; - } - } - - .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 { - @extend .sr-only; - content: "Increment Hours"; - } - - .btn[data-action="incrementMinutes"]::after { - @extend .sr-only; - content: "Increment Minutes"; - } - - .btn[data-action="decrementHours"]::after { - @extend .sr-only; - content: "Decrement Hours"; - } - - .btn[data-action="decrementMinutes"]::after { - @extend .sr-only; - content: "Decrement Minutes"; - } - - .btn[data-action="showHours"]::after { - @extend .sr-only; - content: "Show Hours"; - } - - .btn[data-action="showMinutes"]::after { - @extend .sr-only; - content: "Show Minutes"; - } - - .btn[data-action="togglePeriod"]::after { - @extend .sr-only; - content: "Toggle AM/PM"; - } - - .btn[data-action="clear"]::after { - @extend .sr-only; - content: "Clear the picker"; - } - - .btn[data-action="today"]::after { - @extend .sr-only; - content: "Set the date to today"; - } - - .picker-switch { - text-align: center; - - &::after { - @extend .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 { - @extend .sr-only; - content: "Previous Month"; - } - - &.next::after { - @extend .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; - } - } -} - -.input-group [data-toggle="datetimepicker"] { - cursor: pointer; -} diff --git a/addons/web/static/src/core/datetime/datetime_hook.js b/addons/web/static/src/core/datetime/datetime_hook.js index 0eec201353c..50d58ad9e62 100644 --- a/addons/web/static/src/core/datetime/datetime_hook.js +++ b/addons/web/static/src/core/datetime/datetime_hook.js @@ -1,440 +1,30 @@ -/** @odoo-module **/ +/** @odoo-module */ -import { markRaw, onPatched, onWillRender, reactive, useEffect, useEnv, useRef } from "@odoo/owl"; -import { areDatesEqual } from "../l10n/dates"; -import { usePopover } from "../popover/popover_hook"; -import { registry } from "../registry"; -import { ensureArray, zip, zipWith } from "../utils/arrays"; -import { deepCopy, shallowEqual } from "../utils/objects"; -import { DateTimePicker } from "./datetime_picker"; -import { DateTimePickerPopover } from "./datetime_picker_popover"; +import { onPatched, onWillRender, useEffect, useRef } from "@odoo/owl"; +import { usePopover } from "@web/core/popover/popover_hook"; +import { useService } from "@web/core/utils/hooks"; -/** - * @typedef {luxon.DateTime} DateTime - * - * @typedef DateTimePickerHookParams - * @property {string} [format] - * @property {(value: DateTimePickerProps["value"]) => any} [onChange] callback - * invoked every time the hook updates the reactive value, either through the inputs - * or the picker. - * @property {(value: DateTimePickerProps["value"]) => any} [onApply] callback - * invoked once the value is committed: this is either when all inputs received - * a "change" event or when the datetime picker popover has been closed. - * @property {DateTimePickerProps} [pickerProps] - * @property {string | ReturnType} [target] - * - * @typedef {import("./datetime_picker").DateTimePickerProps} DateTimePickerProps - */ - -/** - * @template {HTMLElement} T - * @typedef {{ el: T | null }} OwlRef - */ - -/** @type {typeof shallowEqual} */ -const arePropsEqual = (obj1, obj2) => - shallowEqual(obj1, obj2, (a, b) => areDatesEqual(a, b) || shallowEqual(a, b)); - -const FOCUS_CLASSNAME = "text-primary"; - -/** - * @param {DateTimePickerHookParams} hookParams - */ -export const useDateTimePicker = (hookParams) => { - // Hook methods - - /** - * @param {HTMLElement} el - * @param {string} type - * @param {(ev: Event) => any} listener - */ - const addListener = (el, type, listener) => { - el.addEventListener(type, listener); - cleanups.push(() => el.removeEventListener(type, listener)); - }; - - /** - * Wrapper method on the "onApply" callback to only call it when the - * value has changed, and set other internal variables accordingly. - */ - const apply = () => { - const serializedValue = JSON.stringify(pickerProps.value); - if (areDatesEqual(lastInitialProps?.value, serializedValue)) { - return; - } - - lastInitialProps = null; // Next pickerProps are considered final - inputsChanged = ensureArray(pickerProps.value).map(() => false); - - onApply?.(pickerProps.value); - }; - - const computeBasePickerProps = () => { - const nextInitialProps = markValuesRaw(hookParams.pickerProps); - const serializedProps = deepCopy(nextInitialProps); - - if (lastInitialProps && arePropsEqual(lastInitialProps, serializedProps)) { - return; - } - - lastInitialProps = serializedProps; - inputsChanged = ensureArray(lastInitialProps.value).map(() => false); - - for (const [key, value] of Object.entries(nextInitialProps)) { - if (pickerProps[key] !== value && !areDatesEqual(pickerProps[key], value)) { - pickerProps[key] = value; - } - } - }; - - /** - * Ensures the current focused input (indicated by `pickerProps.focusedDateIndex`) - * is actually focused. - */ - const focusActiveInput = () => { - const inputEl = getInput(pickerProps.focusedDateIndex); - if (!inputEl) { - shouldFocusOnPatched = true; - return; - } - - const { activeElement } = inputEl.ownerDocument; - if (activeElement !== inputEl) { - inputEl.focus(); - } - - setInputFocus(inputEl); - }; - - /** - * @param {number} valueIndex - * @returns {HTMLInputElement | null} - */ - const getInput = (valueIndex) => { - const el = inputRefs[valueIndex]?.el; - if (el && document.body.contains(el)) { - return el; - } - return null; - }; - - /** - * Returns the appropriate root element to attach the popover: - * - if the value is a range: the closest common parent of the two inputs - * - if not: the first input - */ - const getPopoverTarget = () => { - if (targetRef?.el) { - return targetRef.el; - } - if (pickerProps.range) { - let parentElement = getInput(0).parentElement; - const inputEls = inputRefs.map((ref) => ref.el); - while (parentElement && !inputEls.every((inputEl) => parentElement.contains(inputEl))) { - parentElement = parentElement.parentElement; - } - return parentElement || getInput(0); - } else { - return getInput(0); - } - }; - - /** - * @template {object} T - * @param {T} obj - */ - const markValuesRaw = (obj) => { - /** @type {T} */ - const copy = {}; - for (const [key, value] of Object.entries(obj)) { - if (value && typeof value === "object") { - copy[key] = markRaw(value); - } else { - copy[key] = value; - } - } - return copy; - }; - - /** - * Inputs "change" event handler. This will trigger an "onApply" callback if - * one of the following is true: - * - there is only one input; - * - the popover is closed; - * - the other input has also changed. - * - * @param {Event} ev - */ - const onInputChange = (ev) => { - updateValueFromInputs(); - inputsChanged[ev.target === getInput(1) ? 1 : 0] = true; - if (!popover.isOpen || inputsChanged.every(Boolean)) { - saveAndClose(); - } - }; - - /** - * @param {PointerEvent} ev - */ - const onInputClick = ({ target }) => { - openPicker(target === getInput(1) ? 1 : 0); - }; - - /** - * @param {FocusEvent} ev - */ - const onInputFocus = ({ target }) => { - pickerProps.focusedDateIndex = target === getInput(1) ? 1 : 0; - setInputFocus(target); - }; - - /** - * @param {KeyboardEvent} ev - */ - const onInputKeydown = (ev) => { - switch (ev.key) { - case "Enter": - case "Escape": { - return saveAndClose(); - } - case "Tab": { - if (!getInput(0) || !getInput(1) || ev.target !== getInput(ev.shiftKey ? 1 : 0)) { - return saveAndClose(); - } - } - } - }; - - /** - * @param {number} inputIndex Input from which to open the picker - */ - const openPicker = (inputIndex) => { - pickerProps.focusedDateIndex = inputIndex; - - if (!popover.isOpen) { - const popoverTarget = getPopoverTarget(); - if (env.isSmall) { - popoverTarget.scrollIntoView(true); - } - popover.open(popoverTarget, { pickerProps }); - } - - focusActiveInput(); - }; - - /** - * @template {"format" | "parse"} T - * @param {T} operation - * @param {T extends "format" ? DateTime : string} value - * @returns {[T extends "format" ? string : DateTime, null] | [null, Error]} - */ - const safeConvert = (operation, value) => { - const { type } = pickerProps; - const convertFn = registry - .category(operation === "format" ? "formatters" : "parsers") - .get(type); - try { - return [convertFn(value, { format }), null]; - } catch (error) { - if (error?.name === "ConversionError") { - return [null, error]; - } else { - throw error; - } - } - }; - - /** - * Wrapper method to ensure the "onApply" callback is called, either: - * - by closing the popover (if any); - * - or by directly calling "apply", without updating the values. - */ - const saveAndClose = () => { - if (popover.isOpen) { - // apply will be done in the "onClose" callback - popover.close(); - } else { - apply(); - } - }; - - /** - * Updates class names on given inputs according to the currently selected input. - * - * @param {HTMLInputElement | null} input - */ - const setFocusClass = (input) => { - for (const { el } of inputRefs) { - if (el) { - el.classList.toggle(FOCUS_CLASSNAME, popover.isOpen && el === input); - } - } - }; - - /** - * Applies class names to all inputs according to whether they are focused or not. - * - * @param {HTMLInputElement} inputEl - */ - const setInputFocus = (inputEl) => { - inputEl.selectionStart = 0; - inputEl.selectionEnd = inputEl.value.length; - - setFocusClass(inputEl); - - shouldFocusOnPatched = false; - }; - - /** - * Synchronizes the given input with the given value. - * - * @param {HTMLInputElement} el - * @param {DateTime} value - */ - const updateInput = (el, value) => { - const [formattedValue] = safeConvert("format", value); - el.value = formattedValue || ""; - }; - - /** - * @param {DateTimePickerProps["value"]} value - */ - const updateValue = (value) => { - const previousValue = pickerProps.value; - pickerProps.value = value; - - if (areDatesEqual(previousValue, pickerProps.value)) { - return; - } - - if (pickerProps.range) { - // When in range: compare each individual value - const [prevStart, prevEnd] = ensureArray(previousValue); - const [nextStart, nextEnd] = ensureArray(pickerProps.value); - if ( - (pickerProps.focusedDateIndex === 0 && areDatesEqual(prevEnd, nextEnd)) || - (pickerProps.focusedDateIndex === 1 && areDatesEqual(prevStart, nextStart)) - ) { - pickerProps.focusedDateIndex = pickerProps.focusedDateIndex === 1 ? 0 : 1; - } - } - - onChange?.(pickerProps.value); - }; - - const updateValueFromInputs = () => { - const values = zipWith( - inputRefs, - ensureArray(pickerProps.value), - ({ el }, currentValue) => { - if (!el) { - return currentValue; - } - const [parsedValue, error] = safeConvert("parse", el.value); - if (error) { - updateInput(el, currentValue); - return currentValue; - } else { - return parsedValue; - } - } - ); - updateValue(values.length === 2 ? values : values[0]); - }; - - // Hook variables - - const { format, onApply, onChange, target } = hookParams; - const targetRef = typeof target === "string" ? useRef(target) : target; - /** @type {ReturnType>[]} */ +export function useDateTimePicker(hookParams) { + const datetimePicker = useService("datetime_picker"); + if (typeof hookParams.target === "string") { + const target = useRef(hookParams.target); + Object.defineProperty(hookParams, "target", { + get() { + return target.el; + }, + }); + } const inputRefs = [useRef("start-date"), useRef("end-date")]; - const env = useEnv(); - const popover = usePopover(DateTimePickerPopover, { - onClose: () => { - if (!allowOnClose) { - return; - } - updateValueFromInputs(); - apply(); - setFocusClass(null); - }, - }); - - /** @type {DateTimePickerProps} */ - const rawPickerProps = { - ...DateTimePicker.defaultProps, - onSelect: (value) => { - value &&= markRaw(value); - updateValue(value); - if (!pickerProps.range && pickerProps.type === "date") { - saveAndClose(); - } - }, - ...markValuesRaw(hookParams.pickerProps), - }; - const pickerProps = reactive(rawPickerProps, () => { - // Resets the popover position when switching from single date to a range - // or vice-versa - const currentIsRange = pickerProps.range; - if (popover.isOpen && lastIsRange !== currentIsRange) { - allowOnClose = false; - popover.open(getPopoverTarget(), { pickerProps }); - allowOnClose = true; - } - lastIsRange = currentIsRange; - - // Update inputs - for (const [{ el }, value] of zip(inputRefs, ensureArray(pickerProps.value), true)) { - if (el) { - updateInput(el, value); - } - } - - shouldFocusOnPatched = true; - }); - - /** Decides whether the popover 'onClose' callback can be called */ - let allowOnClose = true; - /** @type {boolean[]} */ - let inputsChanged = []; - /** @type {DateTimePickerProps | null} */ - let lastInitialProps = null; - let lastIsRange = pickerProps.range; - let shouldFocusOnPatched = false; - + const { computeBasePickerProps, state, open, focusIfNeeded, enable } = datetimePicker.create( + hookParams, + () => inputRefs.map((ref) => ref?.el), + usePopover + ); onWillRender(computeBasePickerProps); - - const cleanups = []; - useEffect(() => { - let editableInputs = 0; - for (const [{ el }, value] of zip(inputRefs, ensureArray(pickerProps.value), true)) { - if (el && !el.disabled && !el.readOnly) { - addListener(el, "change", onInputChange); - addListener(el, "click", onInputClick); - addListener(el, "focus", onInputFocus); - addListener(el, "keydown", onInputKeydown); - - updateInput(el, value); - editableInputs++; - } - } - if (!editableInputs) { - saveAndClose(); - } - return () => { - while (cleanups.length) { - cleanups.pop()(); - } - }; - }); + useEffect(enable); // Note: this `onPatched` callback must be called after the `useEffect` since // the effect may change input values that will be selected by the patch callback. - onPatched(() => { - if (popover.isOpen && shouldFocusOnPatched) { - focusActiveInput(); - } - }); - - return { state: pickerProps, open: openPicker }; -}; + onPatched(focusIfNeeded); + return { state, open }; +} diff --git a/addons/web/static/src/core/datetime/datetimepicker_service.js b/addons/web/static/src/core/datetime/datetimepicker_service.js new file mode 100644 index 00000000000..b75fd463bdd --- /dev/null +++ b/addons/web/static/src/core/datetime/datetimepicker_service.js @@ -0,0 +1,470 @@ +/** @odoo-module **/ + +import { markRaw, reactive } from "@odoo/owl"; +import { areDatesEqual, formatDate, formatDateTime, parseDate, parseDateTime } from "../l10n/dates"; +import { makePopover } from "../popover/popover_hook"; +import { registry } from "../registry"; +import { ensureArray, zip, zipWith } from "../utils/arrays"; +import { deepCopy, shallowEqual } from "../utils/objects"; +import { DateTimePicker } from "./datetime_picker"; +import { DateTimePickerPopover } from "./datetime_picker_popover"; + +/** + * @typedef {luxon.DateTime} DateTime + * + * @typedef DateTimePickerHookParams + * @property {string} [format] + * @property {(value: DateTimePickerProps["value"]) => any} [onChange] callback + * invoked every time the hook updates the reactive value, either through the inputs + * or the picker. + * @property {(value: DateTimePickerProps["value"]) => any} [onApply] callback + * invoked once the value is committed: this is either when all inputs received + * a "change" event or when the datetime picker popover has been closed. + * @property {DateTimePickerProps} [pickerProps] + * @property {string | ReturnType} [target] + * + * @typedef {import("./datetime_picker").DateTimePickerProps} DateTimePickerProps + */ + +/** + * @template {HTMLElement} T + * @typedef {{ el: T | null }} OwlRef + */ + +/** @type {typeof shallowEqual} */ +const arePropsEqual = (obj1, obj2) => + shallowEqual(obj1, obj2, (a, b) => areDatesEqual(a, b) || shallowEqual(a, b)); + +const FOCUS_CLASSNAME = "text-primary"; + +const formatters = { + date: formatDate, + datetime: formatDateTime, +}; + +const parsers = { + date: parseDate, + datetime: parseDateTime, +}; + +export const datetimePickerService = { + dependencies: ["popover"], + start(env, { popover: popoverService }) { + return { + /** + * @param {DateTimePickerHookParams} hookParams + */ + create: ( + hookParams, + getInputs = () => [hookParams.target, null], + createPopover = (...args) => makePopover(popoverService, ...args) + ) => { + const popover = createPopover(DateTimePickerPopover, { + onClose: () => { + if (!allowOnClose) { + return; + } + updateValueFromInputs(); + apply(); + setFocusClass(null); + }, + }); + // Hook methods + + /** + * Wrapper method on the "onApply" callback to only call it when the + * value has changed, and set other internal variables accordingly. + */ + const apply = () => { + const serializedValue = JSON.stringify(pickerProps.value); + if (areDatesEqual(lastInitialProps?.value, serializedValue)) { + return; + } + + lastInitialProps = null; // Next pickerProps are considered final + inputsChanged = ensureArray(pickerProps.value).map(() => false); + + hookParams.onApply?.(pickerProps.value); + }; + + const computeBasePickerProps = () => { + const nextInitialProps = markValuesRaw(hookParams.pickerProps); + const serializedProps = deepCopy(nextInitialProps); + + if (lastInitialProps && arePropsEqual(lastInitialProps, serializedProps)) { + return; + } + + lastInitialProps = serializedProps; + inputsChanged = ensureArray(lastInitialProps.value).map(() => false); + + for (const [key, value] of Object.entries(nextInitialProps)) { + if (pickerProps[key] !== value && !areDatesEqual(pickerProps[key], value)) { + pickerProps[key] = value; + } + } + }; + + /** + * Ensures the current focused input (indicated by `pickerProps.focusedDateIndex`) + * is actually focused. + */ + const focusActiveInput = () => { + const inputEl = getInput(pickerProps.focusedDateIndex); + if (!inputEl) { + shouldFocus = true; + return; + } + + const { activeElement } = inputEl.ownerDocument; + if (activeElement !== inputEl) { + inputEl.focus(); + } + + setInputFocus(inputEl); + }; + + /** + * @param {number} valueIndex + * @returns {HTMLInputElement | null} + */ + const getInput = (valueIndex) => { + const el = getInputs()[valueIndex]; + if (el && document.body.contains(el)) { + return el; + } + return null; + }; + + /** + * Returns the appropriate root element to attach the popover: + * - if the value is a range: the closest common parent of the two inputs + * - if not: the first input + */ + const getPopoverTarget = () => { + if (hookParams.target) { + return hookParams.target; + } + if (pickerProps.range) { + let parentElement = getInput(0).parentElement; + const inputEls = getInputs(); + while ( + parentElement && + !inputEls.every((inputEl) => parentElement.contains(inputEl)) + ) { + parentElement = parentElement.parentElement; + } + return parentElement || getInput(0); + } else { + return getInput(0); + } + }; + + /** + * @template {object} T + * @param {T} obj + */ + const markValuesRaw = (obj) => { + /** @type {T} */ + const copy = {}; + for (const [key, value] of Object.entries(obj)) { + if (value && typeof value === "object") { + copy[key] = markRaw(value); + } else { + copy[key] = value; + } + } + return copy; + }; + + /** + * Inputs "change" event handler. This will trigger an "onApply" callback if + * one of the following is true: + * - there is only one input; + * - the popover is closed; + * - the other input has also changed. + * + * @param {Event} ev + */ + const onInputChange = (ev) => { + updateValueFromInputs(); + inputsChanged[ev.target === getInput(1) ? 1 : 0] = true; + if (!popover.isOpen || inputsChanged.every(Boolean)) { + saveAndClose(); + } + }; + + /** + * @param {PointerEvent} ev + */ + const onInputClick = ({ target }) => { + openPicker(target === getInput(1) ? 1 : 0); + }; + + /** + * @param {FocusEvent} ev + */ + const onInputFocus = ({ target }) => { + pickerProps.focusedDateIndex = target === getInput(1) ? 1 : 0; + setInputFocus(target); + }; + + /** + * @param {KeyboardEvent} ev + */ + const onInputKeydown = (ev) => { + switch (ev.key) { + case "Enter": + case "Escape": { + return saveAndClose(); + } + case "Tab": { + if ( + !getInput(0) || + !getInput(1) || + ev.target !== getInput(ev.shiftKey ? 1 : 0) + ) { + return saveAndClose(); + } + } + } + }; + + /** + * @param {number} inputIndex Input from which to open the picker + */ + const openPicker = (inputIndex) => { + pickerProps.focusedDateIndex = inputIndex; + + if (!popover.isOpen) { + const popoverTarget = getPopoverTarget(); + if (env.isSmall) { + popoverTarget.scrollIntoView(true); + } + popover.open(popoverTarget, { pickerProps }); + } + + focusActiveInput(); + }; + + /** + * @template {"format" | "parse"} T + * @param {T} operation + * @param {T extends "format" ? DateTime : string} value + * @returns {[T extends "format" ? string : DateTime, null] | [null, Error]} + */ + const safeConvert = (operation, value) => { + const { type } = pickerProps; + const convertFn = (operation === "format" ? formatters : parsers)[type]; + try { + return [convertFn(value, { format: hookParams.format }), null]; + } catch (error) { + if (error?.name === "ConversionError") { + return [null, error]; + } else { + throw error; + } + } + }; + + /** + * Wrapper method to ensure the "onApply" callback is called, either: + * - by closing the popover (if any); + * - or by directly calling "apply", without updating the values. + */ + const saveAndClose = () => { + if (popover.isOpen) { + // apply will be done in the "onClose" callback + popover.close(); + } else { + apply(); + } + }; + + /** + * Updates class names on given inputs according to the currently selected input. + * + * @param {HTMLInputElement | null} input + */ + const setFocusClass = (input) => { + for (const el of getInputs()) { + if (el) { + el.classList.toggle(FOCUS_CLASSNAME, popover.isOpen && el === input); + } + } + }; + + /** + * Applies class names to all inputs according to whether they are focused or not. + * + * @param {HTMLInputElement} inputEl + */ + const setInputFocus = (inputEl) => { + inputEl.selectionStart = 0; + inputEl.selectionEnd = inputEl.value.length; + + setFocusClass(inputEl); + + shouldFocus = false; + }; + + /** + * Synchronizes the given input with the given value. + * + * @param {HTMLInputElement} el + * @param {DateTime} value + */ + const updateInput = (el, value) => { + const [formattedValue] = safeConvert("format", value); + el.value = formattedValue || ""; + }; + + /** + * @param {DateTimePickerProps["value"]} value + */ + const updateValue = (value) => { + const previousValue = pickerProps.value; + pickerProps.value = value; + + if (areDatesEqual(previousValue, pickerProps.value)) { + return; + } + + if (pickerProps.range) { + // When in range: compare each individual value + const [prevStart, prevEnd] = ensureArray(previousValue); + const [nextStart, nextEnd] = ensureArray(pickerProps.value); + if ( + (pickerProps.focusedDateIndex === 0 && + areDatesEqual(prevEnd, nextEnd)) || + (pickerProps.focusedDateIndex === 1 && + areDatesEqual(prevStart, nextStart)) + ) { + pickerProps.focusedDateIndex = + pickerProps.focusedDateIndex === 1 ? 0 : 1; + } + } + + hookParams.onChange?.(pickerProps.value); + }; + + const updateValueFromInputs = () => { + const values = zipWith( + getInputs(), + ensureArray(pickerProps.value), + (el, currentValue) => { + if (!el) { + return currentValue; + } + const [parsedValue, error] = safeConvert("parse", el.value); + if (error) { + updateInput(el, currentValue); + return currentValue; + } else { + return parsedValue; + } + } + ); + updateValue(values.length === 2 ? values : values[0]); + }; + + // Hook variables + + /** @type {DateTimePickerProps} */ + const rawPickerProps = { + ...DateTimePicker.defaultProps, + onSelect: (value) => { + value &&= markRaw(value); + updateValue(value); + if (!pickerProps.range && pickerProps.type === "date") { + saveAndClose(); + } + }, + ...markValuesRaw(hookParams.pickerProps), + }; + const pickerProps = reactive(rawPickerProps, () => { + // Resets the popover position when switching from single date to a range + // or vice-versa + const currentIsRange = pickerProps.range; + if (popover.isOpen && lastIsRange !== currentIsRange) { + allowOnClose = false; + popover.open(getPopoverTarget(), { pickerProps }); + allowOnClose = true; + } + lastIsRange = currentIsRange; + + // Update inputs + for (const [el, value] of zip( + getInputs(), + ensureArray(pickerProps.value), + true + )) { + if (el) { + updateInput(el, value); + } + } + + shouldFocus = true; + }); + + /** Decides whether the popover 'onClose' callback can be called */ + let allowOnClose = true; + /** @type {boolean[]} */ + let inputsChanged = []; + /** @type {DateTimePickerProps | null} */ + let lastInitialProps = null; + let lastIsRange = pickerProps.range; + let shouldFocus = false; + + /** + * @param {HTMLElement} el + * @param {string} type + * @param {(ev: Event) => any} listener + */ + const addListener = (el, type, listener) => { + el.addEventListener(type, listener); + return () => el.removeEventListener(type, listener); + }; + + return { + state: pickerProps, + open: openPicker, + computeBasePickerProps, + focusIfNeeded() { + if (popover.isOpen && shouldFocus) { + focusActiveInput(); + } + }, + enable() { + let editableInputs = 0; + const cleanups = []; + for (const [el, value] of zip( + getInputs(), + ensureArray(pickerProps.value), + true + )) { + if (el && !el.disabled && !el.readOnly) { + cleanups.push(addListener(el, "change", onInputChange)); + cleanups.push(addListener(el, "click", onInputClick)); + cleanups.push(addListener(el, "focus", onInputFocus)); + cleanups.push(addListener(el, "keydown", onInputKeydown)); + + updateInput(el, value); + editableInputs++; + } + } + if (!editableInputs) { + saveAndClose(); + } + return () => cleanups.forEach((cleanup) => cleanup()); + }, + get isOpen() { + return popover.isOpen; + }, + }; + }, + }; + }, +}; + +registry.category("services").add("datetime_picker", datetimePickerService); diff --git a/addons/web/static/src/core/network/rpc_service.js b/addons/web/static/src/core/network/rpc_service.js index 06411012fda..3b534d2e0d6 100644 --- a/addons/web/static/src/core/network/rpc_service.js +++ b/addons/web/static/src/core/network/rpc_service.js @@ -123,9 +123,9 @@ export const rpcService = { /** * @param {string} route * @param {Object} params - * @param {Object} settings + * @param {Object} [settings] * @param {boolean} settings.silent - * @param {XmlHttpRequest} settings.xhr + * @param {XMLHttpRequest} settings.xhr */ return function rpc(route, params = {}, settings) { return jsonrpc(env, rpcId++, route, params, settings); diff --git a/addons/web/static/src/core/popover/popover_hook.js b/addons/web/static/src/core/popover/popover_hook.js index 8e0585e14d3..bc51ba77696 100644 --- a/addons/web/static/src/core/popover/popover_hook.js +++ b/addons/web/static/src/core/popover/popover_hook.js @@ -15,6 +15,28 @@ import { onWillUnmount, status, useComponent } from "@odoo/owl"; * - Whether the popover is currently open. */ +export function makePopover(popoverService, component, options) { + let removeFn = null; + function close() { + removeFn?.(); + } + return { + open(target, props) { + close(); + const newOptions = Object.create(options); + newOptions.onClose = () => { + removeFn = null; + options.onClose?.(); + }; + removeFn = popoverService.add(target, component, props, newOptions); + }, + close, + get isOpen() { + return Boolean(removeFn); + }, + }; +} + /** * Manages a component to be used as a popover. * @@ -23,28 +45,15 @@ import { onWillUnmount, status, useComponent } from "@odoo/owl"; * @returns {PopoverHookReturnType} */ export function usePopover(component, options = {}) { - let removeFn = null; - const popover = useService("popover"); + const popoverService = useService("popover"); const owner = useComponent(); - function close() { - removeFn?.(); - } - onWillUnmount(close); - return { - open(target, props) { - close(); - const newOptions = Object.create(options); - newOptions.onClose = function () { - removeFn = null; - if (status(owner) !== "destroyed") { - options.onClose?.(); - } - }; - removeFn = popover.add(target, component, props, newOptions); - }, - close, - get isOpen() { - return Boolean(removeFn); - }, + const newOptions = Object.create(options); + newOptions.onClose = () => { + if (status(owner) !== "destroyed") { + options.onClose?.(); + } }; + const popover = makePopover(popoverService, component, newOptions); + onWillUnmount(popover.close); + return popover; } diff --git a/addons/web/static/src/legacy/js/public/public_root.js b/addons/web/static/src/legacy/js/public/public_root.js index 2b8971b1e1b..0b137cea7c8 100644 --- a/addons/web/static/src/legacy/js/public/public_root.js +++ b/addons/web/static/src/legacy/js/public/public_root.js @@ -101,8 +101,6 @@ export const PublicRoot = publicWidget.RootWidget.extend({ this.el.scrollTop = +window.location.hash.match(/scrollTop=([0-9]+)/)[1]; } - this.$el.children().on('error.datetimepicker', this._onDateTimePickerError.bind(this)); - return Promise.all(defs); }, diff --git a/addons/web/static/src/public/datetime_picker_widget.js b/addons/web/static/src/public/datetime_picker_widget.js new file mode 100644 index 00000000000..8e348aac7af --- /dev/null +++ b/addons/web/static/src/public/datetime_picker_widget.js @@ -0,0 +1,36 @@ +/** @odoo-module */ + +import { + deserializeDate, + deserializeDateTime, + parseDate, + parseDateTime, +} from "@web/core/l10n/dates"; +import PublicWidget from "@web/legacy/js/public/public_widget"; + +export const DateTimePickerWidget = PublicWidget.Widget.extend({ + selector: "[data-widget='datetime-picker']", + start() { + this._super(...arguments); + const { widgetType, minDate, maxDate } = this.el.dataset; + const type = widgetType || "datetime"; + const { value } = this.el; + const [parse, deserialize] = + type === "date" ? [parseDate, deserializeDate] : [parseDateTime, deserializeDateTime]; + this.disable = this.call("datetime_picker", "create", { + target: this.el, + pickerProps: { + type, + minDate: minDate && deserialize(minDate), + maxDate: maxDate && deserialize(maxDate), + value: parse(value), + }, + }).enable(); + }, + destroy() { + this.disable(); + return this._super(...arguments); + }, +}); + +PublicWidget.registry.DateTimePickerWidget = DateTimePickerWidget; diff --git a/addons/web/static/tests/core/datetime/datetime_hook_tests.js b/addons/web/static/tests/core/datetime/datetime_hook_tests.js index 60ff01fa51c..c2548d2a88c 100644 --- a/addons/web/static/tests/core/datetime/datetime_hook_tests.js +++ b/addons/web/static/tests/core/datetime/datetime_hook_tests.js @@ -4,6 +4,7 @@ import { Component, reactive, useState, xml } from "@odoo/owl"; import { clearRegistryWithCleanup, makeTestEnv } from "@web/../tests/helpers/mock_env"; import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services"; import { editInput, getFixture, mount, nextTick } from "@web/../tests/helpers/utils"; +import { datetimePickerService } from "@web/core/datetime/datetimepicker_service"; import { useDateTimePicker } from "@web/core/datetime/datetime_hook"; import { DateTimeInput } from "@web/core/datetime/datetime_input"; import { hotkeyService } from "@web/core/hotkeys/hotkey_service"; @@ -57,7 +58,8 @@ QUnit.module("Components", ({ beforeEach }) => { }) ) .add("popover", popoverService) - .add("ui", uiService); + .add("ui", uiService) + .add("datetime_picker", datetimePickerService); fixture = getFixture(); }); diff --git a/addons/web/static/tests/core/datetime/datetime_input_tests.js b/addons/web/static/tests/core/datetime/datetime_input_tests.js index 6a1bc58534b..4bf2f5c4ddb 100644 --- a/addons/web/static/tests/core/datetime/datetime_input_tests.js +++ b/addons/web/static/tests/core/datetime/datetime_input_tests.js @@ -24,6 +24,7 @@ import { getTimePickers, zoomOut, } from "./datetime_test_helpers"; +import { datetimePickerService } from "@web/core/datetime/datetimepicker_service"; const { DateTime } = luxon; @@ -74,7 +75,8 @@ QUnit.module("Components", ({ beforeEach }) => { }) ) .add("popover", popoverService) - .add("ui", uiService); + .add("ui", uiService) + .add("datetime_picker", datetimePickerService); fixture = getFixture(); }); diff --git a/addons/web/static/tests/core/domain_selector_tests.js b/addons/web/static/tests/core/domain_selector_tests.js index 5bf4adb7139..71e84cf02b3 100644 --- a/addons/web/static/tests/core/domain_selector_tests.js +++ b/addons/web/static/tests/core/domain_selector_tests.js @@ -31,6 +31,7 @@ import { openModelFieldSelectorPopover } from "./model_field_selector_tests"; import { nameService } from "@web/core/name_service"; import { dialogService } from "@web/core/dialog/dialog_service"; import { browser } from "@web/core/browser/browser"; +import { datetimePickerService } from "@web/core/datetime/datetimepicker_service"; let serverData; let target; @@ -159,6 +160,7 @@ QUnit.module("Components", (hooks) => { registry.category("services").add("field", fieldService); registry.category("services").add("name", nameService); registry.category("services").add("dialog", dialogService); + registry.category("services").add("datetime_picker", datetimePickerService); target = getFixture(); }); diff --git a/addons/web/static/tests/core/dropdown_tests.js b/addons/web/static/tests/core/dropdown_tests.js index f28dd7b7d25..eb072562844 100644 --- a/addons/web/static/tests/core/dropdown_tests.js +++ b/addons/web/static/tests/core/dropdown_tests.js @@ -25,6 +25,7 @@ import { } from "../helpers/utils"; import { makeParent } from "./tooltip/tooltip_service_tests"; import { getPickerCell } from "./datetime/datetime_test_helpers"; +import { datetimePickerService } from "@web/core/datetime/datetimepicker_service"; const serviceRegistry = registry.category("services"); @@ -1204,6 +1205,7 @@ QUnit.module("Components", ({ beforeEach }) => { QUnit.test( "Dropdown with a date picker inside do not close when a click occurs in date picker", async (assert) => { + registry.category("services").add("datetime_picker", datetimePickerService); class MyComponent extends owl.Component {} MyComponent.template = owl.xml` diff --git a/addons/web/static/tests/search/helpers.js b/addons/web/static/tests/search/helpers.js index 473dabec9ae..d43438bd1b0 100644 --- a/addons/web/static/tests/search/helpers.js +++ b/addons/web/static/tests/search/helpers.js @@ -26,6 +26,7 @@ import { viewService } from "@web/views/view_service"; import { actionService } from "@web/webclient/actions/action_service"; import { MainComponentsContainer } from "@web/core/main_components_container"; import { nameService } from "@web/core/name_service"; +import { datetimePickerService } from "@web/core/datetime/datetimepicker_service"; const serviceRegistry = registry.category("services"); const favoriteMenuRegistry = registry.category("favoriteMenu"); @@ -41,6 +42,7 @@ export function setupControlPanelServiceRegistry() { serviceRegistry.add("popover", popoverService); serviceRegistry.add("view", viewService); serviceRegistry.add("command", commandService); + serviceRegistry.add("datetime_picker", datetimePickerService); } export function setupControlPanelFavoriteMenuRegistry() { diff --git a/addons/web/static/tests/webclient/helpers.js b/addons/web/static/tests/webclient/helpers.js index c70c381513a..5ebcc47a6bf 100644 --- a/addons/web/static/tests/webclient/helpers.js +++ b/addons/web/static/tests/webclient/helpers.js @@ -31,6 +31,7 @@ import { overlayService } from "@web/core/overlay/overlay_service"; import { Component, xml } from "@odoo/owl"; import { fieldService } from "@web/core/field_service"; import { nameService } from "@web/core/name_service"; +import { datetimePickerService } from "@web/core/datetime/datetimepicker_service"; const actionRegistry = registry.category("actions"); const serviceRegistry = registry.category("services"); @@ -77,6 +78,7 @@ export function setupWebClientRegistries() { user: () => makeFakeUserService(), view: () => viewService, company: () => fakeCompanyService, + datetime_picker: () => datetimePickerService, }; for (const serviceName in services) { if (!serviceRegistry.contains(serviceName)) { diff --git a/addons/web_editor/__manifest__.py b/addons/web_editor/__manifest__.py index 02b970b5781..1d0857da2b9 100644 --- a/addons/web_editor/__manifest__.py +++ b/addons/web_editor/__manifest__.py @@ -194,11 +194,6 @@ Odoo Web Editor widget. ('include', 'web_editor.assets_media_dialog'), - # TODO: remove when refactoring 'DatetimePickerUserValueWidget' - 'web/static/src/legacy/scss/tempusdominus_overridden.scss', - 'web/static/lib/tempusdominus/tempusdominus.scss', - 'web/static/lib/tempusdominus/tempusdominus.js', - 'web_editor/static/src/scss/web_editor.common.scss', 'web_editor/static/src/scss/web_editor.backend.scss', diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js index 59c25ccc5b3..3bfbda001a6 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -1758,6 +1758,7 @@ var SnippetsMenu = Widget.extend({ '.modal .btn-close', '.o_we_crop_widget', '.transfo-container', + '.o_datetime_picker', ].join(', '); this.loadingTimers = {}; diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js index 9f5d6e77e97..544b1669d46 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -5,7 +5,6 @@ import { MediaDialogWrapper } from "@web_editor/components/media_dialog/media_di import Dialog from "@web/legacy/js/core/dialog"; import dom from "@web/legacy/js/core/dom"; import rpc from "@web/legacy/js/core/rpc"; -import time from "@web/legacy/js/core/time"; import { throttleForAnimation, debounce } from "@web/core/utils/timing"; import utils from "@web/legacy/js/core/utils"; import Widget from "@web/legacy/js/core/widget"; @@ -34,7 +33,6 @@ import { } from "@web_editor/js/editor/image_processing"; import * as OdooEditorLib from "@web_editor/js/editor/odoo-editor/src/OdooEditor"; import {SIZES, MEDIAS_BREAKPOINTS} from "@web/core/ui/ui_service"; -import { uniqueId } from "@web/core/utils/functions"; import { pick } from "@web/core/utils/objects"; import { _t } from "@web/core/l10n/translation"; import { @@ -1837,11 +1835,9 @@ const VideopickerUserValueWidget = MediapickerUserValueWidget.extend({ const DatetimePickerUserValueWidget = InputUserValueWidget.extend({ events: { // Explicitely not consider all InputUserValueWidget events 'blur input': '_onInputBlur', - 'change.datetimepicker': '_onDateTimePickerChange', - 'error.datetimepicker': '_onDateTimePickerError', 'input input': '_onDateInputInput', }, - defaultFormat: time.getLangDatetimeFormat(), + pickerType: 'datetime', /** * @override @@ -1849,7 +1845,6 @@ const DatetimePickerUserValueWidget = InputUserValueWidget.extend({ init: function () { this._super(...arguments); this._value = DateTime.now().toUnixInteger().toString(); - this.__libInput = 0; }, /** * @override @@ -1857,51 +1852,21 @@ const DatetimePickerUserValueWidget = InputUserValueWidget.extend({ start: async function () { await this._super(...arguments); - const datetimePickerId = uniqueId("datetimepicker"); this.el.classList.add('o_we_large'); this.inputEl.classList.add('datetimepicker-input', 'mx-0', 'text-start'); - this.inputEl.setAttribute('id', datetimePickerId); - this.inputEl.setAttribute('data-target', '#' + datetimePickerId); - const datepickersOptions = { - minDate: moment({ y: 1000 }), - maxDate: moment().add(200, 'y'), - calendarWeeks: true, - defaultDate: moment().format(), - icons: { - close: 'fa fa-check primary', + this.picker = this.call("datetime_picker", "create", { + target: this.inputEl, + onChange: this._onDateTimePickerChange.bind(this), + pickerProps: { + type: this.pickerType, + minDate: DateTime.fromObject({ year: 1000 }), + maxDate: DateTime.now().plus({ year: 200 }), + value: DateTime.fromSeconds(parseInt(this._value)), + rounding: 0, }, - locale: moment.locale(), - format: this.defaultFormat, - sideBySide: true, - buttons: { - showClear: true, - showClose: true, - showToday: true, - }, - widgetParent: 'body', - - // Open the datetimepicker on focus not on click. This allows to - // take care of a bug which is due to the wysiwyg editor: - // sometimes, the datetimepicker loses the focus then get it back - // in the same execution flow. This was making the datepicker close - // for no apparent reason. Now, it only closes then reopens directly - // without it be possible to notice. - allowInputToggle: true, - }; - this.__libInput++; - const $input = $(this.inputEl); - $input.datetimepicker(datepickersOptions); - this.__libInput--; - - // Monkey-patch the library option to add custom classes on the pickers - const libObject = $input.data('datetimepicker'); - const oldFunc = libObject._getTemplate; - libObject._getTemplate = function () { - const $template = oldFunc.call(this, ...arguments); - $template.addClass('o_we_no_overlay o_we_datetimepicker'); - return $template; - }; + }); + this.picker.enable(); }, //-------------------------------------------------------------------------- @@ -1920,23 +1885,21 @@ const DatetimePickerUserValueWidget = InputUserValueWidget.extend({ * @override */ isPreviewed: function () { - return this._super(...arguments) || !!$(this.inputEl).data('datetimepicker').widget; + return this._super(...arguments) || this.picker.isOpen; }, /** * @override */ async setValue() { await this._super(...arguments); - let momentObj = null; + let dateTime = ""; if (this._value) { - momentObj = moment.unix(this._value); - if (!momentObj.isValid()) { - momentObj = moment(); + dateTime = DateTime.fromSeconds(parseInt(this._value)) + if (!dateTime.isValid) { + dateTime = DateTime.now(); } } - this.__libInput++; - $(this.inputEl).datetimepicker('date', momentObj); - this.__libInput--; + this.picker.state.value = dateTime; }, //-------------------------------------------------------------------------- @@ -1947,23 +1910,13 @@ const DatetimePickerUserValueWidget = InputUserValueWidget.extend({ * @private * @param {Event} ev */ - _onDateTimePickerChange: function (ev) { - if (this.__libInput > 0) { - return; - } - if (!ev.date || !ev.date.isValid()) { + _onDateTimePickerChange: function (newDateTime) { + if (!newDateTime || !newDateTime.isValid) { this._value = ''; } else { - this._value = ev.date.unix().toString(); + this._value = newDateTime.toUnixInteger().toString(); } - this._onUserValuePreview(ev); - }, - /** - * Prevents crash manager to throw CORS error. Note that library already - * clears the wrong date format. - */ - _onDateTimePickerError: function (ev) { - ev.stopPropagation(); + this._onUserValuePreview(); }, /** * Handles the clear button of the datepicker. @@ -1980,7 +1933,7 @@ const DatetimePickerUserValueWidget = InputUserValueWidget.extend({ }); const DatePickerUserValueWidget = DatetimePickerUserValueWidget.extend({ - defaultFormat: time.getLangDateFormat(), + pickerType: 'date', }); const ListUserValueWidget = UserValueWidget.extend({ diff --git a/addons/website/static/src/scss/website.edit_mode.scss b/addons/website/static/src/scss/website.edit_mode.scss index 533606409a0..ae614e90a5f 100644 --- a/addons/website/static/src/scss/website.edit_mode.scss +++ b/addons/website/static/src/scss/website.edit_mode.scss @@ -262,7 +262,7 @@ body.editor_enable { } } .s_website_form { - [data-toggle="datetimepicker"], textarea:not(.o_translatable_attribute):not(.o_translatable_text) { + textarea:not(.o_translatable_attribute):not(.o_translatable_text) { pointer-events: none; } } diff --git a/addons/website/static/src/snippets/s_website_form/000.js b/addons/website/static/src/snippets/s_website_form/000.js index 8435a056f86..0a685eeef26 100644 --- a/addons/website/static/src/snippets/s_website_form/000.js +++ b/addons/website/static/src/snippets/s_website_form/000.js @@ -1,6 +1,5 @@ /** @odoo-module **/ - import time from "@web/legacy/js/core/time"; import {ReCaptcha} from "@google_recaptcha/js/recaptcha"; import { session } from "@web/session"; import ajax from "@web/legacy/js/core/ajax"; @@ -10,8 +9,15 @@ import { debounce } from "@web/core/utils/timing"; import { _t } from "@web/core/l10n/translation"; import { renderToElement } from "@web/core/utils/render"; - import { formatDate, formatDateTime } from "@web/core/l10n/dates"; - const { DateTime } = luxon; +import { + formatDate, + formatDateTime, + parseDate, + parseDateTime, + serializeDate, + serializeDateTime, +} from "@web/core/l10n/dates"; +const { DateTime } = luxon; publicWidget.registry.EditModeWebsiteForm = publicWidget.Widget.extend({ selector: '.s_website_form form, form.s_website_form', // !compatibility @@ -97,32 +103,18 @@ this._onFieldInputDebounced = debounce(this._onFieldInput.bind(this), 400); this.$el.on('input.s_website_form', '.s_website_form_field', this._onFieldInputDebounced); - // Initialize datetimepickers - var datepickers_options = { - minDate: moment({y: 1000}), - maxDate: moment({y: 9999, M: 11, d: 31}), - calendarWeeks: true, - icons: { - time: 'fa fa-clock-o', - date: 'fa fa-calendar', - next: 'oi oi-chevron-right', - previous: 'oi oi-chevron-left', - up: 'oi oi-chevron-up', - down: 'oi oi-chevron-down', - }, - locale: moment.locale(), - format: time.getLangDatetimeFormat(), - extraFormats: ['X'], - }; - const $datetimes = this.$el.find('.s_website_form_datetime, .o_website_form_datetime'); // !compatibility - $datetimes.datetimepicker(datepickers_options); - - // Adapt options to date-only pickers - datepickers_options.format = time.getLangDateFormat(); - const $dates = this.$el.find('.s_website_form_date, .o_website_form_date'); // !compatibility - $dates.datetimepicker(datepickers_options); - - this.$allDates = $datetimes.add($dates); + this.$allDates = this.$el.find('.s_website_form_datetime, .o_website_form_datetime, .s_website_form_date, .o_website_form_date'); + for (const field of this.$allDates) { + const input = field.querySelector("input"); + const defaultValue = input.getAttribute("value"); + this.call("datetime_picker", "create", { + target: input, + pickerProps: { + type: field.matches('.s_website_form_date, .o_website_form_date') ? 'date' : 'datetime', + value: defaultValue && DateTime.fromSeconds(parseInt(defaultValue)), + }, + }).enable(); + } this.$allDates.addClass('s_website_form_datepicker_initialized'); // Display form values from tag having data-for attribute @@ -339,20 +331,14 @@ this.$el.find('.s_website_form_field:not(.s_website_form_custom)') .find('.s_website_form_date, .s_website_form_datetime').each(function () { const inputEl = this.querySelector('input'); - - // Datetimepicker('viewDate') will return `new Date()` if the - // input is empty but we want to keep the empty value - if (!inputEl.value) { + const { value } = inputEl; + if (!value) { return; } - var date = $(this).datetimepicker('viewDate').clone().locale('en'); - var format = 'YYYY-MM-DD'; - if ($(this).hasClass('s_website_form_datetime')) { - date = date.utc(); - format = 'YYYY-MM-DD HH:mm:ss'; - } - form_values[inputEl.getAttribute('name')] = date.format(format); + form_values[inputEl.getAttribute("name")] = this.matches(".s_website_form_date") + ? serializeDate(parseDate(value)) + : serializeDateTime(parseDateTime(value)); }); if (this._recaptchaLoaded) { @@ -524,11 +510,13 @@ // consider checking the date inputs are not disabled before // saying they are invalid (see checkValidity used here)) } else if ($(input).hasClass('s_website_form_date') || $(input).hasClass('o_website_form_date')) { // !compatibility - if (!self.is_datetime_valid(input.value, 'date')) { + const date = parseDate(input.value); + if (!date || !date.isValid) { return true; } } else if ($(input).hasClass('s_website_form_datetime') || $(input).hasClass('o_website_form_datetime')) { // !compatibility - if (!self.is_datetime_valid(input.value, 'datetime')) { + const date = parseDateTime(input.value); + if (!date || !date.isValid) { return true; } } else if (input.type === "file" && !self.isFileInputValid(input)) { @@ -567,42 +555,6 @@ return form_valid; }, - is_datetime_valid: function (value, type_of_date) { - if (value === "") { - return true; - } else { - try { - this.parse_date(value, type_of_date); - return true; - } catch { - return false; - } - } - }, - - // This is a stripped down version of format.js parse_value function - parse_date: function (value, type_of_date, value_if_empty) { - var date_pattern = time.getLangDateFormat(), - time_pattern = time.getLangTimeFormat(); - var date_pattern_wo_zero = date_pattern.replace('MM', 'M').replace('DD', 'D'), - time_pattern_wo_zero = time_pattern.replace('HH', 'H').replace('mm', 'm').replace('ss', 's'); - switch (type_of_date) { - case 'datetime': - var datetime = moment(value, [date_pattern + ' ' + time_pattern, date_pattern_wo_zero + ' ' + time_pattern_wo_zero], true); - if (datetime.isValid()) { - return time.datetime_to_str(datetime.toDate()); - } - throw new Error(_t("'%s' is not a correct datetime", value)); - case 'date': - var date = moment(value, [date_pattern, date_pattern_wo_zero], true); - if (date.isValid()) { - return time.date_to_str(date.toDate()); - } - throw new Error(_t("'%s' is not a correct date", value)); - } - return value; - }, - update_status: function (status, message) { if (status !== 'success') { // Restore send button behavior if result is an error this.$el.find('.s_website_form_send, .o_website_form_send') @@ -720,13 +672,7 @@ return value.name === ''; } // Date & Date Time comparison requires formatting the value - if (value.includes(':')) { - const datetimeFormat = time.getLangDatetimeFormat(); - value = moment(value, datetimeFormat)._d.getTime() / 1000; - } else { - const dateFormat = time.getLangDateFormat(); - value = moment(value, dateFormat)._d.getTime() / 1000; - } + value = (value.includes(':') ? parseDateTime(value) : parseDate(value)).toUnixInteger(); comparable = parseInt(comparable); between = parseInt(between) || ''; switch (comparator) { diff --git a/addons/website/static/src/xml/website_form_editor.xml b/addons/website/static/src/xml/website_form_editor.xml index 129a1aa7620..3cc82cafb5a 100644 --- a/addons/website/static/src/xml/website_form_editor.xml +++ b/addons/website/static/src/xml/website_form_editor.xml @@ -176,19 +176,17 @@ - -
+
-
+
@@ -196,19 +194,17 @@ - -
+
-
+
diff --git a/addons/website/static/tests/tours/website_form_editor_frontend.js b/addons/website/static/tests/tours/website_form_editor_frontend.js index 7af3742da3c..8dfdccc743b 100644 --- a/addons/website/static/tests/tours/website_form_editor_frontend.js +++ b/addons/website/static/tests/tours/website_form_editor_frontend.js @@ -86,9 +86,14 @@ registry.category("web_tour.tours").add("website_form_editor_tour_submit", { ":has(.s_website_form_field:has(label:contains('Invoice Scan')):not(.o_has_error))", trigger: "input[name=Products][value='Wiko Stairway']" }, + { + content: "Open datetime picker", + trigger: ".s_website_form_datetime input", + run: "click", + }, { content: "Complete Date field", - trigger: ".s_website_form_datetime [data-toggle='datetimepicker']", + trigger: ".o_date_picker .o_today", }, { content: "Check another product", diff --git a/addons/website_crm_partner_assign/static/src/js/crm_partner_assign.js b/addons/website_crm_partner_assign/static/src/js/crm_partner_assign.js index 0ffa0ab84cf..2353b81fca7 100644 --- a/addons/website_crm_partner_assign/static/src/js/crm_partner_assign.js +++ b/addons/website_crm_partner_assign/static/src/js/crm_partner_assign.js @@ -2,8 +2,7 @@ import { _t } from "@web/core/l10n/translation"; import publicWidget from "@web/legacy/js/public/public_widget"; -import time from "@web/legacy/js/core/time"; -import { parseDate, formatDate } from "@web/core/l10n/dates"; +import { parseDate, formatDate, serializeDate } from "@web/core/l10n/dates"; const { DateTime } = luxon; publicWidget.registry.crmPartnerAssign = publicWidget.Widget.extend({ @@ -18,7 +17,6 @@ publicWidget.registry.crmPartnerAssign = publicWidget.Widget.extend({ 'click .edit_opp_confirm': '_onEditOppConfirm', 'change .edit_opp_form .next_activity': '_onChangeNextActivity', 'change #new-opp-dialog .contact_name': '_onChangeContactName', - 'click div.input-group.date[data-target-input="nearest"]': '_onCalendarInputGroupClick', }, //-------------------------------------------------------------------------- @@ -259,32 +257,11 @@ publicWidget.registry.crmPartnerAssign = publicWidget.Widget.extend({ $('.edit_opp_form .activity_date_deadline').val( formatDate(date) ); } }, - /** - * @private - * @param {Event} ev - */ - _onCalendarInputGroupClick: function (ev) { - const $calendarInputGroup = $(ev.currentTarget); - const calendarOptions = { - format : time.getLangDateFormat(), - icons: { - time: 'fa fa-clock-o', - date: 'fa fa-calendar', - up: 'oi oi-chevron-up', - down: 'oi oi-chevron-down', - }, - }; - $calendarInputGroup.datetimepicker(calendarOptions); - }, - _parse_date: function (value) { - console.log(value); var date = parseDate(value); - if (date.isValid && date.year >= 1900) { - return formatDate(date); - } - else { + if (!date.isValid || date.year < 1900) { return false; } + return serializeDate(date); }, }); diff --git a/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml b/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml index ae42241bf3c..63947253c77 100644 --- a/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml +++ b/addons/website_crm_partner_assign/views/website_crm_partner_assign_templates.xml @@ -759,10 +759,10 @@
-
+
- - + +
@@ -784,10 +784,10 @@
-
+
- - + +
diff --git a/addons/website_slides/__manifest__.py b/addons/website_slides/__manifest__.py index 3398ea19578..bbf71b8a776 100644 --- a/addons/website_slides/__manifest__.py +++ b/addons/website_slides/__manifest__.py @@ -130,8 +130,6 @@ Featuring 'web/static/src/scss/pre_variables.scss', 'web/static/lib/bootstrap/scss/_variables.scss', - 'web/static/src/legacy/scss/tempusdominus_overridden.scss', - 'web/static/lib/tempusdominus/tempusdominus.scss', 'web/static/lib/jquery.ui/jquery-ui.css', 'web/static/src/libs/fontawesome/css/font-awesome.css', 'web/static/lib/odoo_ui_icons/*', @@ -184,7 +182,6 @@ Featuring 'web/static/lib/bootstrap/js/dist/scrollspy.js', 'web/static/lib/bootstrap/js/dist/tab.js', 'web/static/lib/bootstrap/js/dist/toast.js', - 'web/static/lib/tempusdominus/tempusdominus.js', 'web/static/lib/select2/select2.js', 'web/static/lib/clipboard/clipboard.js', 'web/static/lib/jSignature/jSignatureCustom.js', diff --git a/debian/copyright b/debian/copyright index 16252768e1d..d1aead1f199 100644 --- a/debian/copyright +++ b/debian/copyright @@ -318,10 +318,6 @@ Files: addons/web/static/lib/select2/* Copyright: 2014-2015 Igor Vaynberg License: Apache-2.0 -Files: addons/web/static/lib/tempusdominus/* -Copyright: 2016 Tempus Dominus; 2016 Jonathan Peterson -License: MIT - Files: addons/web/static/lib/underscore.string/* Copyright: Esa-Matti Suuronen, Alexandru Marasteanu License: MIT
').addClass('cw').text('#')); - } - - while (currentDate.isBefore(this._viewDate.clone().endOf('w'))) { - row.append($('').addClass('dow').text(currentDate.format('dd'))); - currentDate.add(1, 'd'); - } - this.widget.find('.datepicker-days thead').append(row); - }; - - TempusDominusBootstrap4.prototype._fillMonths = function _fillMonths() { - var spans = [], - monthsShort = this._viewDate.clone().startOf('y').startOf('d'); - while (monthsShort.isSame(this._viewDate, 'y')) { - spans.push($('').attr('data-action', 'selectMonth').addClass('month').text(monthsShort.format('MMM'))); - monthsShort.add(1, 'M'); - } - this.widget.find('.datepicker-months td').empty().append(spans); - }; - - TempusDominusBootstrap4.prototype._updateMonths = function _updateMonths() { - var monthsView = this.widget.find('.datepicker-months'), - monthsViewHeader = monthsView.find('th'), - months = monthsView.find('tbody').find('span'), - self = this; - - monthsViewHeader.eq(0).find('span').attr('title', this._options.tooltips.prevYear); - monthsViewHeader.eq(1).attr('title', this._options.tooltips.selectYear); - monthsViewHeader.eq(2).find('span').attr('title', this._options.tooltips.nextYear); - - monthsView.find('.disabled').removeClass('disabled'); - - if (!this._isValid(this._viewDate.clone().subtract(1, 'y'), 'y')) { - monthsViewHeader.eq(0).addClass('disabled'); - } - - monthsViewHeader.eq(1).text(this._viewDate.year()); - - if (!this._isValid(this._viewDate.clone().add(1, 'y'), 'y')) { - monthsViewHeader.eq(2).addClass('disabled'); - } - - months.removeClass('active'); - if (this._getLastPickedDate().isSame(this._viewDate, 'y') && !this.unset) { - months.eq(this._getLastPickedDate().month()).addClass('active'); - } - - months.each(function (index) { - if (!self._isValid(self._viewDate.clone().month(index), 'M')) { - $(this).addClass('disabled'); - } - }); - }; - - TempusDominusBootstrap4.prototype._getStartEndYear = function _getStartEndYear(factor, year) { - var step = factor / 10, - startYear = Math.floor(year / factor) * factor, - endYear = startYear + step * 9, - focusValue = Math.floor(year / step) * step; - return [startYear, endYear, focusValue]; - }; - - TempusDominusBootstrap4.prototype._updateYears = function _updateYears() { - var yearsView = this.widget.find('.datepicker-years'), - yearsViewHeader = yearsView.find('th'), - yearCaps = this._getStartEndYear(10, this._viewDate.year()), - startYear = this._viewDate.clone().year(yearCaps[0]), - endYear = this._viewDate.clone().year(yearCaps[1]); - var html = ''; - - yearsViewHeader.eq(0).find('span').attr('title', this._options.tooltips.prevDecade); - yearsViewHeader.eq(1).attr('title', this._options.tooltips.selectDecade); - yearsViewHeader.eq(2).find('span').attr('title', this._options.tooltips.nextDecade); - - yearsView.find('.disabled').removeClass('disabled'); - - if (this._options.minDate && this._options.minDate.isAfter(startYear, 'y')) { - yearsViewHeader.eq(0).addClass('disabled'); - } - - yearsViewHeader.eq(1).text(startYear.year() + '-' + endYear.year()); - - if (this._options.maxDate && this._options.maxDate.isBefore(endYear, 'y')) { - yearsViewHeader.eq(2).addClass('disabled'); - } - - html += '' + (startYear.year() - 1) + ''; - while (!startYear.isAfter(endYear, 'y')) { - html += '' + startYear.year() + ''; - startYear.add(1, 'y'); - } - html += '' + startYear.year() + ''; - - yearsView.find('td').html(html); - }; - - TempusDominusBootstrap4.prototype._updateDecades = function _updateDecades() { - var decadesView = this.widget.find('.datepicker-decades'), - decadesViewHeader = decadesView.find('th'), - yearCaps = this._getStartEndYear(100, this._viewDate.year()), - startDecade = this._viewDate.clone().year(yearCaps[0]), - endDecade = this._viewDate.clone().year(yearCaps[1]); - var minDateDecade = false, - maxDateDecade = false, - endDecadeYear = void 0, - html = ''; - - decadesViewHeader.eq(0).find('span').attr('title', this._options.tooltips.prevCentury); - decadesViewHeader.eq(2).find('span').attr('title', this._options.tooltips.nextCentury); - - decadesView.find('.disabled').removeClass('disabled'); - - if (startDecade.year() === 0 || this._options.minDate && this._options.minDate.isAfter(startDecade, 'y')) { - decadesViewHeader.eq(0).addClass('disabled'); - } - - decadesViewHeader.eq(1).text(startDecade.year() + '-' + endDecade.year()); - - if (this._options.maxDate && this._options.maxDate.isBefore(endDecade, 'y')) { - decadesViewHeader.eq(2).addClass('disabled'); - } - - if (startDecade.year() - 10 < 0) { - html += ' '; - } else { - html += '' + (startDecade.year() - 10) + ''; - } - - while (!startDecade.isAfter(endDecade, 'y')) { - endDecadeYear = startDecade.year() + 11; - minDateDecade = this._options.minDate && this._options.minDate.isAfter(startDecade, 'y') && this._options.minDate.year() <= endDecadeYear; - maxDateDecade = this._options.maxDate && this._options.maxDate.isAfter(startDecade, 'y') && this._options.maxDate.year() <= endDecadeYear; - html += '' + startDecade.year() + ''; - startDecade.add(10, 'y'); - } - html += '' + startDecade.year() + ''; - - decadesView.find('td').html(html); - }; - - TempusDominusBootstrap4.prototype._fillDate = function _fillDate() { - var daysView = this.widget.find('.datepicker-days'), - daysViewHeader = daysView.find('th'), - html = []; - var currentDate = void 0, - row = void 0, - clsName = void 0, - i = void 0; - - if (!this._hasDate()) { - return; - } - - daysViewHeader.eq(0).find('span').attr('title', this._options.tooltips.prevMonth); - daysViewHeader.eq(1).attr('title', this._options.tooltips.selectMonth); - daysViewHeader.eq(2).find('span').attr('title', this._options.tooltips.nextMonth); - - daysView.find('.disabled').removeClass('disabled'); - daysViewHeader.eq(1).text(this._viewDate.format(this._options.dayViewHeaderFormat)); - - if (!this._isValid(this._viewDate.clone().subtract(1, 'M'), 'M')) { - daysViewHeader.eq(0).addClass('disabled'); - } - if (!this._isValid(this._viewDate.clone().add(1, 'M'), 'M')) { - daysViewHeader.eq(2).addClass('disabled'); - } - - // !! ODOO FIX START !! - var now = this.getMoment(); - // currentDate = this._viewDate.clone().startOf('M').startOf('w').startOf('d'); - // avoid issue of safari + DST at midnight - currentDate = this._viewDate.clone().startOf('M').startOf('w').add(12, 'hours'); - // !! ODOO FIX END !! - - for (i = 0; i < 42; i++) { - //always display 42 days (should show 6 weeks) - if (currentDate.weekday() === 0) { - row = $('
' + currentDate.week() + '' + currentDate.date() + '
' + currentHour.format(this.use24Hours ? 'HH' : 'hh') + '
' + currentMinute.format('mm') + '
' + currentSecond.format('ss') + '