Determine what triggered focus event? - javascript

I need to determine what caused a focus event.
Ideally, I want to differentiate between a click, a tab/keyboard input, and a manual (via code) trigger.
How can I do this?
I'm looking at the event object, but I'm not seeing anything too useful.

If the focus comes from a $x.focus() call, then the event won't have an originalEvent property because there was no event from the browser so:
if(ev.hasOwnProperty('originalEvent')) {
// Focus event was manually triggered.
}
To differentiate between keyboard and mouse based focus events, you could try binding a keydown handler to everything else to detect a Tab or Shift-Tab but that would be a gross hack and probably not reliable; for example, on an iPad, you don't hit Tab to move to the next field, you hit Next or Previous in the popup keyboard to move around and those may not register as key presses at all.
There's a similar question about click events that might be of interest as well:
In jQuery, how can I tell between a programmatic and user click?
As you note in the comments, you could trap click events to detect a mouse-based focus change and set a flag somewhere to remember it. Then you'd have this:
If there is no originalEvent in the jQuery event then the focus change was triggered manually (i.e. $x.focus() or similar).
If the click handler flag is set then the focus change came from a mouse action.
Otherwise the focus change came from a keyboard event.
You'd have to be careful that your click and focus events came in the right order and you'd need to make sure the flag was cleared when you're done with it. This might not be bullet proof but maybe it doesn't need to be.

Related

How to prevent focus change on long press in Chrome on Android?

I want focus to stay on the currently focused DOM element. I am using the usual way to prevent focus change on click on another element:
button.addEventListener("mousedown", (evt)=>{evt.preventDefault();});
This works on normal clicks, but not on long press.
I expected it to work on long press too. Anyone knows if the behavior is a bug?
Is there any way to prevent focus change on long press?
Listen to the touchstart event:
element.addEventListener("touchstart", (evt)=>{evt.preventDefault();});
Note that you will still need the mousedown event listener for non-touch devices.

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.

Prevent blur event on keyboard close

This is for mobile web apps, not native.
I have an autocomplete drop-down that closes on the blur event. But I want to prevent this from happening when the user closes the keyboard on mobile (ie the autocomplete dropdown should stay visible). Is there a way to distinguish a blur event caused by the keyboard closing, and other kinds of blur events? Can I prevent a blur event specifically caused by closing the keyboard in mobile?
Ok, first I would recommend checking your libraries' documentation because they might provide something in their API, although there's nothing I'm aware of. That's the disadvantage of Web Apps: you can't access native functionality.
If you still really want to do it, here's a possible solution. It's ugly, but it might work.
Container on tap function (event)
If !autocomplete return // if you cant see the popup do nothing and blur normally
If (event.target != inputID) AND (event.target != autocID)
CloseAutocomplete()`
Essentially, instead of closing the autocomplete on blur, close it whenever the user taps on your parent container, but not on the input itself or the autocomplete. Depending on how it works, you could extend it to check any tap on screen.
I see you tagged iOS... you can observe keyboard events with UIKeyboardWillHideNotification. [https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIWindow_Class/index.html#//apple_ref/c/data/UIKeyboardWillHideNotification] You may have to do and asynchronous delay to catch that event on with onblur, though.
If you really want to prevent all "other kinds of blur events" you could make your drop-down close on specific events (instead of on blur). For example: when a another field is clicked, navigation, and/or add an 'x' close button next to that drop-down control.
In the blur event listener, test for event.relatedTarget === null. If focus has moved to a different element, event.relatedTarget will refer to that element, but if there is no focus (as is the case when the cause of the blur is keyboard close), event.relatedTarget will be null.

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

Emulating "input clear" icon in iOS-targeted web app

Native iOS apps contain "clear buttons" in input fields. They clear the text while maintaining field focus.
I am developing a web app targeted specifically at iOS devices, and not having any luck emulating the behavior. If I overlay another element with a click event to clear & refocus the input, the iPad ignores the call to focus because it begins hiding the keyboard the instant the blur event fires on the input (before the click event). Therefore the user must manually re-focus the field after clicking the clear icon to get back the keyboard.
Is there any way to grab a touch event on the overlay image/icon without the soft keyboard deciding to vanish, or a better way to do this?
daxelrod's 2nd comment above led me to the solution: Trap the mousedown event on the clear icon, stop it, and clear the input. Thereby a "click" never occurs, and the input does not lose focus.
I thought that blur() fired at the browser level before any of the mouse events (down, up, click) did, so I didn't think to try it. Glad to see I was wrong!
In Mootools flavored JS:
document.id('inputClearImage').addEvent('mousedown', function (e) {
e.stop();
document.id('input').set('value', '');
});

Categories