From 9348cca0f205c4cdcf88cceec47bf47b83c860ac Mon Sep 17 00:00:00 2001 From: Romain Estievenart Date: Mon, 12 Oct 2020 09:04:34 +0000 Subject: [PATCH] [FIX] web: DropdownMenu autoclose behavior should match Bootstrap Before this commit, with an opened DropdownMenu component, when the user clicks on a Bootstrap's dropdown the DropdownMenu doesn't close. This issue is due to a conflict between the global click listener registered to close the DropdownMenu and the listener registered by the Bootstrap dropdown: the last one prevents the event from bubbling up to the `window` element and be handled by the first one. This commit fixes it by enabling the `capture` flag on the DropdownMenu global click listener, giving it the opportunity to handling it properly and close the DropdownMenu. Note: this commit also fixes an issue when a dialog is opened from a DropdownMenu (like the favorite's delete action) and prevents it from closing in the background (e.g. when the dialog is still opened). opw-1879566 closes odoo/odoo#60928 X-original-commit: 7f1bcc8ceb78729725f65585e923b08f94bf75b5 Signed-off-by: Pierre Paridans --- .../static/src/js/components/dropdown_menu.js | 9 ++- .../tests/components/dropdown_menu_tests.js | 56 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/components/dropdown_menu.js b/addons/web/static/src/js/components/dropdown_menu.js index 4b77bdc4527..ca514e4a5bf 100644 --- a/addons/web/static/src/js/components/dropdown_menu.js +++ b/addons/web/static/src/js/components/dropdown_menu.js @@ -35,7 +35,7 @@ odoo.define('web.DropdownMenu', function (require) { this.dropdownMenu = useRef('dropdown'); this.state = useState({ open: false }); - useExternalListener(window, 'click', this._onWindowClick); + useExternalListener(window, 'click', this._onWindowClick, true); useExternalListener(window, 'keydown', this._onWindowKeydown); } @@ -147,6 +147,13 @@ odoo.define('web.DropdownMenu', function (require) { !this.el.contains(ev.target) && !this.el.contains(document.activeElement) ) { + if (document.body.classList.contains("modal-open")) { + // retrieve the active modal and check if the dropdown is a child of this modal + const modal = document.querySelector('.modal:not(.o_inactive_modal)'); + if (!modal.contains(this.el)) { + return; + } + } this.state.open = false; } } diff --git a/addons/web/static/tests/components/dropdown_menu_tests.js b/addons/web/static/tests/components/dropdown_menu_tests.js index da0d458d680..3aff0ae14ea 100644 --- a/addons/web/static/tests/components/dropdown_menu_tests.js +++ b/addons/web/static/tests/components/dropdown_menu_tests.js @@ -68,6 +68,62 @@ odoo.define('web.dropdown_menu_tests', function (require) { dropdown.destroy(); }); + QUnit.test('only one dropdown rendering at same time (owl vs bootstrap dropdown)', async function (assert) { + assert.expect(12); + + const bsDropdown = document.createElement('div'); + bsDropdown.innerHTML = ``; + document.body.append(bsDropdown); + + const dropdown = await createComponent(DropdownMenu, { + props: { + items: this.items, + title: "Dropdown", + }, + }); + + await testUtils.dom.click(dropdown.el.querySelector('button')); + + assert.hasClass(dropdown.el.querySelector('.dropdown-menu'), 'show'); + assert.doesNotHaveClass(bsDropdown.querySelector('.dropdown-menu'), 'show'); + + assert.isVisible(dropdown.el.querySelector('.dropdown-menu'), + "owl dropdown menu should be visible"); + assert.isNotVisible(bsDropdown.querySelector('.dropdown-menu'), + "bs dropdown menu should not be visible"); + + await testUtils.dom.click(bsDropdown.querySelector('.btn.dropdown-toggle')); + + assert.doesNotHaveClass(dropdown.el, 'show'); + assert.containsNone(dropdown.el, '.dropdown-menu', + "owl dropdown menu should not be set inside the dom"); + + assert.hasClass(bsDropdown.querySelector('.dropdown-menu'), 'show'); + assert.isVisible(bsDropdown.querySelector('.dropdown-menu'), + "bs dropdown menu should be visible"); + + await testUtils.dom.click(document.body); + + assert.doesNotHaveClass(dropdown.el, 'show'); + assert.containsNone(dropdown.el, '.dropdown-menu', + "owl dropdown menu should not be set inside the dom"); + + assert.doesNotHaveClass(bsDropdown.querySelector('.dropdown-menu'), 'show'); + assert.isNotVisible(bsDropdown.querySelector('.dropdown-menu'), + "bs dropdown menu should not be visible"); + + bsDropdown.remove(); + dropdown.destroy(); + }); + QUnit.test('click on an item without options should toggle it', async function (assert) { assert.expect(7);