Skip to content

Commit

Permalink
fix(dropdown): rework touch input handling
Browse files Browse the repository at this point in the history
This PR fixes #1989 in a similar way as my previous PR for the slider (#2327): First, I removed all special event handling for touch events and the whole distiction between touch- and non-touch devices, so mouse events will be handled on touch devices as well. Then, I added only a few touch event bindings for the things that don't work via mouse event emulation of the mobile web browsers.

In addition, I fixed two minor bugs with nested submenus being not correctly hidden in some cases (see commit messages for more details).

I tested everything on

Chrome mobile on Android (with touch and Bluetooth mouse)
Firefox mobile on Android (with touch and Bluetooth mouse)
Chromium on Linux/X11 (with multitouch screen and mouse)
Firefox on Linux/X11 (with multitouch screen and mouse)
  • Loading branch information
mhthies authored May 1, 2022
1 parent b3c0489 commit d72943e
Showing 1 changed file with 36 additions and 58 deletions.
94 changes: 36 additions & 58 deletions src/definitions/modules/dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ $.fn.dropdown = function(parameters) {

moduleSelector = $allModules.selector || '',

hasTouch = ('ontouchstart' in document.documentElement),
clickEvent = hasTouch
? 'touchstart'
: 'click',

time = new Date().getTime(),
performance = [],

Expand Down Expand Up @@ -541,9 +536,7 @@ $.fn.dropdown = function(parameters) {
}
if(settings.onShow.call(element) !== false) {
module.animate.show(function() {
if( module.can.click() ) {
module.bind.intent();
}
module.bind.intent();
if(module.has.search() && !preventFocus) {
module.focusSearch();
}
Expand All @@ -570,8 +563,17 @@ $.fn.dropdown = function(parameters) {
}
callback.call(element);
});
// Hide submenus explicitly. On some browsers (esp. mobile), they will not automatically receive a
// mouseleave event
var $subMenu = $module.find(selector.menu);
if($subMenu.length > 0) {
module.verbose('Hiding sub-menu', $subMenu);
$subMenu.each(function() {
module.animate.hide(false, $(this));
});
}
}
} else if( module.can.click() ) {
} else {
module.unbind.intent();
}
iconClicked = false;
Expand Down Expand Up @@ -639,8 +641,8 @@ $.fn.dropdown = function(parameters) {
module.verbose('Binding mouse events');
if(module.is.multiple()) {
$module
.on(clickEvent + eventNamespace, selector.label, module.event.label.click)
.on(clickEvent + eventNamespace, selector.remove, module.event.remove.click)
.on('click' + eventNamespace, selector.label, module.event.label.click)
.on('click' + eventNamespace, selector.remove, module.event.remove.click)
;
}
if( module.is.searchSelection() ) {
Expand All @@ -649,31 +651,33 @@ $.fn.dropdown = function(parameters) {
.on('mouseup' + eventNamespace, module.event.mouseup)
.on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown)
.on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup)
.on(clickEvent + eventNamespace, selector.icon, module.event.icon.click)
.on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click)
.on('click' + eventNamespace, selector.icon, module.event.icon.click)
.on('click' + eventNamespace, selector.clearIcon, module.event.clearIcon.click)
.on('focus' + eventNamespace, selector.search, module.event.search.focus)
.on(clickEvent + eventNamespace, selector.search, module.event.search.focus)
.on('click' + eventNamespace, selector.search, module.event.search.focus)
.on('blur' + eventNamespace, selector.search, module.event.search.blur)
.on(clickEvent + eventNamespace, selector.text, module.event.text.focus)
.on('click' + eventNamespace, selector.text, module.event.text.focus)
;
if(module.is.multiple()) {
$module
.on(clickEvent + eventNamespace, module.event.click)
.on(clickEvent + eventNamespace, module.event.search.focus)
.on('click' + eventNamespace, module.event.click)
.on('click' + eventNamespace, module.event.search.focus)
;
}
}
else {
if(settings.on == 'click') {
$module
.on(clickEvent + eventNamespace, selector.icon, module.event.icon.click)
.on(clickEvent + eventNamespace, module.event.test.toggle)
.on('click' + eventNamespace, selector.icon, module.event.icon.click)
.on('click' + eventNamespace, module.event.test.toggle)
;
}
else if(settings.on == 'hover') {
$module
.on('mouseenter' + eventNamespace, module.delay.show)
.on('mouseleave' + eventNamespace, module.delay.hide)
.on('touchstart' + eventNamespace, module.event.test.toggle)
.on('touchstart' + eventNamespace, selector.icon, module.event.icon.click)
;
}
else {
Expand All @@ -685,7 +689,7 @@ $.fn.dropdown = function(parameters) {
.on('mousedown' + eventNamespace, module.event.mousedown)
.on('mouseup' + eventNamespace, module.event.mouseup)
.on('focus' + eventNamespace, module.event.focus)
.on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click)
.on('click' + eventNamespace, selector.clearIcon, module.event.clearIcon.click)
;
if(module.has.menuSearch() ) {
$module
Expand All @@ -699,36 +703,25 @@ $.fn.dropdown = function(parameters) {
}
}
$menu
.on((hasTouch ? 'touchstart' : 'mouseenter') + eventNamespace, selector.item, module.event.item.mouseenter)
.on('mouseenter' + eventNamespace, selector.item, module.event.item.mouseenter)
.on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter)
.on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave)
.on('click' + eventNamespace, selector.item, module.event.item.click)
;
},
intent: function() {
module.verbose('Binding hide intent event to document');
if(hasTouch) {
$document
.on('touchstart' + elementNamespace, module.event.test.touch)
.on('touchmove' + elementNamespace, module.event.test.touch)
;
}
$document
.on(clickEvent + elementNamespace, module.event.test.hide)
.on('click' + elementNamespace, module.event.test.hide)
;
}
},

unbind: {
intent: function() {
module.verbose('Removing hide intent event from document');
if(hasTouch) {
$document
.off('touchstart' + elementNamespace)
.off('touchmove' + elementNamespace)
;
}
$document
.off(clickEvent + elementNamespace)
.off('click' + elementNamespace)
;
}
},
Expand Down Expand Up @@ -1267,23 +1260,12 @@ $.fn.dropdown = function(parameters) {
if (!module.is.multiple() || (module.is.multiple() && !module.is.active())) {
focused = true;
}
if( module.determine.eventOnElement(event, toggleBehavior) ) {
if( module.determine.eventOnElement(event, toggleBehavior) && event.type !== 'touchstart') {
// do not preventDefault of touchstart; so emulated mouseenter is triggered on first touch and not later
// (when selecting an item). The double-showing of the dropdown through both events does not hurt.
event.preventDefault();
}
},
touch: function(event) {
module.determine.eventOnElement(event, function() {
if(event.type == 'touchstart') {
module.timer = setTimeout(function() {
module.hide();
}, settings.delay.touch);
}
else if(event.type == 'touchmove') {
clearTimeout(module.timer);
}
});
event.stopPropagation();
},
hide: function(event) {
if(module.determine.eventInModule(event, module.hide)){
if(element.id && $(event.target).attr('for') === element.id){
Expand Down Expand Up @@ -1367,13 +1349,15 @@ $.fn.dropdown = function(parameters) {
},
mouseleave: function(event) {
var
$subMenu = $(this).children(selector.menu)
$subMenu = $(this).find(selector.menu)
;
if($subMenu.length > 0) {
clearTimeout(module.itemTimer);
module.itemTimer = setTimeout(function() {
module.verbose('Hiding sub-menu', $subMenu);
module.animate.hide(false, $subMenu);
$subMenu.each(function() {
module.animate.hide(false, $(this));
});
}, settings.delay.hide);
}
},
Expand Down Expand Up @@ -3626,9 +3610,6 @@ $.fn.dropdown = function(parameters) {
$currentMenu.removeClass(className.loading);
return canOpenRightward;
},
click: function() {
return (hasTouch || settings.on == 'click');
},
extendSelect: function() {
return settings.allowAdditions || settings.apiSettings;
},
Expand Down Expand Up @@ -3698,9 +3679,7 @@ $.fn.dropdown = function(parameters) {
start = ($subMenu)
? function() {}
: function() {
if( module.can.click() ) {
module.unbind.intent();
}
module.unbind.intent();
module.remove.active();
},
transition = settings.transition.hideMethod || module.get.transition($subMenu)
Expand Down Expand Up @@ -4077,7 +4056,6 @@ $.fn.dropdown.settings = {
hide : 300,
show : 200,
search : 20,
touch : 50
},

/* Callbacks */
Expand Down

0 comments on commit d72943e

Please sign in to comment.