Angular - issue with ng-blur on iPhone/iPad - javascript

I use AngularJS 1.4.7
I have editable input. If we focus on input and after click outside of this input field, script will do function "save()" in ng-blur. Everything work correctly, but on iPhone 5/6 and iPad don't work (not execute anything in ng-blur). I don't know why, but I deduce that problem is in focus/touch action. Someone know where is the problem?

Fix ng-blur not working on empty space and most elements with iOS
Goal
In my app, we wanted to hide open menus or search results when clicking away from
the menu or search box.
Problem
iOS Safari does not blur properly when clicking away from the current element.
Note
You do not need a special directive. The problem is not with ng-blur, which works fine. The problem is that a blur event never occurs because of Apple's unusual design on iOS, regardless of whether you use ng-blur or a native DOM blur event.
Reason for Issue
Apple has designed mobile Safari to perform event bubbling differently than desktop browsers do. According to official Apple docs, if an element that is clicked has no click listener attached to it, then no click event is triggered. Without a click event, focus does not change and no blur event occurs, so the current element does not lose focus even though the user really did click elsewhere on the page.
The blur will only happen if the user clicked an element with a click event listener. Some elements like hyperlinks and inputs have "built-in" event listeners for click, and so will always register a click, causing blur to work.
Normally in desktop browsers, the click event is blindly fired whether there is a registered listener or not in the element's DOM hierarchy. That is why ng-blur works as expected in desktop browsers, even on "empty space".
Apple says that for iOS Safari they only dispatch the event if they find an element registered to listen for it.
Apple Docs - go to Figure 6-4
Apple's documentation makes it sound like they are looking for a registered event listener and handled on the target element, but that is not the case. I found the answers in the following article, and more importantly, one of the comments to it.
Quirksmode Article on iOS event delegation
First, observe that placing a listener on the document or body does not cause the click event to be dispatched on iOS Safari, if the target element has no click event listener and handler. It works in most browsers, but not on iOS.
It appears that Apple is checking the DOM hierarchy up to the <body> tag before dispatching the click event, but is not checking the <body> tag or above. Therefore, you need a click event listener attached to an element somewhere below the <body>.
Solution/Workaround
The solution then is very simple. All you need to do is to wrap all of the page content in a "master" container element just below the <body> element, and put the listener there instead of on the body or the document. It can even be an empty handler function. Apple is only checking to see if one is registered. The reason for wrapping all content is so that no matter where in the page the user clicks, the bubble-up process will eventually reach the master container.
If you do that, then ng-blur will work as expected in iOS, because the click event on empty space (an element with no click event listener) will pass the iOS check when it finds the parent container's click event listener and a click event will be dispatched normally, like it would be in any other browser.
Caution
This solution effectively causes iOS Safari to see a click event listener on every DOM element's hierarchy, tricking it into dispatching the click event on every element, just like a normal browser. I do not know if Apple has a performance reason they avoid this in iOS, or if it is just an aesthetic/developer type preference (see example). You will be changing the default iOS behavior in your app by using this workaround.
For example, iOS users may accidentally select text on your page that they did not mean too, and which would not normally occur without a click and hold gesture.

I don't know why that problem occurs.
but do one thing. create one small directive 'fake-blur' . then call your method in that directive
App.directive('fakeBlur', function(){
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.blur(function(){
// call you function ex:
scope.save();
})
}
}
});
<input type="text" fake-blur/>

Related

Why does a `click` event get triggered on my <button> when I press Enter on it?

I'm only adding a click event handler on my <button>.
document.getElementsByTagName("button")[0].addEventListener("click", event => {
event.preventDefault();
console.log("Click:", event);
});
<button>Press <kbd>Enter</kbd> on me</button>
(Demo link)
Nevertheless, when I tab to the button in Firefox, then press Enter, I see the click event being fired. However, I cannot see this behaviour documented anywhere. Is this standard behaviour, and can I count on it working in all browsers?
This is largely because lots of authors have historically written code using click events while forgetting to account for users who don't click (whether because they prefer to use a keyboard to navigate, have a disability which makes it hard to use a pointing device, or whatever other reason).
The behaviour is documented in the HTML specification:
Certain elements in HTML have an activation behavior, which means that the user can activate them. This triggers a sequence of events dependent on the activation mechanism, and normally culminating in a click event, as described below.
…
For accessibility, the keyboard’s Enter and Space keys are often used to trigger an element’s activation behavior.
It then goes on to explain the steps in detail.
Because for keyboard users (where a mouse is not available), when a button is in focus and you press Enter (possibly Space as well) it simulates a click event.
This is the browser's accessibility support which most, if not all, browsers provide.
This is documented in WCAG: SCR35: Making actions keyboard accessible by using the onclick event of anchors and buttons:
While onclick sounds like it is tied to the mouse, the onclick event is actually mapped to the default action of a link or button. The default action occurs when the user clicks the element with a mouse, but it also occurs when the user focuses the element and hits enter or space, and when the element is triggered via the accessibility API.
This is also stated in UIEVents /click section:
In addition to being associated with pointer devices, the click event type MUST be dispatched as part of an element activation, as described in §3.5 Activation triggers and behavior.
The Activation trigggers paragraph states that:
User-initiated activation triggers include clicking a mouse button on an activatable element, pressing the Enter key when an activatable element has focus, or pressing a key that is somehow linked to an activatable element (a hotkey or access key) even when that element does not have focus.
Which means that any of those triggers will dispatch the click event.
I've solved it by using div element with onClick listener instead. It seems that button element captures enter press for the accessibility purposes, like the guys above wrote.

JQuery click event triggered on Chrome and FF device touch emulation?

In my Rails site, I have an element that I want to act as a link when clicked by a mouse (e.g. on a desktop), but not when touched on a touch-screen device (because there is also a hover behavior).
My understanding is that the JQuery .click event should not get triggered on a touch.
My coffeescript for setting the click handler is simply
...
$(this).click ->
location.href = url
...
(where "this" is the element in question)
I know this code works, because the click action works with the mouse. To ensure that it doesn't get triggered on a touch device, I use the device emulation in Chrome's Developer Tools to emulate a touch. However, when I do this, the method still fires and follows the link.
Is this a problem with the Chrome emulation or with my code? I want to know whether it will behave this way on real touch devices.
edit: Same thing happens with Firefox, so I'm thinking it's my code...
I realized that touch events trigger click events as well, later on in the event chain. To get the functionality I wanted, I kept the .click handler, but added a .touchstart handler where I called event.preventDefault() to short-circuit the rest of the event chain. This way, the .click handler fires for mouse clicks, but not for touches.

JavaScript: Swipe for Action Pattern Implementation

I have implemented the Swipe for Action Android pattern in my mobile web application (PhoneGap) using JavaScript & CSS animations/transitions.
However, there's one thing that's still eluding me.
I wish, that once the action menu is displayed fully and the user clicks anywhere outside of the action menu (labelled 3 in the figure), the menu should retract and the original item displayed (labelled 1 in the figure).
In a desktop application, one could "capture focus" and perform the transition back to (1) in lostfocus.
What is the JS equivalent of lostfocus event. I see an onfocus and onblur event, but from what I read it's really meant for things that need focus; like input, textarea, etc.
How else could I catch that event I'm interested in, other than putting some code in the touchend of every other element in the page and forcing the retraction of open actions explicitly?
I think you gave the answer yourself. focus and blur are the events to be used for this and they are not exclusively meant for input elements, as you can see here [1].
I'm even trigger the focus event manually in a layer use case: A layer opens and I want to capture the keypress of ESC to close the layer. For this I need to set the focus on the layer as my event handler would not fire otherwise.
To capture the click outside you just need to register for pointerUp or click events on an element that spans the whole screen (it must really cover the whole screen like the body element). Because of the event bubbling the handler will fire as long as nothing else captured and cancelled it.
[1] https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#event-type-blur

Google Chrome Javascript Link Bug

When clicking a link in google chrome the focus event fails to fire. All other browsers fire the focus event for links.
Link
I do not want to attach the event onmousedown, but onfocus.
Anyone have an idea for a workaround.
EDIT:
I would definitely consider this a bug because all other focusable elements trigger focus on click.
Even non focusable elements with a tabindex trigger focus on click in google chrome.
<div tabindex="-1" onfocus="console.log('focus')">div</div>
I can't attach to both click and focus because then onclick, other browsers would call the function twice. I can't detect this functionality because it requires user
interaction, and I won't do user agent string detection, because well its wrong.
Using this html:
Link
Is they any way to invalidate the second onmousedown call to prevent the function being called twice in non google browsers.
EDIT 2:
After some further testing <input type=radio> also fails to call focus in google chrome.
Why in the world is google chrome like this, while Opera, IE and firefox are all okay. What is crazy is that the mobile webkit browser even triggers focus on links when I tried it on my Android device.
That actually works just fine, except that the focus event isn't triggered simply by clicking on the link (try tabbing and you'll see the event firing). I don't think it's a bug, why not just safe guard and use both?
One work around you could do to avoid the double focus events from popping on the working browsers while still getting the focus event to pop on Chrome, is to on a click event check whether anything has focus on the page, and if not, then trigger the focus event.
With jQuery it could be done like this:
$('a').click(function(e){
if(!$('*:focus').length) $(this).trigger('focus');
});
example: http://jsfiddle.net/niklasvh/qmcUt/
You can use small hack:
Link

Click Delay On IPhone and Suppressing Input Focus

The webkit browser on iphone has a 300ms delay between when a user does a touch and when the javascript gets a click event. This happens because the browser needs to check if a user has done a double tap. My app doesn't allow zooming so a double tap is useless for me. A number of people have proposed solutions to this problem and they usually involve handling the 'click' on the touch end event and then ignoring the click generated by the browser. However, it doesn't seem to be possible to suppress a click that gets sent to an input element. This can cause a problem if you have a dialog that opens above a form then a user hits the close button and their click gets routed to an input element when the form disappears.
Example with jqtouch (for iphone only)
You have to capture your event on touchstart if you want to get the fastest possible responsiveness. Otherwise you'll be doomed with this input lag.
You have to remember though that capturing event on touchstart and responding to it makes it impossible to cancel action by dragging your finger out of responsive area.
I have personally used this in my PhoneGap html/js based iphone application and it worked perfect. The only solution to give this almost-native feel.
Now regarding your problem - have you tried to stop the propagation of the event? It should solve your problem.
$('.button').bind('touchstart', function(e){
e.stopPropagation();
// do something...
});
hope it helps,
Tom
My colleagues and I developed an open source library called FastClick for getting rid of the click delay in Mobile Safari. It converts touches to clicks and handles those special cases for input and select elements cleanly.
It's as easy as instantiating it on the body like so: new FastClick(document.body), then listening for click events as usual.
I made Matt's FastClick a jquery plugin:
stackoverflow link
Just had a comment about the onClick handler being called without the necessary scope being passed. I updated the code to make it work.
There also seems to be a problem when input elements lie under the ghost event's position: the focus event gets triggered without being busted.
I see two problems in the question. One is handling the delay in click and the other is handling input focus.
Yes, both of these have to be handled properly in mobile web.
The delay in click has deeper reasons. The reason for this 300ms delay is explained very well in this article.
Responsiveness of the click event.
Thankfully this problem is well known and solved by many libraries.
JQTouch, JQuery Mobile,
JQuery.tappable,
Mootools-mobile,
and tappable
Most of these libraries create a new event called tap. you can use the tap event similar to the click event. This jquery-mobile event handling might help.
$("#tappableElement").tap(function(){
// provide your implementation here. this is executed immediately without the 300ms delay.
});
Now, the second problem is with the handling of input focus.
There is a noticeable delay here also.
This can be solved by forcing focus on the element immediately for one of the touchstart or touchend events. This JQuery event handling might help.
$('#focusElement').bind('touchstart', function(e){
$(this).focus();
});
$('#focusElement').focus(function(e){
// do your work here.
});
You can do e.stopPropagation in 'touchstart' event handling to avoid propagation. But I would strongly advise against return false; or e.preventDefault as that would stop default functionality like copy/paste, selecting text etc.

Categories