diff options
Diffstat (limited to 'addons/web/static/src/js')
41 files changed, 391 insertions, 287 deletions
diff --git a/addons/web/static/src/js/chrome/control_panel.js b/addons/web/static/src/js/chrome/control_panel.js index 0913b574a3d..fb265390f51 100644 --- a/addons/web/static/src/js/chrome/control_panel.js +++ b/addons/web/static/src/js/chrome/control_panel.js @@ -245,11 +245,12 @@ var ControlPanel = Widget.extend({ var self = this; var is_last = (index === length-1); var li_content = bc.title && _.escape(bc.title.trim()) || data.noDisplayContent; - var $bc = $('<li>') - .append(is_last ? li_content : $('<a>').html(li_content)) + var $bc = $('<li>', {class: 'breadcrumb-item'}) + .append(is_last ? li_content : $('<a>', {href: '#'}).html(li_content)) .toggleClass('active', is_last); if (!is_last) { - $bc.click(function () { + $bc.click(function (ev) { + ev.preventDefault(); self.trigger_up('breadcrumb_clicked', {controllerID: bc.controllerID}); }); } diff --git a/addons/web/static/src/js/chrome/menu.js b/addons/web/static/src/js/chrome/menu.js index 1e253ba54f7..c96d4b17f8c 100644 --- a/addons/web/static/src/js/chrome/menu.js +++ b/addons/web/static/src/js/chrome/menu.js @@ -2,6 +2,7 @@ odoo.define('web.Menu', function (require) { "use strict"; var core = require('web.core'); +var dom = require('web.dom'); var session = require('web.session'); var Widget = require('web.Widget'); @@ -37,66 +38,10 @@ var Menu = Widget.extend({ } this.trigger('menu_bound'); - var lazyreflow = _.debounce(this.reflow.bind(this), 200); - core.bus.on('resize', this, function() { - if ($(window).width() < 768 ) { - lazyreflow('all_outside'); - } else { - lazyreflow(); - } - }); - core.bus.trigger('resize'); + dom.initAutoMoreMenu(this.$el); this.is_bound.resolve(); }, - - /** - * Reflow the menu items and dock overflowing items into a "More" menu item. - * Automatically called when 'menu_bound' event is triggered and on window resizing. - * - * @param {string} behavior If set to 'all_outside', all the items are displayed. - * If not set, only the overflowing items are hidden. - */ - reflow: function(behavior) { - var self = this; - var $more_container = this.$('#menu_more_container').hide(); - var $more = this.$('#menu_more'); - var $systray = this.$el.parents().find('.oe_systray'); - - $more.children('li').insertBefore($more_container); // Pull all the items out of the more menu - - // 'all_outside' beahavior should display all the items, so hide the more menu and exit - if (behavior === 'all_outside') { - // Show list of menu items - self.$el.show(); - this.$el.find('li').show(); - $more_container.hide(); - return; - } - - // Hide all menu items - var $toplevel_items = this.$el.find('li').not($more_container).not($systray.find('li')).hide(); - // Show list of menu items (which is empty for now since all menu items are hidden) - self.$el.show(); - $toplevel_items.each(function() { - var remaining_space = self.$el.parent().width() - $more_container.outerWidth(); - self.$el.parent().children(':visible').each(function() { - remaining_space -= $(this).outerWidth(); - }); - - if ($(this).width() >= remaining_space) { - return false; // the current item will be appended in more_container - } - $(this).show(); // show the current item in menu bar - }); - $more.append($toplevel_items.filter(':hidden').show()); - $more_container.toggle(!!$more.children().length); - // Hide toplevel item if there is only one - var $toplevel = self.$el.children("li:visible"); - if ($toplevel.length === 1) { - $toplevel.hide(); - } - }, /** * Opens a given menu by id, as if a user had browsed to that menu by hand * except does not trigger any event on the way @@ -143,7 +88,7 @@ var Menu = Widget.extend({ } // add a tooltip to cropped menu items this.$secondary_menus.find('.oe_secondary_submenu li a span').each(function() { - $(this).tooltip(this.scrollWidth > this.clientWidth ? {title: $(this).text().trim(), placement: 'right'} :'destroy'); + $(this).tooltip(this.scrollWidth > this.clientWidth ? {title: $(this).text().trim(), placement: 'right'} : 'dispose'); }); }, /** diff --git a/addons/web/static/src/js/chrome/sidebar.js b/addons/web/static/src/js/chrome/sidebar.js index 47d598aaeb8..d02c936f470 100644 --- a/addons/web/static/src/js/chrome/sidebar.js +++ b/addons/web/static/src/js/chrome/sidebar.js @@ -11,7 +11,7 @@ var _t = core._t; var Sidebar = Widget.extend({ events: { - "click .dropdown-menu li a": "_onDropdownClicked" + "click .dropdown-item": "_onDropdownClicked" }, /** * @override @@ -177,9 +177,10 @@ var Sidebar = Widget.extend({ this.$el.html(QWeb.render('Sidebar', {widget: this})); // Hides Sidebar sections when item list is empty - this.$('.o_dropdown').each(function () { - if (!$(this).find('li').length) { - $(this).hide(); + _.each(this.$('.o_dropdown'), function (el) { + var $dropdown = $(el); + if (!$dropdown.find('.dropdown-item').length) { + $dropdown.hide(); } }); this.$("[title]").tooltip({ diff --git a/addons/web/static/src/js/chrome/user_menu.js b/addons/web/static/src/js/chrome/user_menu.js index 84c16b51f82..1251c9b6b5b 100644 --- a/addons/web/static/src/js/chrome/user_menu.js +++ b/addons/web/static/src/js/chrome/user_menu.js @@ -23,7 +23,7 @@ var UserMenu = Widget.extend({ start: function () { var self = this; var session = this.getSession(); - this.$el.on('click', 'li a[data-menu]', function (ev) { + this.$el.on('click', '[data-menu]', function (ev) { ev.preventDefault(); var menu = $(this).data('menu'); self['_onMenu' + menu.charAt(0).toUpperCase() + menu.slice(1)](); diff --git a/addons/web/static/src/js/chrome/web_client.js b/addons/web/static/src/js/chrome/web_client.js index c4886cd0e89..d3ebabfab1e 100644 --- a/addons/web/static/src/js/chrome/web_client.js +++ b/addons/web/static/src/js/chrome/web_client.js @@ -3,6 +3,7 @@ odoo.define('web.WebClient', function (require) { var AbstractWebClient = require('web.AbstractWebClient'); var config = require('web.config'); +var core = require('web.core'); var data_manager = require('web.data_manager'); var dom = require('web.dom'); var framework = require('web.framework'); @@ -48,6 +49,9 @@ return AbstractWebClient.extend({ return $.when(systray_menu_loaded, user_menu_loaded).then(function() { self.menu.start(); self.bind_hashchange(); + setTimeout(function () { + self.menu.$el.removeClass('d-none'); + }, 200) }); }, @@ -165,7 +169,7 @@ return AbstractWebClient.extend({ toggle_fullscreen: function(fullscreen) { this._super(fullscreen); if (!fullscreen) { - this.menu.reflow(); + core.bus.trigger('resize'); } }, diff --git a/addons/web/static/src/js/core/dialog.js b/addons/web/static/src/js/core/dialog.js index 847845ae17e..9cca9c1127a 100644 --- a/addons/web/static/src/js/core/dialog.js +++ b/addons/web/static/src/js/core/dialog.js @@ -40,7 +40,7 @@ var Dialog = Widget.extend({ * button is added to allow closing the dialog * @param {string} [options.buttons[].text] * @param {string} [options.buttons[].classes] - * Default to 'btn-primary' if only one button, 'btn-default' + * Default to 'btn-primary' if only one button, 'btn-secondary' * otherwise * @param {boolean} [options.buttons[].close=false] * @param {function} [options.buttons[].click] @@ -123,7 +123,7 @@ var Dialog = Widget.extend({ _.each(buttons, function (buttonData) { var $button = dom.renderButton({ attrs: { - class: buttonData.classes || (buttons.length > 1 ? 'btn-default' : 'btn-primary'), + class: buttonData.classes || (buttons.length > 1 ? 'btn-secondary' : 'btn-primary'), disabled: buttonData.disabled, }, icon: buttonData.icon, diff --git a/addons/web/static/src/js/core/dom.js b/addons/web/static/src/js/core/dom.js index e82db818bb5..a34a207fda8 100644 --- a/addons/web/static/src/js/core/dom.js +++ b/addons/web/static/src/js/core/dom.js @@ -20,6 +20,7 @@ odoo.define('web.dom', function (require) { * something happens in the DOM. */ +var config = require('web.config'); var core = require('web.core'); /** @@ -208,11 +209,10 @@ return { * @param {Object} options * @param {Object} [options.attrs] - Attributes to put on the button element * @param {string} [options.attrs.type='button'] - * @param {string} [options.attrs.class='btn-default'] + * @param {string} [options.attrs.class='btn-secondary'] * Note: automatically completed with "btn btn-X" * (@see options.size for the value of X) - * @param {string} [options.size='sm'] - @see options.attrs.class - * Note: use 'md' for no sizing class + * @param {string} [options.size] - @see options.attrs.class * @param {string} [options.icon] * The specific fa icon class (for example "fa-home") or an URL for * an image to use as icon. @@ -235,10 +235,10 @@ return { } jQueryParams.class = 'btn'; - if (options.size !== 'md') { - jQueryParams.class += (' btn-' + (options.size || 'sm')); + if (options.size) { + jQueryParams.class += (' btn-' + options.size); } - jQueryParams.class += (' ' + (extraClasses || 'btn-default')); + jQueryParams.class += (' ' + (extraClasses || 'btn-secondary')); var $button = $('<button/>', jQueryParams); @@ -262,33 +262,41 @@ return { return $button; }, /** - * Renders a checkbox with standard odoo template. This does not use any xml - * template to avoid forcing the frontend part to lazy load a xml file for - * each widget which might want to create a simple checkbox. + * Renders a checkbox with standard odoo/BS template. This does not use any + * xml template to avoid forcing the frontend part to lazy load a xml file + * for each widget which might want to create a simple checkbox. * * @param {Object} [options] * @param {Object} [options.prop] * Allows to set the input properties (disabled and checked states). * @param {string} [options.text] * The checkbox's associated text. If none is given then a simple - * checkbox without label structure is rendered. + * checkbox is rendered. * @returns {jQuery} */ renderCheckbox: function (options) { - var $container = $('<div class="o_checkbox"><input type="checkbox"/><span/></div>'); - if (options && options.prop) { - $container.children('input').prop(options.prop); + var id = _.uniqueId('checkbox-'); + var $container = $('<div/>', { + class: 'custom-control custom-checkbox', + }); + var $input = $('<input/>', { + type: 'checkbox', + id: id, + class: 'custom-control-input', + }); + var $label = $('<label/>', { + for: id, + class: 'custom-control-label', + text: options && options.text || '', + }); + if (!options || !options.text) { + $label.html('​'); // BS checkboxes need some label content (so + // add a zero-width space when there is no text) } - if (options && options.text) { - $container = $('<label/>').append( - $container, - $('<span/>', { - class: 'ml8', - text: options.text, - }) - ); + if (options && options.prop) { + $input.prop(options.prop); } - return $container; + return $container.append($input, $label); }, /** * Sets the selection range of a given input or textarea @@ -308,5 +316,117 @@ return { .select(); } }, + /** + * Creates an automatic 'more' dropdown-menu for a set of navbar items. + * + * @param {jQuery} $el + * @param {Object} [options] + * @param {string} [options.unfoldable='none'] + * @param {function} [options.maxWidth] + * @param {string} [options.sizeClass='SM'] + */ + initAutoMoreMenu: function ($el, options) { + options = _.extend({ + unfoldable: 'none', + maxWidth: false, + sizeClass: 'SM', + }, options || {}); + + var $extraItemsToggle = null; + + var debouncedAdapt = _.debounce(_adapt, 250); + core.bus.on('resize', null, debouncedAdapt); + _adapt(); + + $el.data('dom:autoMoreMenu:destroy', function () { + _restore(); + core.bus.off('resize', null, debouncedAdapt); + $el.removeData('dom:autoMoreMenu:destroy'); + }); + + function _restore() { + if ($extraItemsToggle === null) { + return; + } + var $items = $extraItemsToggle.children('.dropdown-menu').children(); + $items.addClass('nav-item'); + $items.children().removeClass('dropdown-item').addClass('nav-link'); + $items.insertBefore($extraItemsToggle); + $extraItemsToggle.remove(); + $extraItemsToggle = null; + } + + function _adapt() { + if (!$el.is(':visible')) { + return; + } + + _restore(); + if (config.device.size_class <= config.device.SIZES[options.sizeClass]) { + return; + } + + var $allItems = $el.children(); + var $unfoldableItems = $allItems.filter(options.unfoldable); + var $items = $allItems.not($unfoldableItems); + + var maxWidth = 0; + if (options.maxWidth) { + maxWidth = options.maxWidth(); + } else { + var rect = $el[0].getBoundingClientRect(); + var style = window.getComputedStyle($el[0]); + maxWidth = (rect.right - rect.left); + maxWidth -= (parseFloat(style.paddingLeft) + parseFloat(style.paddingRight) + parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth)); + maxWidth -= _.reduce($unfoldableItems, function (sum, el) { + return sum + computeFloatOuterWidthWithMargins(el); + }, 0); + } + + var nbItems = $items.length; + var menuItemsWidth = _.reduce($items, function (sum, el) { + return sum + computeFloatOuterWidthWithMargins(el); + }, 0); + + if (maxWidth - menuItemsWidth >= -0.001) { + return + } + + var $dropdownMenu = $('<ul/>', {class: 'dropdown-menu'}); + $extraItemsToggle = $('<li/>', {class: 'nav-item dropdown o_extra_menu_items'}) + .append($('<a/>', {href: '#', class: 'nav-link dropdown-toggle o-no-caret', 'data-toggle': 'dropdown'}) + .append($('<i/>', {class: 'fa fa-plus'}))) + .append($dropdownMenu); + $extraItemsToggle.insertAfter($items.last()); + + menuItemsWidth += computeFloatOuterWidthWithMargins($extraItemsToggle[0]); + do { + menuItemsWidth -= computeFloatOuterWidthWithMargins($items.eq(--nbItems)[0]); + } while (menuItemsWidth > maxWidth); + + var $extraItems = $items.slice(nbItems).detach(); + $extraItems.removeClass('nav-item'); + $extraItems.children().removeClass('nav-link').addClass('dropdown-item'); + $dropdownMenu.append($extraItems); + $extraItemsToggle.find('.nav-link').toggleClass('active', $extraItems.children().hasClass('active')); + } + + function computeFloatOuterWidthWithMargins(el) { + var rect = el.getBoundingClientRect(); + var style = window.getComputedStyle(el); + return rect.right - rect.left + parseFloat(style.marginLeft) + parseFloat(style.marginRight); + } + }, + /** + * Cleans what has been done by `initAutoMoreMenu'. + * + * @param {jQuery} $el + */ + destroyAutoMoreMenu: function ($el) { + var destroyFunc = $el.data('dom:autoMoreMenu:destroy'); + if (destroyFunc) { + destroyFunc.call(null); + } + }, }; }); diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js index eab0093ed50..7cf72791be5 100644 --- a/addons/web/static/src/js/fields/abstract_field.js +++ b/addons/web/static/src/js/fields/abstract_field.js @@ -304,7 +304,6 @@ var AbstractField = Widget.extend({ this._reset(record, event); return this._render() || $.when(); }, - /** * Remove the invalid class on a field */ @@ -312,7 +311,15 @@ var AbstractField = Widget.extend({ this.$el.removeClass('o_field_invalid'); this.$el.removeAttr('aria-invalid'); }, - + /** + * Sets the given id on the focusable element of the field and as 'for' + * attribute of potential internal labels. + * + * @param {string} id + */ + setIDForLabel: function (id) { + this.getFocusableElement().attr('id', id); + }, /** * add the invalid class on a field */ @@ -320,7 +327,6 @@ var AbstractField = Widget.extend({ this.$el.addClass('o_field_invalid'); this.$el.attr('aria-invalid', 'true'); }, - /** * Update the modifiers with the newest value. * Now this.attrs.modifiersValue can be used consistantly even with diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index 51816220c9f..bd13305df0c 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -701,8 +701,8 @@ var FieldBoolean = AbstractField.extend({ // The formatValue of boolean fields renders HTML elements similar to // the one rendered by the widget itself. Even though the event might // have been fired on the non-widget version of this field, we can still - // test the presence of its o_checkbox class. - if (activated && options && options.event && $(options.event.target).parents('.o_checkbox').length) { + // test the presence of its custom class. + if (activated && options && options.event && $(options.event.target).closest('.custom-control.custom-checkbox').length) { this._setValue(!this.value); // Toggle the checkbox } return activated; @@ -737,6 +737,15 @@ var FieldBoolean = AbstractField.extend({ } return rendered; }, + /** + * Associates the 'for' attribute of the internal label. + * + * @override + */ + setIDForLabel: function (id) { + this._super.apply(this, arguments); + this.$('.custom-control-label').attr('for', id); + }, //-------------------------------------------------------------------------- // Private @@ -1612,10 +1621,7 @@ var AttachmentImage = AbstractField.extend({ var StateSelectionWidget = AbstractField.extend({ template: 'FormSelection', events: { - 'click a': function (e) { - e.preventDefault(); - }, - 'click li': '_setSelection' + 'click .dropdown-item': '_setSelection', }, supportedFieldTypes: ['selection'], @@ -1696,13 +1702,12 @@ var StateSelectionWidget = AbstractField.extend({ * @param {MouseEvent} ev */ _setSelection: function (ev) { - var li = $(ev.target).closest('li'); - if (li.length) { - var value = String(li.data('value')); - this._setValue(value); - if (this.mode === 'edit') { - this._render(); - } + ev.preventDefault(); + var $item = $(ev.currentTarget); + var value = String($item.data('value')); + this._setValue(value); + if (this.mode === 'edit') { + this._render(); } }, }); @@ -1778,7 +1783,7 @@ var LabelSelection = AbstractField.extend({ _render: function () { this.classes = this.nodeOptions && this.nodeOptions.classes || {}; var labelClass = this.classes[this.value] || 'primary'; - this.$el.addClass('label label-' + labelClass).text(this._formatValue(this.value)); + this.$el.addClass('badge badge-' + labelClass).text(this._formatValue(this.value)); }, }); @@ -2693,9 +2698,9 @@ var ImageSelection = AbstractField.extend({ var self = this; this.$el.empty(); _.each(this.nodeOptions, function (val, key) { - var $container = $('<div>').addClass('col-xs-3 text-center'); + var $container = $('<div>').addClass('col-3 text-center'); var $img = $('<img>') - .addClass('img img-responsive img-thumbnail ml16') + .addClass('img img-fluid img-thumbnail ml16') .toggleClass('btn-info', key === self.value) .attr('src', val.image_link) .data('key', key); diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index c1c458d1935..ba83de415e7 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -968,7 +968,7 @@ var FieldX2Many = AbstractField.extend({ _renderButtons: function () { if (!this.isReadonly && this.view.arch.tag === 'kanban') { this.$buttons = $(qweb.render('KanbanView.buttons', { - btnClass: 'btn-default', + btnClass: 'btn-secondary', create_text: this.nodeOptions.create_text, })); this.$buttons.on('click', 'button.o-kanban-button-new', this._onAddRecord.bind(this)); @@ -1887,7 +1887,7 @@ var FormFieldMany2ManyTags = FieldMany2ManyTags.extend({ this.$color_picker.dropdown('toggle'); this.$color_picker.attr("tabindex", 1).focus(); if (!tagColor) { - this.$('.o_checkbox input').prop('checked', true); + this.$('.custom-checkbox input').prop('checked', true); } } }, @@ -1909,7 +1909,7 @@ var FormFieldMany2ManyTags = FieldMany2ManyTags.extend({ var changes = {}; if ($target.is('.o_hide_in_kanban')) { - var $checkbox = $('.o_hide_in_kanban .o_checkbox input'); + var $checkbox = $('.o_hide_in_kanban .custom-checkbox input'); $checkbox.prop('checked', !$checkbox.prop('checked')); // toggle checkbox this.prevColors = this.prevColors ? this.prevColors : {}; if ($checkbox.is(':checked')) { diff --git a/addons/web/static/src/js/fields/upgrade_fields.js b/addons/web/static/src/js/fields/upgrade_fields.js index 119a64881e3..dd91ca2efc2 100644 --- a/addons/web/static/src/js/fields/upgrade_fields.js +++ b/addons/web/static/src/js/fields/upgrade_fields.js @@ -94,7 +94,7 @@ var AbstractFieldUpgrade = { this._super.apply(this, arguments); this._insertEnterpriseLabel($("<span>", { text: "Enterprise", - 'class': "label label-primary oe_inline o_enterprise_label" + 'class': "badge badge-primary oe_inline o_enterprise_label" })); }, /** diff --git a/addons/web/static/src/js/libs/bootstrap.js b/addons/web/static/src/js/libs/bootstrap.js index 04ca2381d7d..135a6b65df6 100644 --- a/addons/web/static/src/js/libs/bootstrap.js +++ b/addons/web/static/src/js/libs/bootstrap.js @@ -7,23 +7,19 @@ odoo.define('web.bootstrap.extensions', function () { */ /* Bootstrap defaults overwrite */ -$.fn.tooltip.Constructor.DEFAULTS.placement = 'auto top'; -$.fn.tooltip.Constructor.DEFAULTS.html = true; -$.fn.tooltip.Constructor.DEFAULTS.trigger = 'hover focus click'; -$.fn.tooltip.Constructor.DEFAULTS.container = 'body'; -$.fn.tooltip.Constructor.DEFAULTS.delay = { show: 1000, hide: 0 }; -//overwrite bootstrap tooltip method to prevent showing 2 tooltip at the same time -var bootstrap_show_function = $.fn.tooltip.Constructor.prototype.show; -$.fn.modal.Constructor.prototype.enforceFocus = function () { }; +$.fn.tooltip.Constructor.Default.placement = 'top'; +$.fn.tooltip.Constructor.Default.fallbackPlacement = ['bottom', 'right', 'left', 'top']; +$.fn.tooltip.Constructor.Default.html = true; +$.fn.tooltip.Constructor.Default.trigger = 'hover focus click'; +$.fn.tooltip.Constructor.Default.container = 'body'; +$.fn.tooltip.Constructor.Default.delay = { show: 1000, hide: 0 }; + +var bootstrapShowFunction = $.fn.tooltip.Constructor.prototype.show; $.fn.tooltip.Constructor.prototype.show = function () { + // Overwrite bootstrap tooltip method to prevent showing 2 tooltip at the + // same time $('.tooltip').remove(); - //the following fix the bug when using placement - //auto and the parent element does not exist anymore resulting in - //an error. This should be remove once we updade bootstrap to a version that fix the bug - //edit: bug has been fixed here : https://github.com/twbs/bootstrap/pull/13752 - var e = $.Event('show.bs.' + this.type); - var inDom = $.contains(document.documentElement, this.$element[0]); - if (e.isDefaultPrevented() || !inDom) return; - return bootstrap_show_function.call(this); + + return bootstrapShowFunction.call(this); }; }); diff --git a/addons/web/static/src/js/services/config.js b/addons/web/static/src/js/services/config.js index d05a263ef4e..946f7d39f55 100644 --- a/addons/web/static/src/js/services/config.js +++ b/addons/web/static/src/js/services/config.js @@ -41,22 +41,23 @@ var config = { /** * A frequent use case is to have a different render in 'mobile' mode, * meaning when the screen is small. This flag (boolean) is true when - * the size is XXS or XS. It is also updated dynamically. + * the size is XS/VSM/SM. It is also updated dynamically. * * @type Boolean */ isMobile: null, /** - * Mapping between the numbers 0,1,2,3,4,5 and some descriptions + * Mapping between the numbers 0,1,2,3,4,5,6 and some descriptions */ - SIZES: { XXS: 0, XS: 1, SM: 2, MD: 3, LG: 4, XL: 5 }, + SIZES: { XS: 0, VSM: 1, SM: 2, MD: 3, LG: 4, XL: 5, XXL: 6 }, }, }; var medias = [ window.matchMedia('(max-width: 474px)'), - window.matchMedia('(min-width: 475px) and (max-width: 767px)'), + window.matchMedia('(min-width: 475px) and (max-width: 575px)'), + window.matchMedia('(min-width: 576px) and (max-width: 767px)'), window.matchMedia('(min-width: 768px) and (max-width: 991px)'), window.matchMedia('(min-width: 992px) and (max-width: 1199px)'), window.matchMedia('(min-width: 1200px) and (max-width: 1533px)'), @@ -69,8 +70,8 @@ var medias = [ * @returns {integer} a number between 0 and 5, included */ function _getSizeClass() { - for(var i = 0 ; i < medias.length ; i++) { - if(medias[i].matches) { + for (var i = 0 ; i < medias.length ; i++) { + if (medias[i].matches) { return i; } } @@ -83,7 +84,7 @@ function _updateSizeProps() { var sc = _getSizeClass(); if (sc !== config.device.size_class) { config.device.size_class = sc; - config.device.isMobile = config.device.size_class <= config.device.SIZES.SM; + config.device.isMobile = config.device.size_class <= config.device.SIZES.MD; } } diff --git a/addons/web/static/src/js/services/crash_manager.js b/addons/web/static/src/js/services/crash_manager.js index a5b8cb32c13..b105913464e 100644 --- a/addons/web/static/src/js/services/crash_manager.js +++ b/addons/web/static/src/js/services/crash_manager.js @@ -142,7 +142,7 @@ var CrashManager = core.Class.extend({ }); }); dialog.on("closed", this, function () { - $clipboardBtn.tooltip("destroy"); + $clipboardBtn.tooltip('dispose'); clipboard.destroy(); }); diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js index 13a00dda046..197892f3361 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -152,7 +152,6 @@ var BasicRenderer = AbstractRenderer.extend({ // widget's $el $node = $node.length ? $node : widget.$el; $node.tooltip({ - delay: { show: 1000, hide: 0 }, title: function () { return qweb.render('WidgetLabel.tooltip', { debug: config.debug, diff --git a/addons/web/static/src/js/views/calendar/calendar_renderer.js b/addons/web/static/src/js/views/calendar/calendar_renderer.js index 4b4050b67cc..ffb4f41ad6b 100644 --- a/addons/web/static/src/js/views/calendar/calendar_renderer.js +++ b/addons/web/static/src/js/views/calendar/calendar_renderer.js @@ -98,7 +98,7 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, { this.many2one.filter_ids = _.without(_.pluck(this.filters, 'value'), 'all'); } this.$el.on('click', '.o_remove', this._onFilterRemove.bind(this)); - this.$el.on('click', '.o_checkbox input', this._onFilterActive.bind(this)); + this.$el.on('click', '.custom-checkbox input', this._onFilterActive.bind(this)); }, //-------------------------------------------------------------------------- diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index 646dfdd8c40..276173c6f9a 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -35,7 +35,7 @@ var FormRenderer = BasicRenderer.extend({ * @override */ start: function () { - if (config.device.size_class <= config.device.SIZES.XXS) { + if (config.device.size_class <= config.device.SIZES.XS) { this.$el.addClass('o_xxs_form_view'); } return this._super.apply(this, arguments); @@ -159,9 +159,9 @@ var FormRenderer = BasicRenderer.extend({ .removeAttr('disabled'); }, /** - * Put the focus on the last activated widget. + * Put the focus on the last activated widget. * This function is used when closing a dialog to give the focus back to the - * form that has opened it and ensures that the focus is in the correct + * form that has opened it and ensures that the focus is in the correct * field. */ focusLastActivatedWidget: function () { @@ -181,7 +181,7 @@ var FormRenderer = BasicRenderer.extend({ var $notebook = $(this); var name = $notebook.data('name'); var index = -1; - $notebook.find('li').each(function (i) { + $notebook.find('.nav-link').each(function (i) { if ($(this).hasClass('active')) { index = i; } @@ -265,7 +265,6 @@ var FormRenderer = BasicRenderer.extend({ _addButtonTooltip: function (node, $button) { var self = this; $button.tooltip({ - delay: { show: 1000, hide: 0 }, title: function () { return qweb.render('WidgetButton.tooltip', { debug: config.debug, @@ -364,11 +363,11 @@ var FormRenderer = BasicRenderer.extend({ text: _t("More"), })); - var $ul = $("<ul>", {'class': "dropdown-menu o_dropdown_more", role: "menu"}); + var $dropdown = $("<div>", {'class': "dropdown-menu o_dropdown_more", role: "menu"}); _.each(folded_buttons, function ($button) { - $('<li>').appendTo($ul).append($button); + $button.addClass('dropdown-item').appendTo($dropdown); }); - $ul.appendTo($result); + $dropdown.appendTo($result); } this._handleAttributes($result, node); @@ -397,12 +396,13 @@ var FormRenderer = BasicRenderer.extend({ // Current API of odoo for rendering buttons is "if classes are given // use those on top of the 'btn' and 'btn-{size}' classes, otherwise act - // as if 'btn-default' class was given". The problem is that, for header - // buttons only, we allowed users to only indicate their custom classes - // without having to explicitely ask for the 'btn-default' class to be - // added. We force it so here when no bootstrap btn type class is found. - if ($button.not('.btn-primary, .btn-default, .btn-link, .btn-success, .btn-info, .btn-warning, .btn-danger').length) { - $button.addClass('btn-default'); + // as if 'btn-secondary' class was given". The problem is that, for + // header buttons only, we allowed users to only indicate their custom + // classes without having to explicitely ask for the 'btn-secondary' + // class to be added. We force it so here when no bootstrap btn type + // class is found. + if ($button.not('.btn-primary, .btn-secondary, .btn-link, .btn-success, .btn-info, .btn-warning, .btn-danger').length) { + $button.addClass('btn-secondary'); } this._addOnClickAction($button, node); @@ -612,10 +612,11 @@ var FormRenderer = BasicRenderer.extend({ 'data-toggle': 'tab', disable_anchor: 'true', href: '#' + page_id, + class: 'nav-link', role: 'tab', text: page.attrs.string, }); - return $('<li>').append($a); + return $('<li>', {class: 'nav-item'}).append($a); }, /** * @private @@ -761,7 +762,7 @@ var FormRenderer = BasicRenderer.extend({ _renderTagNotebook: function (node) { var self = this; var $headers = $('<ul class="nav nav-tabs">'); - var $pages = $('<div class="tab-content nav nav-tabs">'); + var $pages = $('<div class="tab-content">'); var autofocusTab = -1; // renderedTabs is used to aggregate the generated $headers and $pages // alongside their node, so that their modifiers can be registered once @@ -785,7 +786,7 @@ var FormRenderer = BasicRenderer.extend({ }); if (renderedTabs.length) { var tabToFocus = renderedTabs[Math.max(0, autofocusTab)]; - tabToFocus.$header.addClass('active'); + tabToFocus.$header.find('.nav-link').addClass('active'); tabToFocus.$page.addClass('active'); } // register the modifiers for each tab @@ -793,12 +794,13 @@ var FormRenderer = BasicRenderer.extend({ self._registerModifiers(tab.node, self.state, tab.$header, { callback: function (element, modifiers) { // if the active tab is invisible, activate the first visible tab instead - if (modifiers.invisible && element.$el.hasClass('active')) { - element.$el.removeClass('active'); + var $link = element.$el.find('.nav-link'); + if (modifiers.invisible && $link.hasClass('active')) { + $link.removeClass('active'); tab.$page.removeClass('active'); - var $firstVisibleTab = $headers.find('li:not(.o_invisible_modifier):first()'); + var $firstVisibleTab = $headers.find('li:not(.o_invisible_modifier):first() > a'); $firstVisibleTab.addClass('active'); - $pages.find($firstVisibleTab.find('a').attr('href')).addClass('active'); + $pages.find($firstVisibleTab.attr('href')).addClass('active'); } }, }); @@ -859,7 +861,7 @@ var FormRenderer = BasicRenderer.extend({ this.defs = defs; var $form = this._renderNode(this.arch).addClass(this.className); delete this.defs; - + return $.when.apply($, defs).then(function () { self._updateView($form.contents()); }, function () { @@ -867,7 +869,7 @@ var FormRenderer = BasicRenderer.extend({ }).then(function(){ if (self.lastActivatedFieldIndex >= 0) { self._activateNextFieldWidget(self.state, self.lastActivatedFieldIndex); - } + } }); }, /** @@ -900,7 +902,7 @@ var FormRenderer = BasicRenderer.extend({ // enterprise label will be displayed as many times as the field // exists on settings. var $widgets = self.$('.o_field_widget[name=' + widget.name + ']'); - var $label = idForLabel ? self.$('label[for=' + idForLabel + ']') : $(); + var $label = idForLabel ? self.$('.o_form_label[for=' + idForLabel + ']') : $(); $label = $label.eq($widgets.index(widget.$el)); if (config.debug || widget.attrs.help || widget.field.help) { self._addFieldTooltip(widget, $label); @@ -920,7 +922,7 @@ var FormRenderer = BasicRenderer.extend({ * @param {idForLabel} string */ _setIDForLabel: function (widget, idForLabel) { - widget.getFocusableElement().attr('id', idForLabel); + widget.setIDForLabel(idForLabel); }, //-------------------------------------------------------------------------- diff --git a/addons/web/static/src/js/views/graph/graph_controller.js b/addons/web/static/src/js/views/graph/graph_controller.js index e7f25c23fdd..b6a1c121f0f 100644 --- a/addons/web/static/src/js/views/graph/graph_controller.js +++ b/addons/web/static/src/js/views/graph/graph_controller.js @@ -45,7 +45,7 @@ var GraphController = AbstractController.extend(GroupByMenuInterfaceMixin,{ destroy: function () { if (this.$buttons) { // remove jquery's tooltip() handlers - this.$buttons.find('button').off().tooltip('destroy'); + this.$buttons.find('button').off().tooltip('dispose'); } this._super.apply(this, arguments); }, @@ -161,8 +161,9 @@ var GraphController = AbstractController.extend(GroupByMenuInterfaceMixin,{ this.$buttons .find('.o_graph_button[data-mode="' + state.mode + '"]') .addClass('active'); - this.$measureList.find('li').each(function (index, li) { - $(li).toggleClass('selected', $(li).data('field') === state.measure); + _.each(this.$measureList.find('.dropdown-item'), function (item) { + var $item = $(item); + $item.toggleClass('selected', $item.data('field') === state.measure); }); }, @@ -185,8 +186,7 @@ var GraphController = AbstractController.extend(GroupByMenuInterfaceMixin,{ } else if ($target.parents('.o_graph_measures_list').length) { event.preventDefault(); event.stopPropagation(); - parent = $target.parent(); - field = parent.data('field'); + field = $target.data('field'); this._setMeasure(field); } }, diff --git a/addons/web/static/src/js/views/graph/graph_renderer.js b/addons/web/static/src/js/views/graph/graph_renderer.js index 1fbd6572a9a..686bbaee842 100644 --- a/addons/web/static/src/js/views/graph/graph_renderer.js +++ b/addons/web/static/src/js/views/graph/graph_renderer.js @@ -243,7 +243,7 @@ return AbstractRenderer.extend({ svg.transition().duration(100); - var legend_right = config.device.size_class > config.device.SIZES.XS; + var legend_right = config.device.size_class > config.device.SIZES.VSM; var chart = nv.models.pieChart().labelType('percent'); chart.options({ diff --git a/addons/web/static/src/js/views/kanban/kanban_column.js b/addons/web/static/src/js/views/kanban/kanban_column.js index 95e3f1e85b1..a1c7e415470 100644 --- a/addons/web/static/src/js/views/kanban/kanban_column.js +++ b/addons/web/static/src/js/views/kanban/kanban_column.js @@ -153,7 +153,7 @@ var KanbanColumn = Widget.extend({ this.$el.toggleClass('o_column_folded', this.folded && !config.device.isMobile); var tooltip = this.data.count + _t(' records'); tooltip = '<p>' + tooltip + '</p>' + this.tooltipInfo; - this.$header.find('.o_kanban_header_title').tooltip({html: true}).attr('data-original-title', tooltip); + this.$header.find('.o_kanban_header_title').tooltip({}).attr('data-original-title', tooltip); if (!this.remaining) { this.$('.o_kanban_load_more').remove(); } else { diff --git a/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js b/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js index 7af6b677dcf..0bfe5027876 100644 --- a/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js +++ b/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js @@ -133,9 +133,8 @@ var KanbanColumnProgressBar = Widget.extend({ }); if (this.progressBarHelp && _.every(this.subgroupCounts, function (val) { return val === 0; })) { this.$el.tooltip({ - delay: '0', - trigger:'hover', - placement: 'top', + delay: 0, + trigger: 'hover', title: this.progressBarHelp, }); } @@ -152,9 +151,8 @@ var KanbanColumnProgressBar = Widget.extend({ // Adapt tooltip $bar.attr('data-original-title', count + ' ' + key); $bar.tooltip({ - delay: '0', - trigger:'hover', - placement: 'top' + delay: 0, + trigger: 'hover', }); // Adapt active state diff --git a/addons/web/static/src/js/views/kanban/kanban_column_quick_create.js b/addons/web/static/src/js/views/kanban/kanban_column_quick_create.js index 170b950a49d..b2a38d4e53e 100644 --- a/addons/web/static/src/js/views/kanban/kanban_column_quick_create.js +++ b/addons/web/static/src/js/views/kanban/kanban_column_quick_create.js @@ -184,7 +184,7 @@ var ColumnQuickCreate = Widget.extend({ examples: this.examples, })), buttons: [{ - classes: 'btn-primary pull-right', + classes: 'btn-primary float-right', close: true, text: _t('Got it'), }], diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js index 26846e2c511..ab05fabd517 100644 --- a/addons/web/static/src/js/views/kanban/kanban_controller.js +++ b/addons/web/static/src/js/views/kanban/kanban_controller.js @@ -119,7 +119,7 @@ var KanbanController = BasicController.extend({ return this._super.apply(this, arguments); }, /** - * In grouped mode, set 'Create' button as btn-default if there is no column + * In grouped mode, set 'Create' button as btn-secondary if there is no column * (except if we can't create new columns) * * @private diff --git a/addons/web/static/src/js/views/kanban/kanban_record.js b/addons/web/static/src/js/views/kanban/kanban_record.js index 247f8eca90f..627543b3422 100644 --- a/addons/web/static/src/js/views/kanban/kanban_record.js +++ b/addons/web/static/src/js/views/kanban/kanban_record.js @@ -99,8 +99,7 @@ var KanbanRecord = Widget.extend({ var tooltip = $el.attr('tooltip'); if (tooltip) { $el.tooltip({ - 'html': true, - 'title': self.qweb.render(tooltip, self.qweb_context) + title: self.qweb.render(tooltip, self.qweb_context) }); } }); @@ -350,7 +349,7 @@ var KanbanRecord = Widget.extend({ _setFieldDisplay: function ($el, fieldName) { // attribute display if (this.fieldsInfo[fieldName].display === 'right') { - $el.addClass('pull-right'); + $el.addClass('float-right'); } else if (this.fieldsInfo[fieldName].display === 'full') { $el.addClass('o_text_block'); } @@ -490,7 +489,7 @@ var KanbanRecord = Widget.extend({ if (elem === event.currentTarget) { ischild = false; } - var test_event = events && events.click && (events.click.length > 1 || events.click[0].namespace !== "tooltip"); + var test_event = events && events.click && (events.click.length > 1 || events.click[0].namespace !== 'bs.tooltip'); if (ischild) { children.push(elem); if (test_event) { diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js index 82a08b3b27f..20d68744f6a 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -653,7 +653,7 @@ var ListRenderer = BasicRenderer.extend({ return this._super(); } - var $table = $('<table>').addClass('o_list_view table table-condensed table-hover table-striped'); + var $table = $('<table>').addClass('o_list_view table table-sm table-hover table-striped'); this.$el .addClass('table-responsive') .append($table); diff --git a/addons/web/static/src/js/views/pivot/pivot_controller.js b/addons/web/static/src/js/views/pivot/pivot_controller.js index f0976c9a8b4..945cc132b03 100644 --- a/addons/web/static/src/js/views/pivot/pivot_controller.js +++ b/addons/web/static/src/js/views/pivot/pivot_controller.js @@ -164,9 +164,9 @@ var PivotController = AbstractController.extend({ fields: fields })); - this.$fieldSelection.find('ul').first() + this.$fieldSelection.find('.dropdown-menu').first() .css({top: top, left: left}) - .show(); + .addClass('show'); }, /** * @private @@ -186,7 +186,7 @@ var PivotController = AbstractController.extend({ var state = this.model.get({raw: true}); _.each(this.measures, function (measure, name) { var isSelected = _.contains(state.measures, name); - self.$buttons.find('li[data-field="' + name + '"]') + self.$buttons.find('.dropdown-item[data-field="' + name + '"]') .toggleClass('selected', isSelected); }); }, @@ -215,8 +215,7 @@ var PivotController = AbstractController.extend({ .then(this.update.bind(this, {}, {reload: false})); } if ($target.parents('.o_pivot_measures_list').length) { - var parent = $target.parent(); - var field = parent.data('field'); + var field = $target.data('field'); event.preventDefault(); event.stopPropagation(); this.model @@ -298,11 +297,11 @@ var PivotController = AbstractController.extend({ _onFieldMenuSelection: function (event) { event.preventDefault(); var $target = $(event.target); - if ($target.parent().hasClass('disabled')) { + if ($target.hasClass('disabled')) { event.stopPropagation(); return; } - var field = $target.parent().data('field'); + var field = $target.data('field'); var interval = $target.data('interval'); var header = this.model.getHeader(this.lastHeaderSelected); if (interval) { diff --git a/addons/web/static/src/js/views/pivot/pivot_renderer.js b/addons/web/static/src/js/views/pivot/pivot_renderer.js index b3953076161..9312b27f1ef 100644 --- a/addons/web/static/src/js/views/pivot/pivot_renderer.js +++ b/addons/web/static/src/js/views/pivot/pivot_renderer.js @@ -9,7 +9,7 @@ var QWeb = core.qweb; var PivotRenderer = AbstractRenderer.extend({ tagName: 'table', - className: 'table-hover table-condensed table-bordered', + className: 'table-hover table-sm table-bordered', events: _.extend({}, AbstractRenderer.prototype.events, { 'hover td': '_onTdHover', }), @@ -105,7 +105,7 @@ var PivotRenderer = AbstractRenderer.extend({ } $row.append($cell); - $cell.toggleClass('hidden-xs', (cell.expanded !== undefined) || (cell.measure !== undefined && j < headers[i].length - this.state.measures.length)); + $cell.toggleClass('d-none d-md-table-cell', (cell.expanded !== undefined) || (cell.measure !== undefined && j < headers[i].length - this.state.measures.length)); if (cell.height > 1) { $cell.css('padding', 0); } @@ -159,7 +159,7 @@ var PivotRenderer = AbstractRenderer.extend({ } $row.append($cell); - $cell.toggleClass('hidden-xs', j < length - nbrMeasures); + $cell.toggleClass('d-none d-md-table-cell', j < length - nbrMeasures); } $tbody.append($row); } diff --git a/addons/web/static/src/js/views/search/favorites_menu.js b/addons/web/static/src/js/views/search/favorites_menu.js index 1e62ffb5ce3..47e99aec573 100644 --- a/addons/web/static/src/js/views/search/favorites_menu.js +++ b/addons/web/static/src/js/views/search/favorites_menu.js @@ -14,14 +14,11 @@ var _t = core._t; return Widget.extend({ template: 'SearchView.FavoriteMenu', events: { - 'click li': function (event) { - event.stopImmediatePropagation(); + 'click .dropdown-item': function (ev) { + ev.preventDefault(); }, - 'click li a': function (event) { - event.preventDefault(); - }, - 'click .o_save_search a': function (event) { - event.preventDefault(); + 'click .o_save_search': function (ev) { + ev.preventDefault(); this.toggle_save_menu(); }, 'click .o_save_name button': 'save_favorite', @@ -49,8 +46,8 @@ return Widget.extend({ this.$save_search = this.$('.o_save_search'); this.$save_name = this.$('.o_save_name'); this.$inputs = this.$save_name.find('input'); - this.$user_divider = this.$('.divider.user_filter'); - this.$shared_divider = this.$('.divider.shared_filter'); + this.$user_divider = this.$('.dropdown-divider.user_filter'); + this.$shared_divider = this.$('.dropdown-divider.shared_filter'); this.$inputs.eq(0).val(this.searchview.get_title()); var $shared_filter = this.$inputs.eq(1), $default_filter = this.$inputs.eq(2); @@ -207,7 +204,7 @@ return Widget.extend({ }; }, clear_selection: function () { - this.$('li.selected').removeClass('selected'); + this.$('.selected').removeClass('selected'); }, /** * Adds a filter description to the filters dict @@ -230,10 +227,10 @@ return Widget.extend({ this.$shared_divider.show(); } if (!(key in this.$filters)) { - var $filter = $('<li>') + var $filter = $('<div>', {class: 'position-relative'}) .addClass(filter.user_id ? 'o-searchview-custom-private' : 'o-searchview-custom-public') - .append($('<a>', {'href': '#'}).text(filter.name)) + .append($('<a>', {href: '#', class: 'dropdown-item'}).text(filter.name)) .append($('<span>', { class: 'fa fa-trash-o o-remove-filter', on: { @@ -256,7 +253,7 @@ return Widget.extend({ }); if (current) { this.query.remove(current); - this.$filters[this.key_for(filter)].removeClass('selected'); + this.$filters[this.key_for(filter)].find('.dropdown-item').removeClass('selected'); return; } this.query.reset([this.facet_for(filter)], { @@ -268,7 +265,7 @@ return Widget.extend({ this.searchview.dataset.set_sort(sort_items); } - this.$filters[this.key_for(filter)].addClass('selected'); + this.$filters[this.key_for(filter)].find('.dropdown-item').addClass('selected'); }, remove_filter: function (filter, $filter, key) { var self = this; diff --git a/addons/web/static/src/js/views/search/filters_menu.js b/addons/web/static/src/js/views/search/filters_menu.js index 221f98faa97..af5425ec2d7 100644 --- a/addons/web/static/src/js/views/search/filters_menu.js +++ b/addons/web/static/src/js/views/search/filters_menu.js @@ -76,7 +76,7 @@ var FiltersMenu = DropdownMenu.extend({ category: 'filterCategory', title: 'Filters', icon: 'fa fa-filter', - symbol: this.isMobile ? 'fa fa-chevron-right pull-right mt4' : 'caret' + symbol: this.isMobile ? 'fa fa-chevron-right float-right mt4' : false, }; this._super(parent, dropdownHeader, filters, this.fields); }, @@ -88,7 +88,7 @@ var FiltersMenu = DropdownMenu.extend({ * @private */ start: function () { - this.$menu = this.$('ul.o_dropdown_menu'); + this.$menu = this.$('.o_dropdown_menu'); this.$menu.addClass('o_filters_menu'); var generatorMenu = QWeb.render('FiltersMenuGenerator', {widget: this}); this.$menu.append(generatorMenu); diff --git a/addons/web/static/src/js/views/search/groupby_menu.js b/addons/web/static/src/js/views/search/groupby_menu.js index 7b3b8e31fef..d686a4d55cb 100644 --- a/addons/web/static/src/js/views/search/groupby_menu.js +++ b/addons/web/static/src/js/views/search/groupby_menu.js @@ -14,7 +14,7 @@ var DEFAULT_INTERVAL = 'month'; var GroupByMenu = DropdownMenu.extend({ events: _.extend({}, DropdownMenu.prototype.events, { - 'click .o_add_custom_group a': '_onAddCustomGroupClick', + 'click .o_add_custom_group': '_onAddCustomGroupClick', 'click button.o_apply_group': '_onButtonApplyClick', 'click .o_group_selector': '_onGroupSelectorClick', }), @@ -78,14 +78,14 @@ var GroupByMenu = DropdownMenu.extend({ if (options && options.headerStyle === 'primary') { style = { el: {class: 'btn-group o_graph_groupbys_menu o_dropdown', attrs: {'role': 'group'}}, - mainButton: {class: 'btn btn-primary btn-sm dropdown-toggle'}, + mainButton: {class: 'btn btn-primary dropdown-toggle'}, }; } var dropdownHeader = { category: 'groupByCategory', title: 'Group By', icon: 'fa fa-bars', - symbol: this.isMobile ? 'fa fa-chevron-right pull-right mt4' : 'caret', + symbol: this.isMobile ? 'fa fa-chevron-right float-right mt4' : false, style: style, }; this._super(parent, dropdownHeader, groupbys, options); @@ -98,7 +98,7 @@ var GroupByMenu = DropdownMenu.extend({ * @private */ start: function () { - this.$menu = this.$('ul.o_dropdown_menu'); + this.$menu = this.$('.o_dropdown_menu'); this.$menu.addClass('o_group_by_menu'); var $generatorMenu = QWeb.render('GroupbyMenuGenerator', {widget: this}); this.$menu.append($generatorMenu); diff --git a/addons/web/static/src/js/views/search/search_inputs.js b/addons/web/static/src/js/views/search/search_inputs.js index ee179d7da74..81f53ee4053 100644 --- a/addons/web/static/src/js/views/search/search_inputs.js +++ b/addons/web/static/src/js/views/search/search_inputs.js @@ -503,8 +503,13 @@ var ManyToOneField = CharField.extend({ var FilterGroup = Input.extend(/** @lends instance.web.search.FilterGroup# */{ template: 'SearchView.filters', + events: { + click: 'toggle_filter', + }, + icon: "fa-filter", completion_label: _lt("Filter on: %s"), + /** * Inclusive group of filters, creates a continuous "button" with clickable * sections (the normal display for filters is to be a self-contained button) @@ -536,7 +541,6 @@ var FilterGroup = Input.extend(/** @lends instance.web.search.FilterGroup# */{ this.searchview.query.on('add remove change reset', this.proxy('search_change')); }, start: function () { - this.$el.on('click', 'a', this.proxy('toggle_filter')); return $.when(null); }, /** @@ -684,7 +688,7 @@ var FilterGroup = Input.extend(/** @lends instance.web.search.FilterGroup# */{ toggle_filter: function (e) { e.preventDefault(); e.stopPropagation(); - this.toggle(this.filters[Number($(e.target).parent().data('index'))]); + this.toggle(this.filters[Number($(e.currentTarget).data('index'))]); }, toggle: function (filter, options) { this.searchview.query.toggle(this.make_facet([this.make_value(filter)]), options); diff --git a/addons/web/static/src/js/views/search/search_view.js b/addons/web/static/src/js/views/search/search_view.js index 9e1f70fec22..2bac31ed27f 100644 --- a/addons/web/static/src/js/views/search/search_view.js +++ b/addons/web/static/src/js/views/search/search_view.js @@ -535,7 +535,7 @@ var SearchView = Widget.extend({ * puts the focus on the search input */ _focusInput: function () { - if (!config.device.touch && config.device.size_class >= config.device.SIZES.SM) { + if (!config.device.touch && config.device.size_class >= config.device.SIZES.MD) { this.$('input').focus(); } }, diff --git a/addons/web/static/src/js/views/search/search_view_mobile.js b/addons/web/static/src/js/views/search/search_view_mobile.js index 852a65c0a37..ea7fe33ad81 100644 --- a/addons/web/static/src/js/views/search/search_view_mobile.js +++ b/addons/web/static/src/js/views/search/search_view_mobile.js @@ -54,7 +54,7 @@ SearchView.include({ * @private */ _toggleMobileSearchView: function () { - this.$('.o_enable_searchview').toggleClass('btn-default', !!this.query.length); + this.$('.o_enable_searchview').toggleClass('btn-secondary', !!this.query.length); this.$('.o_mobile_search').toggleClass('o_hidden'); }, diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index ed0adf0ff3b..e99f81ccb08 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -109,7 +109,7 @@ var FormViewDialog = ViewDialog.extend({ options = options || {}; options.buttons = [{ text: (readonly ? _t("Close") : _t("Discard")), - classes: "btn-default o_form_button_cancel", + classes: "btn-secondary o_form_button_cancel", close: true, click: function () { if (!readonly) { @@ -360,7 +360,7 @@ var SelectCreateDialog = ViewDialog.extend({ // Set the dialog's buttons self.__buttons = [{ text: _t("Cancel"), - classes: "btn-default o_form_button_cancel", + classes: "btn-secondary o_form_button_cancel", close: true, }]; if (!self.options.no_create) { diff --git a/addons/web/static/src/js/widgets/data_export.js b/addons/web/static/src/js/widgets/data_export.js index 30e17604077..d05bf2d33fc 100644 --- a/addons/web/static/src/js/widgets/data_export.js +++ b/addons/web/static/src/js/widgets/data_export.js @@ -49,7 +49,7 @@ var DataExport = Dialog.extend({ function process_children() { var $this = $(this); - if($this.hasClass('open')) { + if($this.hasClass('show')) { $this.children('.o_export_tree_item') .addClass('o_selected') .each(process_children); @@ -77,19 +77,19 @@ var DataExport = Dialog.extend({ switch(e.keyCode || e.which) { case $.ui.keyCode.LEFT: - if ($elem.hasClass('open')) { + if ($elem.hasClass('show')) { this.on_expand_action(record); } break; case $.ui.keyCode.RIGHT: - if (!$elem.hasClass('open')) { + if (!$elem.hasClass('show')) { this.on_expand_action(record); } break; case $.ui.keyCode.UP: var $prev = $elem.prev('.o_export_tree_item'); if($prev.length === 1) { - while($prev.hasClass('open')) { + while($prev.hasClass('show')) { $prev = $prev.children('.o_export_tree_item').last(); } } else { @@ -104,7 +104,7 @@ var DataExport = Dialog.extend({ break; case $.ui.keyCode.DOWN: var $next; - if($elem.hasClass('open')) { + if($elem.hasClass('show')) { $next = $elem.children('.o_export_tree_item').first(); } else { $next = $elem.next('.o_export_tree_item'); @@ -408,7 +408,7 @@ var DataExport = Dialog.extend({ var self = this; if(expansion) { this.$('.o_export_tree_item[data-id="' + expansion + '"]') - .addClass('open') + .addClass('show') .find('.o_expand_parent') .toggleClass('fa-chevron-right fa-chevron-down') .next() @@ -429,8 +429,8 @@ var DataExport = Dialog.extend({ }, show_content: function(id) { var $this = this.$('.o_export_tree_item[data-id="' + id + '"]'); - $this.toggleClass('open'); - var is_open = $this.hasClass('open'); + $this.toggleClass('show'); + var is_open = $this.hasClass('show'); $this.children('.o_expand_parent').toggleClass('fa-chevron-down', !!is_open).toggleClass('fa-chevron-right', !is_open); @@ -441,8 +441,8 @@ var DataExport = Dialog.extend({ if(!is_open) { $child.hide(); } else if(child_len === $child_field.eq(i).data('id').split("/").length) { - if ($child.hasClass('open')) { - $child.removeClass('open'); + if ($child.hasClass('show')) { + $child.removeClass('show'); $child.children('.o_expand_parent').removeClass('fa-chevron-down').addClass('fa-chevron-right'); } $child.show(); diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js index 8f87b36c874..9d47e79f6a7 100644 --- a/addons/web/static/src/js/widgets/date_picker.js +++ b/addons/web/static/src/js/widgets/date_picker.js @@ -1,69 +1,74 @@ odoo.define('web.datepicker', function (require) { "use strict"; -var core = require('web.core'); var field_utils = require('web.field_utils'); var time = require('web.time'); var Widget = require('web.Widget'); -var _t = core._t; - var DateWidget = Widget.extend({ template: "web.datepicker", type_of_date: "date", events: { - 'dp.change': 'changeDatetime', - 'dp.show': '_onShow', + 'change.datetimepicker': 'changeDatetime', + 'show.datetimepicker': '_onShow', + 'click input.o_datepicker_input': '_onClick', 'change .o_datepicker_input': 'changeDatetime', + 'input input': '_onInput', }, /** * @override */ - init: function(parent, options) { + init: function (parent, options) { this._super.apply(this, arguments); - var l10n = _t.database.parameters; - this.name = parent.name; - this.options = _.defaults(options || {}, { + this.options = _.extend({ format : this.type_of_date === 'datetime' ? time.getLangDatetimeFormat() : time.getLangDateFormat(), minDate: moment({ y: 1900 }), maxDate: moment().add(200, "y"), - calendarWeeks: true, + useCurrent: false, icons: { time: 'fa fa-clock-o', date: 'fa fa-calendar', - next: 'fa fa-chevron-right', - previous: 'fa fa-chevron-left', up: 'fa fa-chevron-up', down: 'fa fa-chevron-down', - close: 'fa fa-times', + previous: 'fa fa-chevron-left', + next: 'fa fa-chevron-right', + today: 'fa fa-calendar-check-o', + clear: 'fa fa-delete', + close: 'fa fa-times' + }, + calendarWeeks: true, + buttons: { + showToday: false, + showClear: false, + showClose: false, }, - locale : moment.locale(), - allowInputToggle: true, - keyBinds: null, widgetParent: 'body', - useCurrent: false, - }); + keyBinds: null, + allowInputToggle: true, + }, options || {}); }, /** * @override */ - start: function() { + start: function () { this.$input = this.$('input.o_datepicker_input'); - this.$input.focus(function(e) { + this.$input.focus(function (e) { e.stopImmediatePropagation(); }); + this.__libInput = true; this.$input.datetimepicker(this.options); - this.picker = this.$input.data('DateTimePicker'); - this.$input.click(this.picker.toggle.bind(this.picker)); + this.__libInput = false; this._setReadonly(false); }, /** * @override */ - destroy: function() { - this.picker.destroy(); + destroy: function () { + this.__libInput = true; + this.$input.datetimepicker('destroy'); + this.__libInput = false; this._super.apply(this, arguments); }, @@ -82,7 +87,7 @@ var DateWidget = Widget.extend({ var hasChanged = !oldValue !== !newValue; if (oldValue && newValue) { var formattedOldValue = oldValue.format(time.getLangDatetimeFormat()); - var formattedNewValue = newValue.format(time.getLangDatetimeFormat()) + var formattedNewValue = newValue.format(time.getLangDatetimeFormat()); if (formattedNewValue !== formattedOldValue) { hasChanged = true; } @@ -106,13 +111,13 @@ var DateWidget = Widget.extend({ */ isValid: function () { var value = this.$input.val(); - if(value === "") { + if (value === "") { return true; } else { try { this._parseClient(value); return true; - } catch(e) { + } catch (e) { return false; } } @@ -124,9 +129,9 @@ var DateWidget = Widget.extend({ this.set({'value': value}); var formatted_value = value ? this._formatClient(value) : null; this.$input.val(formatted_value); - if (this.picker) { - this.picker.date(value || null); - } + this.__libInput = true; + this.$input.datetimepicker('date', value || null); + this.__libInput = false; }, //-------------------------------------------------------------------------- @@ -162,7 +167,7 @@ var DateWidget = Widget.extend({ * * @private */ - _setValueFromUi: function() { + _setValueFromUi: function () { var value = this.$input.val() || false; this.setValue(this._parseClient(value)); }, @@ -172,6 +177,23 @@ var DateWidget = Widget.extend({ //-------------------------------------------------------------------------- /** + * @private + */ + _onClick: function () { + this.__libInput = true; + this.$input.datetimepicker('toggle'); + this.__libInput = false; + }, + /** + * @private + * @param {Event} ev + */ + _onInput: function (ev) { + if (this.__libInput) { + ev.stopImmediatePropagation(); + } + }, + /** * set the date of the picker by the current date or the today date * * @private @@ -179,9 +201,11 @@ var DateWidget = Widget.extend({ _onShow: function () { //when opening datetimepicker the date and time by default should be the one from //the input field if any or the current day otherwise - if(this.$input.val().length !== 0 && this.isValid()) { + if (this.$input.val().length !== 0 && this.isValid()) { var value = this._parseClient(this.$input.val()); - this.picker.date(value); + this.__libInput = true; + this.$input.datetimepicker('date', value); + this.__libInput = false; this.$input.select(); } }, @@ -189,11 +213,14 @@ var DateWidget = Widget.extend({ var DateTimeWidget = DateWidget.extend({ type_of_date: "datetime", - init: function() { - this._super.apply(this, arguments); - this.options = _.defaults(this.options, { - showClose: true, - }); + init: function (parent, options) { + this._super(parent, _.extend({ + buttons: { + showToday: false, + showClear: false, + showClose: true, + }, + }, options || {})); }, }); diff --git a/addons/web/static/src/js/widgets/debug_manager.js b/addons/web/static/src/js/widgets/debug_manager.js index 4eee9266594..d553387f153 100644 --- a/addons/web/static/src/js/widgets/debug_manager.js +++ b/addons/web/static/src/js/widgets/debug_manager.js @@ -23,12 +23,12 @@ var DebugManager = Widget.extend({ template: "WebClient.DebugManager", events: { "click a[data-action]": "perform_callback", - "mouseover .o_debug_dropdowns > li:not(.open)": function(e) { + "mouseover .o_debug_dropdowns > li:not(.show)": function(e) { // Open other dropdowns on mouseover - var $opened = this.$('.o_debug_dropdowns > li.open'); + var $opened = this.$('.o_debug_dropdowns > li.show'); if($opened.length) { - $opened.removeClass('open'); - $(e.currentTarget).addClass('open').find('> a').focus(); + $opened.removeClass('show'); + $(e.currentTarget).addClass('show').find('> a').focus(); } }, }, diff --git a/addons/web/static/src/js/widgets/domain_selector.js b/addons/web/static/src/js/widgets/domain_selector.js index c5a561b1b77..dab0c97108b 100644 --- a/addons/web/static/src/js/widgets/domain_selector.js +++ b/addons/web/static/src/js/widgets/domain_selector.js @@ -161,7 +161,7 @@ var DomainNode = Widget.extend({ var DomainTree = DomainNode.extend({ template: "DomainTree", events: _.extend({}, DomainNode.prototype.events, { - "click .o_domain_tree_operator_selector > ul > li > a": "_onOperatorChange", + "click .o_domain_tree_operator_selector .dropdown-item": "_onOperatorChange", }), custom_events: { // If a domain child sends a request to add a child or remove one, call diff --git a/addons/web/static/src/js/widgets/dropdown_menu.js b/addons/web/static/src/js/widgets/dropdown_menu.js index a3a83519678..6f8f78a13d5 100644 --- a/addons/web/static/src/js/widgets/dropdown_menu.js +++ b/addons/web/static/src/js/widgets/dropdown_menu.js @@ -11,8 +11,8 @@ var QWeb = core.qweb; var DropdownMenu = Widget.extend({ template: 'web.DropdownMenu', events: { - 'click li.o_menu_item': '_onItemClick', - 'click li.o_item_option': '_onOptionClick', + 'click .o_menu_item': '_onItemClick', + 'click .o_item_option': '_onOptionClick', 'click span.o_trash_button': '_onTrashClick', 'hidden.bs.dropdown': '_onBootstrapClose', }, @@ -44,12 +44,12 @@ var DropdownMenu = Widget.extend({ this.dropdownCategory = dropdownHeader.category; this.dropdownTitle = dropdownHeader.title; this.dropdownIcon = dropdownHeader.icon; - this.dropdownSymbol = dropdownHeader.symbol || 'caret'; + this.dropdownSymbol = dropdownHeader.symbol || false; // this parameter fixes the menu style. By default, // the style used is the one used in the search view this.dropdownStyle = dropdownHeader.style || { el: {class: 'btn-group o_dropdown', attrs: {}}, - mainButton: {class: 'o_dropdown_toggler_btn btn btn-sm btn-default dropdown-toggle'}, + mainButton: {class: 'o_dropdown_toggler_btn btn btn-secondary dropdown-toggle' + (this.dropdownSymbol ? ' o-no-caret' : '')}, }; this.items = items; _.each(this.items, this._prepareItem.bind(this)); @@ -105,8 +105,8 @@ var DropdownMenu = Widget.extend({ */ _renderMenuItems: function () { var newMenuItems = QWeb.render('DropdownMenu.MenuItems', {widget: this}); - this.$el.find('.o_menu_item,.divider[data-removable="1"]').remove(); - this.$('ul.o_dropdown_menu').prepend($(newMenuItems)); + this.$el.find('.o_menu_item, .dropdown-divider[data-removable="1"]').remove(); + this.$('.o_dropdown_menu').prepend($(newMenuItems)); }, /** * this function computes some default values for some of the item keys @@ -189,7 +189,7 @@ var DropdownMenu = Widget.extend({ * @param {jQueryEvent} event */ _onBootstrapClose: function () { - _.each(this.items, function(item) { + _.each(this.items, function (item) { item.isOpen = false; }); this._renderMenuItems(); diff --git a/addons/web/static/src/js/widgets/model_field_selector.js b/addons/web/static/src/js/widgets/model_field_selector.js index 81abfbe0d6c..e9990ac5105 100644 --- a/addons/web/static/src/js/widgets/model_field_selector.js +++ b/addons/web/static/src/js/widgets/model_field_selector.js @@ -253,7 +253,7 @@ var ModelFieldSelector = Widget.extend({ if (!this._isOpen) return; this._isOpen = false; - this.$popover.addClass("hidden"); + this.$popover.addClass('d-none'); if (this.dirty) { this.dirty = false; @@ -332,7 +332,7 @@ var ModelFieldSelector = Widget.extend({ })); // Toggle the warning message - this.$valid.toggleClass("hidden", !!this.isValid()); + this.$valid.toggleClass('d-none', !!this.isValid()); // Adapt the popover content var page = _.last(this.pages); @@ -376,7 +376,7 @@ var ModelFieldSelector = Widget.extend({ if (this._isOpen) return; this._isOpen = true; - this.$popover.removeClass("hidden"); + this.$popover.removeClass('d-none'); }, /** * Toggles the valid status of the widget and display the error message if diff --git a/addons/web/static/src/js/widgets/switch_company_menu.js b/addons/web/static/src/js/widgets/switch_company_menu.js index 01c224370d2..c9498950a04 100644 --- a/addons/web/static/src/js/widgets/switch_company_menu.js +++ b/addons/web/static/src/js/widgets/switch_company_menu.js @@ -18,7 +18,7 @@ var _t = core._t; var SwitchCompanyMenu = Widget.extend({ template: 'SwitchCompanyMenu', events: { - 'click .dropdown-menu li a[data-menu]': '_onClick', + 'click .dropdown-item[data-menu]': '_onClick', }, /** * @override @@ -53,8 +53,8 @@ var SwitchCompanyMenu = Widget.extend({ } else { a = '<span style="margin-right: 24px;"/>'; } - companiesList += '<li><a href="#" data-menu="company" data-company-id="' + - company[0] + '">' + a + company[1] + '</a></li>'; + companiesList += '<a href="#" class="dropdown-item" data-menu="company" data-company-id="' + + company[0] + '">' + a + company[1] + '</a>'; }); this.$('.dropdown-menu').html(companiesList); return this._super(); |
