Skip to content

Commit d72943e

Browse files
authored
fix(dropdown): rework touch input handling
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)
1 parent b3c0489 commit d72943e

1 file changed

Lines changed: 36 additions & 58 deletions

File tree

src/definitions/modules/dropdown.js

Lines changed: 36 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,6 @@ $.fn.dropdown = function(parameters) {
3030

3131
moduleSelector = $allModules.selector || '',
3232

33-
hasTouch = ('ontouchstart' in document.documentElement),
34-
clickEvent = hasTouch
35-
? 'touchstart'
36-
: 'click',
37-
3833
time = new Date().getTime(),
3934
performance = [],
4035

@@ -541,9 +536,7 @@ $.fn.dropdown = function(parameters) {
541536
}
542537
if(settings.onShow.call(element) !== false) {
543538
module.animate.show(function() {
544-
if( module.can.click() ) {
545-
module.bind.intent();
546-
}
539+
module.bind.intent();
547540
if(module.has.search() && !preventFocus) {
548541
module.focusSearch();
549542
}
@@ -570,8 +563,17 @@ $.fn.dropdown = function(parameters) {
570563
}
571564
callback.call(element);
572565
});
566+
// Hide submenus explicitly. On some browsers (esp. mobile), they will not automatically receive a
567+
// mouseleave event
568+
var $subMenu = $module.find(selector.menu);
569+
if($subMenu.length > 0) {
570+
module.verbose('Hiding sub-menu', $subMenu);
571+
$subMenu.each(function() {
572+
module.animate.hide(false, $(this));
573+
});
574+
}
573575
}
574-
} else if( module.can.click() ) {
576+
} else {
575577
module.unbind.intent();
576578
}
577579
iconClicked = false;
@@ -639,8 +641,8 @@ $.fn.dropdown = function(parameters) {
639641
module.verbose('Binding mouse events');
640642
if(module.is.multiple()) {
641643
$module
642-
.on(clickEvent + eventNamespace, selector.label, module.event.label.click)
643-
.on(clickEvent + eventNamespace, selector.remove, module.event.remove.click)
644+
.on('click' + eventNamespace, selector.label, module.event.label.click)
645+
.on('click' + eventNamespace, selector.remove, module.event.remove.click)
644646
;
645647
}
646648
if( module.is.searchSelection() ) {
@@ -649,31 +651,33 @@ $.fn.dropdown = function(parameters) {
649651
.on('mouseup' + eventNamespace, module.event.mouseup)
650652
.on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown)
651653
.on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup)
652-
.on(clickEvent + eventNamespace, selector.icon, module.event.icon.click)
653-
.on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click)
654+
.on('click' + eventNamespace, selector.icon, module.event.icon.click)
655+
.on('click' + eventNamespace, selector.clearIcon, module.event.clearIcon.click)
654656
.on('focus' + eventNamespace, selector.search, module.event.search.focus)
655-
.on(clickEvent + eventNamespace, selector.search, module.event.search.focus)
657+
.on('click' + eventNamespace, selector.search, module.event.search.focus)
656658
.on('blur' + eventNamespace, selector.search, module.event.search.blur)
657-
.on(clickEvent + eventNamespace, selector.text, module.event.text.focus)
659+
.on('click' + eventNamespace, selector.text, module.event.text.focus)
658660
;
659661
if(module.is.multiple()) {
660662
$module
661-
.on(clickEvent + eventNamespace, module.event.click)
662-
.on(clickEvent + eventNamespace, module.event.search.focus)
663+
.on('click' + eventNamespace, module.event.click)
664+
.on('click' + eventNamespace, module.event.search.focus)
663665
;
664666
}
665667
}
666668
else {
667669
if(settings.on == 'click') {
668670
$module
669-
.on(clickEvent + eventNamespace, selector.icon, module.event.icon.click)
670-
.on(clickEvent + eventNamespace, module.event.test.toggle)
671+
.on('click' + eventNamespace, selector.icon, module.event.icon.click)
672+
.on('click' + eventNamespace, module.event.test.toggle)
671673
;
672674
}
673675
else if(settings.on == 'hover') {
674676
$module
675677
.on('mouseenter' + eventNamespace, module.delay.show)
676678
.on('mouseleave' + eventNamespace, module.delay.hide)
679+
.on('touchstart' + eventNamespace, module.event.test.toggle)
680+
.on('touchstart' + eventNamespace, selector.icon, module.event.icon.click)
677681
;
678682
}
679683
else {
@@ -685,7 +689,7 @@ $.fn.dropdown = function(parameters) {
685689
.on('mousedown' + eventNamespace, module.event.mousedown)
686690
.on('mouseup' + eventNamespace, module.event.mouseup)
687691
.on('focus' + eventNamespace, module.event.focus)
688-
.on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click)
692+
.on('click' + eventNamespace, selector.clearIcon, module.event.clearIcon.click)
689693
;
690694
if(module.has.menuSearch() ) {
691695
$module
@@ -699,36 +703,25 @@ $.fn.dropdown = function(parameters) {
699703
}
700704
}
701705
$menu
702-
.on((hasTouch ? 'touchstart' : 'mouseenter') + eventNamespace, selector.item, module.event.item.mouseenter)
706+
.on('mouseenter' + eventNamespace, selector.item, module.event.item.mouseenter)
707+
.on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter)
703708
.on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave)
704709
.on('click' + eventNamespace, selector.item, module.event.item.click)
705710
;
706711
},
707712
intent: function() {
708713
module.verbose('Binding hide intent event to document');
709-
if(hasTouch) {
710-
$document
711-
.on('touchstart' + elementNamespace, module.event.test.touch)
712-
.on('touchmove' + elementNamespace, module.event.test.touch)
713-
;
714-
}
715714
$document
716-
.on(clickEvent + elementNamespace, module.event.test.hide)
715+
.on('click' + elementNamespace, module.event.test.hide)
717716
;
718717
}
719718
},
720719

721720
unbind: {
722721
intent: function() {
723722
module.verbose('Removing hide intent event from document');
724-
if(hasTouch) {
725-
$document
726-
.off('touchstart' + elementNamespace)
727-
.off('touchmove' + elementNamespace)
728-
;
729-
}
730723
$document
731-
.off(clickEvent + elementNamespace)
724+
.off('click' + elementNamespace)
732725
;
733726
}
734727
},
@@ -1267,23 +1260,12 @@ $.fn.dropdown = function(parameters) {
12671260
if (!module.is.multiple() || (module.is.multiple() && !module.is.active())) {
12681261
focused = true;
12691262
}
1270-
if( module.determine.eventOnElement(event, toggleBehavior) ) {
1263+
if( module.determine.eventOnElement(event, toggleBehavior) && event.type !== 'touchstart') {
1264+
// do not preventDefault of touchstart; so emulated mouseenter is triggered on first touch and not later
1265+
// (when selecting an item). The double-showing of the dropdown through both events does not hurt.
12711266
event.preventDefault();
12721267
}
12731268
},
1274-
touch: function(event) {
1275-
module.determine.eventOnElement(event, function() {
1276-
if(event.type == 'touchstart') {
1277-
module.timer = setTimeout(function() {
1278-
module.hide();
1279-
}, settings.delay.touch);
1280-
}
1281-
else if(event.type == 'touchmove') {
1282-
clearTimeout(module.timer);
1283-
}
1284-
});
1285-
event.stopPropagation();
1286-
},
12871269
hide: function(event) {
12881270
if(module.determine.eventInModule(event, module.hide)){
12891271
if(element.id && $(event.target).attr('for') === element.id){
@@ -1367,13 +1349,15 @@ $.fn.dropdown = function(parameters) {
13671349
},
13681350
mouseleave: function(event) {
13691351
var
1370-
$subMenu = $(this).children(selector.menu)
1352+
$subMenu = $(this).find(selector.menu)
13711353
;
13721354
if($subMenu.length > 0) {
13731355
clearTimeout(module.itemTimer);
13741356
module.itemTimer = setTimeout(function() {
13751357
module.verbose('Hiding sub-menu', $subMenu);
1376-
module.animate.hide(false, $subMenu);
1358+
$subMenu.each(function() {
1359+
module.animate.hide(false, $(this));
1360+
});
13771361
}, settings.delay.hide);
13781362
}
13791363
},
@@ -3626,9 +3610,6 @@ $.fn.dropdown = function(parameters) {
36263610
$currentMenu.removeClass(className.loading);
36273611
return canOpenRightward;
36283612
},
3629-
click: function() {
3630-
return (hasTouch || settings.on == 'click');
3631-
},
36323613
extendSelect: function() {
36333614
return settings.allowAdditions || settings.apiSettings;
36343615
},
@@ -3698,9 +3679,7 @@ $.fn.dropdown = function(parameters) {
36983679
start = ($subMenu)
36993680
? function() {}
37003681
: function() {
3701-
if( module.can.click() ) {
3702-
module.unbind.intent();
3703-
}
3682+
module.unbind.intent();
37043683
module.remove.active();
37053684
},
37063685
transition = settings.transition.hideMethod || module.get.transition($subMenu)
@@ -4077,7 +4056,6 @@ $.fn.dropdown.settings = {
40774056
hide : 300,
40784057
show : 200,
40794058
search : 20,
4080-
touch : 50
40814059
},
40824060

40834061
/* Callbacks */

0 commit comments

Comments
 (0)