diff --git a/web_widget_timepicker/README.rst b/web_widget_timepicker/README.rst index fe0009b3..0d05f34d 100644 --- a/web_widget_timepicker/README.rst +++ b/web_widget_timepicker/README.rst @@ -2,7 +2,6 @@ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 - =============================== Timepicker widget in form views =============================== @@ -52,16 +51,10 @@ See the available options at `jquery-timepicker `_ plugin by Jon Thornton. This software is made available under the open source MIT License. © 2014 Jon Thornton and contributors +* The module uses the `jquery-timepicker `_. plugin by Jon Thornton. This software is made available under the open source MIT License. © 2014 Jon Thornton and contributors * Odoo Community Association (OCA) @@ -70,3 +63,20 @@ Contributors ------------ * Michael Fried +* Kaushal Prajapati + + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. \ No newline at end of file diff --git a/web_widget_timepicker/__manifest__.py b/web_widget_timepicker/__manifest__.py index 5b7507b3..23e4f302 100644 --- a/web_widget_timepicker/__manifest__.py +++ b/web_widget_timepicker/__manifest__.py @@ -3,12 +3,14 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { "name": "Web Timepicker Widget", - "version": "9.0.1.0.0", - "author": "VividLab, Odoo Community Association (OCA)", + "version": "10.0.1.0.0", + "author": "VividLab, " + "Odoo Community Association (OCA), " + "Kaushal Prajapati", "license": "AGPL-3", "category": "Web", "website": "http://www.vividlab.de", - 'installable': False, + 'installable': True, "depends": [ "web", ], diff --git a/web_widget_timepicker/static/src/css/web_widget_timepicker.css b/web_widget_timepicker/static/src/css/web_widget_timepicker.css index c66e9cd0..444ab0dc 100644 --- a/web_widget_timepicker/static/src/css/web_widget_timepicker.css +++ b/web_widget_timepicker/static/src/css/web_widget_timepicker.css @@ -1,3 +1,6 @@ -.oe_form_editable .oe_form .oe_form_field_time input { +.o_form_editable .o_form_field_time input { width: 6em; } +.o_form_field_time{ + display: -webkit-inline-box !important; +} diff --git a/web_widget_timepicker/static/src/js/web_widget_timepicker.js b/web_widget_timepicker/static/src/js/web_widget_timepicker.js index b56a8cf4..573c3881 100644 --- a/web_widget_timepicker/static/src/js/web_widget_timepicker.js +++ b/web_widget_timepicker/static/src/js/web_widget_timepicker.js @@ -3,22 +3,22 @@ odoo.define('web_widget_timepicker', function (require) { var core = require('web.core'); var formats = require('web.formats'); - var common = require('web.form_common'); + var common = require('web.form_common'); var TimePickerField = common.AbstractField.extend(common.ReinitializeFieldMixin, { - is_field_number: true, + is_field_number: true, template: "TimePickerField", internal_format: 'float_time', - widget_class: 'oe_form_field_time', + widget_class: 'o_form_field_time', events: { 'change input': 'store_dom_value', }, init: function (field_manager, node) { this._super(field_manager, node); - + this.internal_set_value(0); - this.options = _.defaults( this.options, { + this.options = _.defaults(this.options, { step: 15, selectOnBlur: true, timeFormat: 'H:i', @@ -27,14 +27,14 @@ odoo.define('web_widget_timepicker', function (require) { }, initialize_content: function() { if(!this.get("effective_readonly")) { - this.$el.find('input').timepicker(this.options); - this.setupFocus(this.$('input')); - } + this.$el.find('input').timepicker(this.options); + this.setupFocus(this.$('input')); + } }, is_syntax_valid: function() { if (!this.get("effective_readonly") && this.$("input").size() > 0) { try { - this.parse_value(this.$('input').val(),''); + this.parse_value(this.$('input').val(), ''); return true; } catch(e) { return false; @@ -55,28 +55,28 @@ odoo.define('web_widget_timepicker', function (require) { height: height, width: width }); - }, + }, store_dom_value: function () { if (!this.get('effective_readonly')) { this.internal_set_value( this.parse_value( - this.$('input').val(),'')); + this.$('input').val(), '')); } }, parse_value: function(val, def) { - return formats.parse_value(val, {"widget": this.internal_format}, def); + return formats.parse_value(val, {"widget": this.internal_format}, def); }, format_value: function(val, def) { return formats.format_value(val, {"widget": this.internal_format}, def); }, render_value: function() { - var show_value = this.format_value(this.get('value'),''); + var show_value = this.format_value(this.get('value'), ''); if (!this.get("effective_readonly")) { this.$input = this.$el.find('input'); this.$input.val(show_value); } else { - this.$(".oe_form_time_content").text(show_value); + this.$(".o_form_time_content").text(show_value); } }, }); @@ -84,6 +84,6 @@ odoo.define('web_widget_timepicker', function (require) { core.form_widget_registry.add('timepicker', TimePickerField); return { - TimePickerField: TimePickerField, + TimePickerField: TimePickerField, }; }); diff --git a/web_widget_timepicker/static/src/lib/jquery.timepicker/jquery.timepicker.css b/web_widget_timepicker/static/src/lib/jquery.timepicker/jquery.timepicker.css index cd75f13f..78e1415e 100644 --- a/web_widget_timepicker/static/src/lib/jquery.timepicker/jquery.timepicker.css +++ b/web_widget_timepicker/static/src/lib/jquery.timepicker/jquery.timepicker.css @@ -1,6 +1,6 @@ .ui-timepicker-wrapper { overflow-y: auto; - height: 150px; + max-height: 150px; width: 6.5em; background: #fff; border: 1px solid #ddd; @@ -69,4 +69,4 @@ li.ui-timepicker-selected .ui-timepicker-duration, .ui-timepicker-list li.ui-timepicker-disabled:hover, .ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled { background: #f2f2f2; -} +} \ No newline at end of file diff --git a/web_widget_timepicker/static/src/lib/jquery.timepicker/jquery.timepicker.js b/web_widget_timepicker/static/src/lib/jquery.timepicker/jquery.timepicker.js index d6cb947c..b9e4f9ab 100644 --- a/web_widget_timepicker/static/src/lib/jquery.timepicker/jquery.timepicker.js +++ b/web_widget_timepicker/static/src/lib/jquery.timepicker/jquery.timepicker.js @@ -1,5 +1,5 @@ /*! - * jquery-timepicker v1.10.1 - A jQuery timepicker plugin inspired by Google Calendar. It supports both mouse and keyboard navigation. + * jquery-timepicker v1.11.11 - A jQuery timepicker plugin inspired by Google Calendar. It supports both mouse and keyboard navigation. * Copyright (c) 2015 Jon Thornton - http://jonthornton.github.com/jquery-timepicker/ * License: MIT */ @@ -30,6 +30,58 @@ hrs: 'hrs' }; + var _DEFAULTS = { + appendTo: 'body', + className: null, + closeOnWindowScroll: false, + disableTextInput: false, + disableTimeRanges: [], + disableTouchKeyboard: false, + durationTime: null, + forceRoundTime: false, + maxTime: null, + minTime: null, + noneOption: false, + orientation: 'l', + roundingFunction: function(seconds, settings) { + if (seconds === null) { + return null; + } else if (typeof settings.step !== "number") { + // TODO: nearest fit irregular steps + return seconds; + } else { + var offset = seconds % (settings.step*60); // step is in minutes + + var start = settings.minTime || 0; + + // adjust offset by start mod step so that the offset is aligned not to 00:00 but to the start + offset -= start % (settings.step * 60); + + if (offset >= settings.step*30) { + // if offset is larger than a half step, round up + seconds += (settings.step*60) - offset; + } else { + // round down + seconds -= offset; + } + + return _moduloSeconds(seconds, settings); + } + }, + scrollDefault: null, + selectOnBlur: false, + show2400: false, + showDuration: false, + showOn: ['click', 'focus'], + showOnFocus: true, + step: 30, + stopScrollPropagation: false, + timeFormat: 'g:ia', + typeaheadHighlight: true, + useSelect: false, + wrapHours: true + }; + var methods = { init: function(options) { @@ -39,13 +91,13 @@ // pick up settings from data attributes var attributeOptions = []; - for (var key in $.fn.timepicker.defaults) { + for (var key in _DEFAULTS) { if (self.data(key)) { attributeOptions[key] = self.data(key); } } - var settings = $.extend({}, $.fn.timepicker.defaults, attributeOptions, options); + var settings = $.extend({}, _DEFAULTS, options, attributeOptions); if (settings.lang) { _lang = $.extend(_lang, settings.lang); @@ -70,8 +122,10 @@ if (settings.disableTextInput) { self.on('keydown.timepicker', _disableTextInputHandler); } + self.on('cut.timepicker', _keyuphandler); + self.on('paste.timepicker', _keyuphandler); - _formatValue.call(self.get(0)); + _formatValue.call(self.get(0), null, 'initial'); } }); }, @@ -165,6 +219,11 @@ } } + // if not found or disabled, intelligently find first selectable element + if (!selected.length || selected.hasClass('ui-timepicker-disabled')) { + selected = list.find('li:not(.ui-timepicker-disabled):first'); + } + if (selected && selected.length) { var topOffset = list.scrollTop() + selected.position().top - selected.outerHeight(); list.scrollTop(topOffset); @@ -243,6 +302,8 @@ self.data('timepicker-settings', settings); + _formatValue.call(self.get(0), {'type':'change'}, 'initial'); + if (list) { list.remove(); self.data('timepicker-list', false); @@ -308,7 +369,9 @@ prettyTime = value; } - _setTimeValue(self, prettyTime); + self.val(prettyTime); + _formatValue.call(self.get(0), {'type':'change'}, 'initial'); + if (self.data('timepicker-list')) { _setSelected(self, self.data('timepicker-list')); } @@ -507,8 +570,8 @@ row.text(timeString); } else { var row = $('
  • '); - row.addClass(timeInt % 86400 < 43200 ? 'ui-timepicker-am' : 'ui-timepicker-pm'); - row.data('time', (timeInt <= 86400 ? timeInt : timeInt % 86400)); + row.addClass((timeInt % _ONE_DAY) < (_ONE_DAY / 2) ? 'ui-timepicker-am' : 'ui-timepicker-pm'); + row.data('time', _moduloSeconds(timeInt, settings)); row.text(timeString); } @@ -570,7 +633,7 @@ appendTo.append(wrapped_list); _setSelected(self, list); - list.on('mousedown touchstart', 'li', function(e) { + list.on('mousedown click', 'li', function(e) { // hack: temporarily disable the focus handler // to deal with the fact that IE fires 'focus' @@ -592,8 +655,8 @@ if (_selectValue(self)) { self.trigger('hideTimepicker'); - list.on('mouseup.timepicker touchend.timepicker', 'li', function(e) { - list.off('mouseup.timepicker touchend.timepicker'); + list.on('mouseup.timepicker click.timepicker', 'li', function(e) { + list.off('mouseup.timepicker click.timepicker'); wrapped_list.hide(); }); } @@ -640,13 +703,21 @@ // event handler to decide whether to close timepicker function _closeHandler(e) { + if (e.target == window) { + // mobile Chrome fires focus events against window for some reason + return; + } + var target = $(e.target); - var input = target.closest('.ui-timepicker-input'); - if (input.length === 0 && target.closest('.ui-timepicker-wrapper').length === 0) { - methods.hide(); - $(document).unbind('.ui-timepicker'); - $(window).unbind('.ui-timepicker'); + + if (target.closest('.ui-timepicker-input').length || target.closest('.ui-timepicker-wrapper').length) { + // active timepicker was focused. ignore + return; } + + methods.hide(); + $(document).unbind('.ui-timepicker'); + $(window).unbind('.ui-timepicker'); } function _hideKeyboard(self) @@ -685,7 +756,8 @@ { list.find('li').removeClass('ui-timepicker-selected'); - var timeValue = _time2int(_getTimeValue(self), self.data('timepicker-settings')); + var settings = self.data('timepicker-settings'); + var timeValue = _time2int(_getTimeValue(self), settings); if (timeValue === null) { return; } @@ -699,19 +771,26 @@ list.scrollTop(list.scrollTop() + selected.position().top - selected.outerHeight()); } - selected.addClass('ui-timepicker-selected'); + if (settings.forceRoundTime || selected.data('time') === timeValue) { + selected.addClass('ui-timepicker-selected'); + } } } function _formatValue(e, origin) { - if (this.value === '' || origin == 'timepicker') { + if (origin == 'timepicker') { return; } var self = $(this); + if (this.value === '') { + _setTimeValue(self, null, origin); + return; + } + if (self.is(':focus') && (!e || e.type != 'change')) { return; } @@ -726,8 +805,8 @@ var rangeError = false; // check that the time in within bounds - if (settings.minTime !== null && seconds < settings.minTime - && settings.maxTime !== null && seconds > settings.maxTime) { + if ((settings.minTime !== null && settings.maxTime !== null) + && (seconds < settings.minTime || seconds > settings.maxTime)) { rangeError = true; } @@ -740,17 +819,21 @@ }); if (settings.forceRoundTime) { - seconds = settings.roundingFunction(seconds, settings); + var roundSeconds = settings.roundingFunction(seconds, settings); + if (roundSeconds != seconds) { + seconds = roundSeconds; + origin = null; + } } var prettyTime = _int2time(seconds, settings); if (rangeError) { - if (_setTimeValue(self, prettyTime, 'error')) { + if (_setTimeValue(self, prettyTime, 'error') || e && e.type == 'change') { self.trigger('timeRangeError'); } } else { - _setTimeValue(self, prettyTime); + _setTimeValue(self, prettyTime, origin); } } @@ -779,7 +862,7 @@ self.data('ui-timepicker-value', value); if (source == 'select') { self.trigger('selectTime').trigger('changeTime').trigger('change', 'timepicker'); - } else if (source != 'error') { + } else if (['error', 'initial'].indexOf(source) == -1) { self.trigger('changeTime'); } @@ -830,6 +913,7 @@ case 13: // return if (_selectValue(self)) { + _formatValue.call(self.get(0), {'type':'change'}); methods.hide.apply(this); } @@ -909,6 +993,17 @@ return true; } + if (e.type === 'paste' || e.type === 'cut') { + setTimeout(function () { + if (settings.typeaheadHighlight) { + _setSelected(self, list); + } else { + list.hide(); + } + }, 0); + return; + } + switch (e.keyCode) { case 96: // numpad numerals @@ -1124,10 +1219,11 @@ return null; } - var unboundedHour = parseInt(time[2]*1, 10); - var hour = (unboundedHour > 24) ? unboundedHour % 24 : unboundedHour; + var hour = parseInt(time[2]*1, 10); var ampm = time[1] || time[5]; var hours = hour; + var minutes = ( time[3]*1 || 0 ); + var seconds = ( time[4]*1 || 0 ); if (hour <= 12 && ampm) { var isPm = (ampm == _lang.pm || ampm == _lang.PM); @@ -1137,10 +1233,17 @@ } else { hours = (hour + (isPm ? 12 : 0)); } + } else if (settings) { + var t = hour * 3600 + minutes * 60 + seconds; + if (t >= _ONE_DAY + (settings.show2400 ? 1 : 0)) { + if (settings.wrapHours === false) { + return null; + } + + hours = hour % 24; + } } - var minutes = ( time[3]*1 || 0 ); - var seconds = ( time[4]*1 || 0 ); var timeInt = hours*3600 + minutes*60 + seconds; // if no am/pm provided, intelligently guess based on the scrollDefault @@ -1158,6 +1261,14 @@ return ("0" + n).slice(-2); } + function _moduloSeconds(seconds, settings) { + if (seconds == _ONE_DAY && settings.show2400) { + return seconds; + } + + return seconds%_ONE_DAY; + } + // Plugin entry $.fn.timepicker = function(method) { @@ -1172,54 +1283,4 @@ else if(typeof method === "object" || !method) { return methods.init.apply(this, arguments); } else { $.error("Method "+ method + " does not exist on jQuery.timepicker"); } }; - // Global defaults - $.fn.timepicker.defaults = { - appendTo: 'body', - className: null, - closeOnWindowScroll: false, - disableTextInput: false, - disableTimeRanges: [], - disableTouchKeyboard: false, - durationTime: null, - forceRoundTime: false, - maxTime: null, - minTime: null, - noneOption: false, - orientation: 'l', - roundingFunction: function(seconds, settings) { - if (seconds === null) { - return null; - } else if (typeof settings.step !== "number") { - // TODO: nearest fit irregular steps - return seconds; - } else { - var offset = seconds % (settings.step*60); // step is in minutes - - if (offset >= settings.step*30) { - // if offset is larger than a half step, round up - seconds += (settings.step*60) - offset; - } else { - // round down - seconds -= offset; - } - - if (seconds == _ONE_DAY && settings.show2400) { - return seconds; - } - - return seconds%_ONE_DAY; - } - }, - scrollDefault: null, - selectOnBlur: false, - show2400: false, - showDuration: false, - showOn: ['click', 'focus'], - showOnFocus: true, - step: 30, - stopScrollPropagation: false, - timeFormat: 'g:ia', - typeaheadHighlight: true, - useSelect: false - }; -})); +})); \ No newline at end of file diff --git a/web_widget_timepicker/static/src/xml/web_widget_timepicker.xml b/web_widget_timepicker/static/src/xml/web_widget_timepicker.xml index dcaeda0d..c123eb6e 100644 --- a/web_widget_timepicker/static/src/xml/web_widget_timepicker.xml +++ b/web_widget_timepicker/static/src/xml/web_widget_timepicker.xml @@ -2,7 +2,7 @@ - + - + diff --git a/web_widget_timepicker/views/web_widget_timepicker_assets.xml b/web_widget_timepicker/views/web_widget_timepicker_assets.xml index 290bf8b6..8e67bb53 100644 --- a/web_widget_timepicker/views/web_widget_timepicker_assets.xml +++ b/web_widget_timepicker/views/web_widget_timepicker_assets.xml @@ -1,14 +1,18 @@ - - - - - + +