preventDefault() on a multiselect, different behavior on FF - IE - Chrome - javascript

I'm struggling with the mouse events on a select multiple. I didn't expect to get this difference in behavior across browsers in 2015...
I'm basically trying to simulate the Ctrl+Click only with a click, for easy use. It was working on Chrome, using preventDefault(), by cancelling the default behaviour (select current option and deselect others)
select.addEventListener('mousedown', function(evt) {
evt.preventDefault();
evt.target.selected = !evt.target.selected;
return false;
}, true);
Here is the fiddle, you can check with different browsers:
https://jsfiddle.net/fzvkw1xv/
Chrome -> works as expected
FF -> It's like preventDefault() doesn't
do anything. Other options get unchecked.
IE 11 -> No option get
selected at all
What I want to achieve is full control over the multiselect to make a better user experience.
I couldn't find any documentation related to this, I don't know which browser is the buggy one, or what the standard expected behavior is. Any info on this would be much appreciated. I'm starting to think about making checkboxes look like a multiselect box.
Thanks

Different browsers has different ways to handle events.
There is event bubbling besides event capturing. You need to stop it too.
Discover event propagation model: http://javascript.info/tutorial/bubbling-and-capturing
Try more wide solution:
function (event) {
event = event || window.event // Cross-browser event
event.preventDefault(); // Stop event capturing
// Stop event bubbling
if (event.stopPropagation) { // W3C standard variant
event.stopPropagation();
} else { // IE variant
event.cancelBubble = true
}
return false;
}
http://javascript.info/tutorial/default-browser-action

Related

event.preventDefault is not working for "document" / "document.body" elements in Chrome

I have some functionality that preventing touch events on devices (in some specific cases). It worked well till the last update of the Chrome.
After the update, trying to prevent the events that I get from document or document.body is not working anymore, but it works if I listen to events from a some specific element.
For example:
//this not works
document.addEventListener("touchmove", function(event) {
event.preventDefault();
});
//this one works
document.querySelector(".container").addEventListener("touchmove", function(event) {
event.preventDefault();
});
This is not very convenient behavior, since I have a lot of children inside the body element and can't change this structure.
Does anybody knows how to get it works again, or is it a correct behavior for the latest Chrome ? Would appreciate any help, thanks.
There was a change in Chrome 56:
With this change touchstart and touchmove listeners added to the document will default to passive:true (so that calls to preventDefault will be ignored).
To get it work again, passive property can be set to false.
document.addEventListener("touchmove", function(event) {
event.preventDefault();
}, {passive: false});
Not sure if it reasonable to force event handler to be not a passive, but anyway this can be taken into account.

onkeyup on firing for modifier keys

I don't appear to be able to use the onkeyup event to detect when modifier keys, specifically the Alt key, is being released, reliably. Sometimes it works, sometimes it doesn't. Most of the time it doesn't, though.
My current code is:
document.documentElement.onkeyup = function(e) {
e = e || window.event;
if( !e.altKey) {
// do stuff here
document.documentElement.onkeyup = null;
}
}
Possibly related to Prevent default event action not working...? as I'm working in IE9 and the File menu pops up. I do dismiss the menu before attempting to trigger the event, though.
Not directly an answer to your question, but this might help you. It is a very detailed description on how browsers manage keydown/press/up.
I believe that typically a browsers key events take precedence over page defined ones. However, I would suggest using jQuery because I was just testing in IE9 and they seem to have overcome that problem.
Edit: While this seems to capture the event, I don't think it's possible to prevent IE from performing it's own events.

Suppress native keyboard events in Firefox (select tag)?

I'm trying to suppress the normal keyboard events on Firefox's interpretation of the <select multiple="multiple"> box, but the methods I'm used to aren't working. YUI's stopEvent doesn't seem to actually suppress the effects of a keypress or keydown (or both).
keyPressHandler = function (e) {
YUE.stopEvent(e);
};
YAHOO.util.Event.addListener(element, 'keypress', keyPressHandler)
YAHOO.util.Event.addListener(element, 'keydown', keyPressHandler)
Assuming element is a selectbox, the keyboard it still usable to navigate the selectbox in Firefox, however it is not in Chrome. Any ideas on how to properly suppress keyboard events in Firefox?
I generally don't step outside the bounds of YUI but if there is another solution I'm open to it.
Try this:
var keyPressHandler = function(e){
e.preventDefault(); // prevents any default actions of the browser
e.stopPropagation(); // prevents the event mechanism from bubbling
return false; // If there is any condition on the return value, assure it fails
};
element.addEventListener('keydown',keyPressHandler,false);

Preventing mouse emulation events (i.e. click) from touch events in Mobile Safari / iPhone using Javascript

In doing a single page Javascript app with interactive DOM elements I've found that the "mouseover-mousemove-mousedown-mouseup-click" sequence happens all in a bunch after the "touchstart-touchmove-touchend" sequence of events.
I've also found that it is possible to prevent the "mouse*-click" events from happening by doing an "event.preventDefault()" during the touchstart event, but only then, and not during the touchmove and touchend. This is a strange design, because because it is not possible to know during the touchstart yet whether the user intents to drag or swipe or just tap/click on the item.
I ended up setting up a "ignore_next_click" flag somewhere tied to a timestamp, but this is obviously not very clean.
Does anybody know of a better way of doing this, or are we missing something?
Note that while a "click" can be recognized as a "touchstart-touchend" sequence (ie no "touchmove"), there are certain things, such as keyboard input focus, that can only happen during a proper click event.
Just prevent the touchend event. It will let the browser scroll the page when you touch the element but won't let it emit artificial mouse events.
element.addEventListener('touchend', event => {
event.preventDefault();
});
I've run into similar problems making cross-platform HTML5/JS apps. The only real answer for me was to preventDefault on the touch events, and actually manage the touch states and fire click, drags, etc. events on my own according to my logic. This sounds much much more daunting than it really is, but the mimicked click/mouse events work perfectly on most mobile browsers.
Click and the extra mouse sequence are all there for your convenience (and compatibility). My rule of thumb- if it's for your convenience but it's inconvenient, best kill it.
As far as the input boxes, they only need the touchend events. I've killed click/mouse events and was still able to let mobile browsers respond correctly to touches on inputs. If it's still giving you issues, you can modify the event handler to only supress events on non-inputs:
function touchHandler(event) {
var shouldIgnore = event.target != null
&& ( event.target.tagName.toLowerCase() == "input" || event.target.tagName.toLowerCase() == "textarea" );
if(!shouldIgnore) e.preventDefault();
}
I've made a solution myself, since I have not found a sufficient solution elsewhere:
var isTouch = ('ontouchstart' in window);
function kill(type){
window.document.body.addEventListener(type, function(e){
e.preventDefault();
e.stopPropagation();
return false;
}, true);
}
if( isTouch ){
kill('mousedown');
kill('mouseup');
kill('click');
kill('mousemove');
}
The check of isTouch lets things work as normal on mouse-input devices but kills the emulated events on Safari/iOS. The trick is to use useCapture = true in the call to addEventListener so we scoop up all the mouse events in the page without hacking the code all over the web app. See the docs for the function here: https://developer.mozilla.org/en-US/docs/DOM/EventTarget.addEventListener?redirectlocale=en-US&redirectslug=DOM%2Felement.addEventListener
Edit:
Now that libraries for handling this issue are better, you can just use something like Fastclick as an alternative (https://github.com/ftlabs/fastclick).
If you have to support devices which support both mouse and touch, another solution is to use a capture event listener which stops all mouse events which occur either
within a delay after the touch event
at the same position as the touch event
on the same target element as the touch event
The information (time, position or target element) of the touch event can be recorded in another capture event listener.
Wrapping your mouse-only code in a Window.matchesMedia function is the cleanest way I found.
if (window.matchMedia('(hover: hover), (any-hover: hover), (-moz-touch-enabled: 0)').matches) {
el.addEventListener('mouseover', ev => {
// mouse handler, no simulated hover
}
}
This works for preventing simulated hovers but will likely prevent simulated clicks, too.
Note: -moz-touch-enabled part required on Firefox as of version 58.
This solution allows you to listen for PointerEvents if they exist, followed by TouchEvents if they exist, followed by MouseEvents if neither of the other two exist. Mobile Safari will still raise both touchstart and mousedown, but you'll only be listening for touchstart.
if (window.PointerEvent) { /* decent browsers */
etouch.addEventListener('pointerdown', (e) => {
console.log('pointerdown');
});
}
else if (window.TouchEvent) { /* mobile Safari */
etouch.addEventListener('touchstart', (e) => {
console.log('touchstart');
});
}
else { /* desktop Safari */
etouch.addEventListener('mousedown', (e) => {
console.log('mousedown');
});
}
Using 'pointerwhatever' instead of 'mousewhatever' seems to work fine on current browsers (2019).
i.e. they invented a way of having the same code for all the entry devices.
Creating Fast Buttons for Mobile Web Applications has their solution to the problem.
Also beware that when using IE10 preventDefault() doesn't stop the ghost/synthetic/emulated mouse events after a MSPointerDown event, so a true cross-browser solution is harder.
You could try to quit the function on "click", "mousedown" or "mouseup" events when the device supports touch events.
use.addEventListener("click",function(e){
// EXIT FUNCTION IF DEVICE SUPPORTS TOUCH EVENTS
if ("ontouchstart" in document.documentElement) return false;
// YOURMOUSEONLY CODE HERE
});
Add an event listener to touchstart that adds attribute data-touched to the element. Add another event listener to click that checks for data-touched. If it's there, prevent default and remove it. Here's some JS from my implementation.
var menuLinks = document.querySelectorAll('#my-nav>li>a');
for (var i = 0; i < menuLinks.length; i++) {
var menuLink = menuLinks[i];
menuLink.addEventListener('touchstart', function () {
menuLink.setAttribute('data-touched', '');
});
menuLink.addEventListener('click', function (event) {
if (menuLink.hasAttribute('data-touched')) {
menuLink.removeAttribute('data-touched');
event.preventDefault();
}
});
pointer... events have a pointerType property that mouse... events lack. You can use this property to detect and ignore events that were generated by touch rather than by a mouse.
Before:
window.addEventListner('mousemove', (e) => {
/* No way to tell if this event came from a mouse or a finger */
console.log(':(');
});
After:
window.addEventListner('pointermove', (e) => {
if (e.pointerType !== 'mouse') return;
/* This event definitely came from a mouse */
console.log(':)');
});
You can take advantage of this property just by replacing your mouse... event listeners with pointer... listeners. pointer... events are well-supported in modern browsers (going back at least three years).

Javascript detect when drop down list is closed

Because of the issue explained in this question I have a situation where I need to attach the mousewheel event to the drop down list only when it is expanded (I do this in the onclick event). However I need to remove the mousewheel event when the list collapses. How do I go about detecting this?
I can't just use the onchange event because the user may not have actually changed their selection. I've tried the onblur event but in most browsers (except IE) the drop list stays focused when the list is collapsed.
Cheers.
var list = document.getElementById("list");
list.onclick = function (e) {
// attach mousewheel
list.onmousewheel = function (e) {
// ...
}
// attach click off
// This event fires fine in all browsers except FF when the list is expanded.
// In firefox it only fires when anywhere in the document is clicked twice.
// The first click closes the drop down list as expected and the second one
// fires the event.
window.document.onclick = function (e) {
list.onmousewheel = null;
window.document.onclick = null
}
};
EDIT:
Unfortunately meder's solution doesnt work in firefox. The click event on the document doesn't get fired until i click twice off the drop down list. How do I get around that? It works fine in IE.
EDIT2:
I've done some more testing and the following browsers behave as expected
IE 7,
Chrome 3
Opera 10
Firefox requires 2 clicks in the window to make it work & Safari doesn't work at all.
It appears that even when you click off the drop down list firefox maintains focus on it. It's not until the second click occurs that the drop down list eventually loses it's focus.
Are you looking for something like this? If the user clicks anywhere that's not within #el, it will branch out and you can do what you want, though this requires jQuery but it would take far too many lines of DOM Scripting.
var dropdown = $('#el');
$(document).click(function(e){
if ( (!$(e.target).is(dropdown)) || !$(e.target).closest('#el').length ) {
// do what you need to
}
});
If not, can you be more specific and include an example?
PS - I did not test the snippet, sorry if it isn't what you want.
OK, I still have no idea what you're trying to achieve with such a tightly-scripted select box, but in general trying to change the internal working of a native <select> isn't fruitful. There's no standard that says how events flow internally to the form element, and browsers that implement select as an OS-level widget (IE) can't do much to support it anyway.
If you must have this behaviour, I'd suggest using scripting to replace the <select> box on-fly with a JavaScript-powered analogue made out of <div>s. Then you can control exactly how each mouse and keyboard interaction behaves. There are many libraries that do this already, though again if you need to be very specific about the exact behaviour they might not suit you.

Categories

Resources