From 6d64afe508bfd0bcfb5831a9a4708cef4ad88334 Mon Sep 17 00:00:00 2001 From: "Patrick H. Lauke" Date: Fri, 14 Apr 2017 09:19:00 +0100 Subject: Replace dropdown backdrop hack with cleaner JS-only hack * Replace backdrop with simple noop mouse listener As discussed in https://github.com/twbs/bootstrap/pull/22422 the current approach of injecting a backdrop (to work around iOS' broken event delegation for the `click` event) has annoying consequences on touch-enabled laptop/desktop devices. Instead of a backdrop `
`, here we simply add extra empty/noop mouse listeners to the immediate children of `` (and remove them when the dropdown is closed) in order to force iOS to properly bubble a `click` resulting from a tap (essentially, method 2 from https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html) This is sufficient (except in rare cases where the user does manage to tap on the body itself, rather than any child elements of body - which is not very likely in an iOS phone/tablet scenario for most layouts) to get iOS to get a grip and do the correct event bubbling/delegation, meaning the regular "click" event will bubble back to the `` when tapping outside of the dropdown, and the dropdown will close properly (just like it already does, even without this fix, in non-iOS touchscreen devices/browsers, like Chrome/Android and Windows on a touch laptop). This approach, though a bit hacky, has no impact on the DOM structure, and has no unforeseen side effects on touch-enabled laptops/desktops. And crucially, it works just fine in iOS. * Remove dropdown backdrop styles * Update doc for dropdowns and touch-enabled devices --- js/src/dropdown.js | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index b616186f3..812e718a8 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -43,13 +43,11 @@ const Dropdown = (($) => { } const ClassName = { - BACKDROP : 'dropdown-backdrop', DISABLED : 'disabled', SHOW : 'show' } const Selector = { - BACKDROP : '.dropdown-backdrop', DATA_TOGGLE : '[data-toggle="dropdown"]', FORM_CHILD : '.dropdown form', MENU : '.dropdown-menu', @@ -107,16 +105,13 @@ const Dropdown = (($) => { return false } - // set the backdrop only if the dropdown menu will be opened + // if this is a touch-enabled device we add extra + // empty mouseover listeners to the body's immediate children; + // only needed because of broken event delegation on iOS + // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html if ('ontouchstart' in document.documentElement && !$(parent).closest(Selector.NAVBAR_NAV).length) { - - // if touch-enabled device we use a backdrop because click events - // don't delegate on iOS - see https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html - const backdrop = document.createElement('div') - backdrop.className = ClassName.BACKDROP - $(backdrop).insertBefore(this) - $(backdrop).on('click', Dropdown._clearMenus) + $('body').children().on('mouseover', '*', $.noop) } this.focus() @@ -192,10 +187,10 @@ const Dropdown = (($) => { continue } - // remove backdrop only if the dropdown menu will be hidden - const backdrop = $(parent).find(Selector.BACKDROP)[0] - if (backdrop) { - backdrop.parentNode.removeChild(backdrop) + // if this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support + if ('ontouchstart' in document.documentElement) { + $('body').children().off('mouseover', '*', $.noop) } toggles[i].setAttribute('aria-expanded', 'false') -- cgit v1.2.3