Why is blur event not fired in iOS Safari Mobile (iPhone / iPad)? - javascript

I've two event handlers bound to an anchor tag: one for focus and blur.
The handlers fire on desktop, but in iphone and ipad only focus is fired correctly. Blur is not fired if I click outside the anchor tag (blur fires only when I click some other form elements in the page):
$("a").focus(function(){
console.log("focus fired");
});
$("a").blur(function(){
console.log("blur fired");
});
HTML:
<html>
<form>
test link
<div>
<input type="text" title="" size="38" value="" id="lname1" name="" class="text">
</div>
<div style="padding:100px">
<p>test content</p>
</div>
</form>
</html>

If an anchor has any events attached, the first tap on it in iOS causes the anchor to be put into the hover state, and focused. A tap away removes the hover state, but the link remains focused. This is by design. To properly control an application on iOS, you need to implement touch-based events and react to those instead of desktop ones.
There is a complete guide to using Javascript events in WebKit on iOS.

If you're working with touch devices you can use the touchleave or touchend event to handle when the user clicks outside the area.
$("a").on('touchleave touchcancel', function () {
// do something
});
For this to work you need to update your focus function to listen for an on click event as follows
$("a").on("click", function (e) {
if(e.handled !== true) {
e.handled = true
} else {
return false
}
// do something
})

I have check all the doc in the #NicholasShanks answer, but a little frustrated testing all the events.
Android and iOS:
Tested on Android Samsung S9
Tested on iOS iPad 5ºgen
Finally i have got a solution:
Seems iPad listen to mouseout as blur, and seems android listen perfectly to the blur event, i just add a ternary on this case to attach the right event (previously i have aimed to a mobile or tablet device instead of a computer.
// element >> element you want to trigger
// os >> function that return operative system 'ios' or 'android' in my case
element.addEventListener(os === 'ios' ? 'mouseout' : 'blur', () => {
// Do something
})

It's a hack, but you can get .blur to fire by registering a click handler on every DOM element. This removes focus from the previously focused element.
$('*').click();
$('html').css('-webkit-tap-highlight-color', 'rgba(0, 0, 0, 0)');
The second line removes the highlight when elements are clicked.
I know this is sub-optimal, but it may get you going.

The blur event does not fire because when you click outside the anchor tag on a non-clickable element, iOS ignores the click (and the click event does not fire).
There are a couple of threads regarding this (e.g. .click event not firing in Chrome on iOS). You can fix it by adding cursor: pointer to the <body> or some other element that the click will be performed on.

The simplest solution I've found is to just make document.body "clickable" at page initialization time:
document.body.onclick = function() {};
Then a click anywhere will blur the active element, just like on a desktop browser. Tested on iOS 15.3.1.

Related

addEventListener not working on mobile/input element [duplicate]

I've used onclick events in my website. But when I open it in google chromes' developer mode's mobile view, nothing happens on touch on the elements which work on click with mouse. So my question is:
Do I have to also add ontouch events along with onclick events, or onClick event work on touch on all touch-screen devices?
P.S: You can see all of my codes here: https://github.com/SycoScientistRecords/sycoscientistrecords.github.io/
Or at the live website: http://sycoscientistrecords.github.io
And no I haven't tested the website on real phone.
onclick works fine on touchscreens; I've used it several times and have never had any problem.
You could consider using onmousedown instead of onclick. Or use jQuery to detect taps.
I found this detailed writeup at MDN very helpful. In particular:
the browser may fire both touch events and mouse events in response to the same user input [emphasis mine]
and
the element's touch event handlers should call preventDefault() and no additional mouse events will be dispatched
So, your touchstart or touchend listener can call evt.preventDefault() and your mousedown / mouseup listeners won't fire because they come later in the chain.
In Angular, I was able to detect whether I'd clicked a button using my mouse or my laptop's touchscreen, by changing (click)="doSomething()" to (mouseup)="doSomething(false)" (touchend)="doSomething(true); $event.preventDefault()". The method is called with true for touch events and false for mouse events.
onclick may not work on touch devices, I had this issue and the event ontouchstart sorts it.
if you use ontouchstart and onclick watch that you don't trigger the event twice.
this is another post related
onClick not working on mobile (touch)
New browsers have a pointerType which determines if the onClick is made by a mouse or via a touch. If you just want make adjustments in user behavior based on the input, using pointerType is the safest way.
if you are using jQuery:
$(selector).click(e => {
if (e.pointerType === "mouse") {} // mouse event
else {} // touch event
});
if you are using vanilla JS:
element.addEventListener('click', e => {
if (e.pointerType === "mouse") {} // mouse event
else {} // touch event
});
If you are using React, the event is wrapped around a synthetic event. To access the pointerType, you have to use the nativeEvent of the react event. Here is what you need to consider (especially if you are using Typescript). If the event is triggered by a mouse, the native event is an instance of MouseEvent which does not have pointerType, so, first you need to check the type of native event which will also take care of the typing problems in TS
<div
onClick={e => {
if (e.nativeEvent instanceof PointerEvent && e.nativeEvent.pointerType === 'touch') {} // Touch Event
else {} // Mouse Event
}}
></div>
Pro tip: If you want to test the touch event in development, use Chrome following this. Note that Safari has a responsive mode which simulates the framework of iPhones and iPads. However, Safari always registers a mouse event even when you are in responsive design mode and have selected an iPhone or iPad.

Responding to touchstart and mousedown in desktop and mobile, without sniffing

So, I have a problem. I want to respond to a user pressing the mouse button (on desktop) or touching a div (on mobile). I'm trying to be compatile with evergreen browsers. This is what I tried so far:
listen only to mouseDown event. This works on desktop but doesn't work in mobile if the user is dragging. I want the handler to be called as soon as the user touches the screen, no matter if they're moving their finger in the process.
listen only to touchStart event. This works on mobile and desktop, except for Edge and Safari desktop, which don't support touch events.
listen to both, then preventDefault. This causes a double handler call on Chrome mobile. It seems that touch events are passive to allow uninterrupted scrolling on mobile Chrome, so preventDefualt has no effect on them . What I get is a warning message saying "[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080" in the console, preventDefault is ignored and my event is called twice.
Obviously this can be solved by sniffing touch events, but the net is full of self-righteous rants on how one has to be device-agnostic and that it's dangerous to detect touch events before the user interacted.
So I guess that the question is: is there a way to do what I want to do without sniffing for touch events?
Below my sample React code:
function handler(e) {
console.log('handler called')
e.preventDefault()
}
export default function MyElement() {
return (
<div
onMouseDown={handler}
onTouchStart={handler}
>
Hello
</div>
)
}
It turn out it's not yet possible in React. One workaround is set a flag the first time touchStart it's received.
touchHandler = () => {
this.useTouch = true
this.realHandler()
}
mouseHandler = () => {
if (this.useTouch) return
this.realHandler()
}
With the caveat that the first touchStart can be lost in case of dragging.
Quite disappointing.

Display all iphone screen interaction events to the user

My iPhone's screen is playing up. I had the idea, since I'm a web developer, to go to a page on my local machine's web server, or on jsfiddle, using my phone, and have some jquery running on that page which gives me some simple feedback about every touch event. (I have a feeling that I will see lots of spurious swipe events firing off even when i'm not touching the screen, for example).
So, something like this:
<div id="feedback">
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
function feedback(msg){
$("#feedback").append("<div class=\"line\">"+msg+"</div");
}
$(function() {
feedback("in $(function) block");
$(document).on("click", function(event){
feedback("clicked at "+event.pageX+","+event.pageY);
});
//can you replace the below with working code?
$(document).on("all events", function(event){
var msg = "Some basic info about this event: if it's a swipe, which direction. if it's a keypress, which character? etc"
feedback(msg);
});
});
</script>
I've set this up in a jsfiddle here: https://jsfiddle.net/sxqjp9xe/
Do I need to write a handler for every event i'm interested in, like I've done with the click handler? Or is there some more generic solution? Thanks, Max
EDIT: changed my "feedback" function to use prepend() instead of append() as it's easier to see once you get a lot of lines on there.
You can include more than one event in the handler:
function feedback(msg){
$("#feedback").append("<div class=\"line\">"+msg+"</div");
}
$(document).on("blur touchstart touchmove touchend touchcancel focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error", function(event) {
var msg = event.type
feedback(msg);
}
https://jsfiddle.net/e9e0ehyc/

Blur event stops click event from working?

It appears that the Blur event stops the click event handler from working? I have a combo box where the options only appear when the text field has focus. Choosing an option link should cause an event to occur.
I have a fiddle example here: http://jsfiddle.net/uXq5p/6/
To reproduce:
Select the text box
Links appear
Click a link
The blur even occurs and the links disappear
Nothing else happens.
Expected behavior:
On step 5, after blur occurs, the click even should also then fire. How do I make that happen?
UPDATE:
After playing with this for a while, it seems that someone has gone to great lengths to prevent an already-occurred click event from being handled if a blur event makes the clicked element Un-clickable.
For example:
$('#ShippingGroupListWrapper').css('left','-20px');
works just fine, but
$('#ShippingGroupListWrapper').css('left','-2000px');
prevents the click event.
This appears to be a bug in Firefox, since making an element un-clickable should prevent future clicks, but not cancel ones that have already occurred when it could be clicked.
Other things that prevent the click event from processing:
$('#ShippingGroupListWrapper').css('z-index','-20');
$('#ShippingGroupListWrapper').css('display','none');
$('#ShippingGroupListWrapper').css('visibility','hidden');
$('#ShippingGroupListWrapper').css('opacity','.5');
I've found a few other questions on this site that are having similar problems. There seem to be two solutions floating around:
Use a delay. This is bad because it creates a race condition between the hiding and the click event handler. Its also sloppy.
Use the mousedown event. But this isn't a great solution either since click is the correct event for a link. The behavior of mousedown is counter-intuitive from a UX perspective, particularly since you can't cancel the click by moving the mouse off the element before releasing the button.
I can think of a few more.
3.Use mouseover and mouseout on the link to enable/disable the blur event for the field. This doesn't work with keyboard tabing since the mouse is not involved.
4.The best solution would be something like:
$('#ShippingGroup').blur(function()
{
if($(document.activeElement) == $('.ShippingGroupLinkList'))
return; // The element that now has focus is a link, do nothing
$('#ShippingGroupListWrapper').css('display','none'); // hide it.
}
Unfortunately, $(document.activeElement) seems to always return the body element, not the one that was clicked. But maybe if there was a reliable way to know either 1. which element now has focus or two, which element caused the blur (not which element is blurring) from within the blur handler. Also, is there any other event (besides mousedown) that fires before blur?
click event triggers after the blur so the link gets hidden. Instead of click use mousedown it will work.
$('.ShippingGroupLinkList').live("mousedown", function(e) {
alert('You wont see me if your cursor was in the text box');
});
Other alternative is to have some delay before you hide the links on blur event. Its upto you which approach to go for.
Demo
You could try the mousedown event instead of click.
$('.ShippingGroupLinkList').live("mousedown", function(e) {
alert('You wont see me if your cursor was in the text box');
});
This is clearly not the best solution as a mousedown event is not achieved the same way for the user than a click event. Unfortunately, the blur event will cancel out mouseup events as well.
Performing an action that should happen on a click on a mousedown is bad UX. Instead, what's a click effectively made up of? A mousedown and a mouseup.
Therefore, stop the propagation of the mousedown event in the mousedown handler, and perform the action in the mouseup handler.
An example in ReactJS:
<a onMouseDown={e => e.preventDefault()}
onMouseUp={() => alert("CLICK")}>
Click me!
</a>
4.The best solution would be something like:
$('#ShippingGroup').blur(function()
{
if($(document.activeElement) == $('.ShippingGroupLinkList'))
return; // The element that now has focus is a link, do nothing
$('#ShippingGroupListWrapper').css('display','none'); // hide it.
}
Unfortunately, $(document.activeElement) seems to always return the
body element, not the one that was clicked. But maybe if there was a
reliable way to know either 1. which element now has focus or two,
which element caused the blur (not which element is blurring) from
within the blur handler.
What you may be looking for is e.relatedTarget. So when clicking the link, e.relatedTarget should get populated with the link element, so in your blur handler, you can choose not to hide the container if the element clicked is within the container (or compare it directly with the link):
$('#ShippingGroup').blur(function(e)
{
if(!e.relatedTarget || !e.currentTarget.contains(e.relatedTarget)) {
// Alt: (!e.relatedTarget || $(e.relatedTarget) == $('.ShippingGroupLinkList'))
$('#ShippingGroupListWrapper').css('display','none'); // hide it.
}
}
(relatedTarget may not be supported in older browsers for blur events, but it appears to work in latest Chrome, Firefox, and Safari)
If this.menuTarget.classList.add("hidden") is the blur behavior that hides the clickable menu, then I succeeded by waiting 100ms before invoking it.
setTimeout(() => {
this.menuTarget.classList.add()
}, 100)
This allowed the click event to be processed upon the menuTarget DOM before it was hidden.
I know this is a later reply, but I had this same issue, and a lot of these solutions didn't really work in my scenario. mousedown is not functional with forms, it can cause the enter key functionality to change on the submit button. Instead, you can set a variable _mouseclick true in the mousedown, check it in the blur, and preventDefault() if it's true. Then, in the mouseup set the variable false. I did not see issues with this, unless someone can think of any.
I have faced a similar issue while using jQuery blur, click handlers where I had an input name field and a Save button. Used blur event to populate name into a title placeholder. But when we click save immediately after typing the name, only the blur event gets fired and the save btn click event is disregarded.
The hack I used was to tap into the event object we get from blur event and check for event.relatedTarget.
PFB the code that worked for me:
$("#inputName").blur(function (event) {
title = event.target.value;
//since blur stops an immediate click event from firing - Firing click event here
if (event.relatedTarget ? event.relatedTarget.id == "btnSave" : false) {
saveBtn();
}
});
$("#btnSave").click(SaveBtn)
As already discussed in this thread - this is due to blur event blocking click event when fired simultaneously. So I have a click event registered for Save Btn calling a function which is also called when blur event's related Target is the Save button to compensate for the click event not firing.
Note: Didnt notice this issue while using native onclick and onblur handlers - tested in html.

Clicking on a div's scroll bar fires the blur event in I.E

I have a div that acts like a drop-down. So it pops-up when you click a button and it allows you to scroll through this big list. So the div has a vertical scroll bar. The div is supposed to disappear if you click outside of the div, i.e. on blur.
The problem is that when the user clicks on the div's scrollbar, IE wrongly fires the onblur event, whereas Firefox doesn't. I guess Firefox still treats the scrollbar as part of the div, which I think is correct. I just want IE to behave the same way.
I've had a similar problem with a scrollbar in an autocomplete dropdown. Since the dropdown should be hidden when the form element it is attached to loses focus, maintaining focus on the correct element became an issue. When the scrollbar was clicked, only Firefox (10.0) kept focus on the input element. IE (8.0), Opera (11.61), Chrome (17.0) and Safari (5.1) all removed focus from the input, resulting in the dropdown being hidden, and since it was hidden, click events would not fire on the dropdown.
Fortunately, the shift of focus can be easily prevented in most of the problem browsers. This is done by canceling the default browser action:
dropdown.onmousedown = function(event) {
// Do stuff
return false;
}
Adding a return value to the event handler sorted out the problem on all browsers except IE. Doing this cancels the default browser action, in this case the focus shift. Also, using mousedown instead of click meant that the event handler would be executed before the blur event fired on the input element.
This left IE as the only remaining problem (no surprise there). It turns out that there is no way to cancel the focus shift on IE. Fortunately, IE is the only browser that fires a focus event on the dropdown, meaning focus on the input element can be restored with an IE-exclusive event handler:
dropdown.onfocus = function() {
input.focus();
}
This solution for IE is not perfect, but while the focus shift is not cancelable, this is the best you can do. What happens is that the blur event fires on the input, hiding the dropdown, after which focus fires on the now hidden dropdown, which restores focus on the input and triggers showing the dropdown. In my code it also triggers repopulating the dropdown, resulting in a short delay and loss of the selection, but if the user wants to scroll the selection is probably useless anyway, so I deemed this acceptable.
I hope this is helpful, even though my example is slightly different than in the question. From what I gathered, the question was about IE firing a blur event on the dropdown itself, rather than the button that opened it, which makes no sense to me... Like my use of a focus event handler indicates, clicking on a scrollbar should move focus to the element the scrollbar is part of on IE.
Late answer, but I had the same issue and the current answers didn't work for me.
The hover state of the popup element works as expected, so in your blur event you can check to see if your popup element is hovered, and only remove/hide it if it isn't:
$('#element-with-focus').blur(function()
{
if ($('#popup:hover').length === 0)
{
$('#popup').hide()
}
}
You'll need to shift focus back to the original element that has the blur event bound to it. This doesn't interfere with the scrolling:
$('#popup').focus(function(e)
{
$('#element-with-focus').focus();
});
This does not work with IE7 or lower - so just drop support for it...
Example: http://jsfiddle.net/y7AuF/
I'm having a similar problem with IE firing the blur event when you click on a scrollbar. Apparently it only happens n IE7 and below, and IE8 in quirksmode.
Here's something I found via Google
https://prototype.lighthouseapp.com/projects/8887/tickets/248-results-popup-from-ajaxautocompleter-disappear-when-user-clicks-on-scrollbars-in-ie6ie7
Basically you only do the blur if you know the person clicked somewhere on the document other than the currently focused div. It's possible to inversely detect the scrollbar click because document.onclick doesn't fire when you click on the scrollbar.
This is an old question but since it still applies to IE11 here is what I did.
I listen to the mousedown event on the menu and set a flag on this event. When I catch the blur event, if the mousedown flag is on, I set the focus back. Since Edge, FF and Chrome won't fire the blur event but will fire the mouseup event (which IE won't), I reset the mousedown flag on the mouseup for them (on the blur for IE).
mousedown: function (e) {
this.mouseddown = true;
this.$menu.one("mouseup", function(e){
// IE won't fire this, but FF and Chrome will so we reset our flag for them here
this.mouseddown = false;
}.bind(this));
}
blur: function (e) {
if (!this.mouseddown && this.shown) {
this.hide();
this.focused = false;
} else if (this.mouseddown) {
// This is for IE that blurs the input when user clicks on scroll.
// We set the focus back on the input and prevent the lookup to occur again
this.skipShowHintOnFocus = true; // Flag used to avoid repopulating the menu
this.$element.focus();
this.mouseddown = false;
}
},
That way the menu stays visible and user doesn't loose anything.
Use focusout, and focusin (IE specific events)
$(document).bind('focusout', function(){
preventHiding = false;
//trigger blur event
this.$element.trigger('blur');
});
$(document).bind('focusin', function(){
preventHiding = true;
});
$(document).bind('blur', function(){
// Did anyone want us to prevent hiding?
if (this.preventHiding) {
this.preventHiding = false;
return;
}
this.hide();
});
I had the same probleme. Resolved by putting the menu in a wrapping (bigger) div. With the blur applied to the wrapper, it worked!
Perhaps try adding the tabindex attribute set to -1 to the div node.
I don't think this is an IE issue.
It's more a case of how to design your interaction and where to handle which event.
If you have a unique css-class-accessor for the related target, canceling a blur event can be done by checking the classList of the event.relatedTarget for the element you want to allow or disallow to initiate the blur event. See my onBlurHandler from a custom autocomplete dropdown in an ES2015 project of mine (you might need to work around contains() for older JS support):
onBlurHandler(event: FocusEvent) {
if (event.relatedTarget
&& (event.relatedTarget as HTMLElement).classList.contains('folding-select-result-list')) {
// Disallow any blur event from `.folding-select-result-list`
event.preventDefault();
} else if (!event.relatedTarget
|| event.relatedTarget
&& !(event.relatedTarget as HTMLElement).classList.contains('select-item')) {
// If blur event is from outside (not `.select-item`), clear the suggest list
// onClickHandler of `.select-item` will clear suggestList as configured with this.clearAfterSelect
this.clearOptions(this.clearAfterBlur);
}
}
.folding-select-result-list is my suggestions-dropdown having 'empty spots' and 'possibly a scrollbar', where I don't need this blur event.
.select-item has it's own onClickHandler that fires the XHR-request of the selection, and closes the dropdown when another property of the component this.clearAfterSelect is true.

Categories