I have a tree component at the end of the DOM that handles keydown events so that the tree is navigable via arrow keys. I don't have any cases that respond to a "Tab" press, but the default behavior seems to send the focus to the first tabbable element in the DOM, rather than shifting focus to the browser.
I am unsure if this would qualify as a keyboard trap, as the focus is able to pass through all available DOM elements, and users can still access the browser via Ctrl+L/Cmd+L.
If I add a tabbable item after this tree component, such as a , shifting focus from the tree moves it to the button, and another Tab press will bring the focus into the browser.
Currently, I am not able to add a tabbable element to the end of the DOM that would make sense with the UI design. My main question: Does this looping back to the first element in the DOM from the last element introduce a Keyboard Trap concern? If so, is there any way I could get a Tab keypress to move the focus into the browser via Javascript?
Edit: This behavior was observed on Chrome on a Windows VM.
We're using jQuery to handle the keydown events, but removing the Tab case still cycles the focus back to the top:
$.on({
mousedown: {
//...handle mousedown event
},
keydown: function (e) {
var newFocus;
if (e.keyCode === keyCodes.Tab) {
// NOTE: tried removing this check entirely before, still cycles back to the first element rather than exiting focus to the browser
// Perhaps cycling to the first tabbable element is default jQuery keydown behavior?
e.preventDefault(); // Prevent tab nav from bubbling-up and triggering focusin
newFocus = e.shiftKey ?
getPreviousTabTarget() :
getNextTabTarget();
} else if (arrowKeyCodes.indexOf(e.keyCode) !== -1) {
//...handle arrow key keydown event
} else if (e.keyCode === keyCodes.Enter) {
//...handle Enter keydown event
} else if (e.keyCode === keyCodes.Space) {
//...handle Space keydown event
}
if (newFocus) {
newFocus.focus();
}
},
focusin: function (e) {
if (!currentItem) {
currentItem = jqElement.children()[0];
}
if (currentItem) {
currentItem.focus();
}
}
});
Edit 2: This example page from W3 showing a jQuery keydown/keyup event handler does pass the tab into the browser properly. There is definitely something I missed here. Thanks #QuentinC for the sanity check! And thanks to everyone for your responses.
Related
tab keydown event with preventDefault resets cursor position to start when tab is the last in a series of continuous keystrokes
I've got a chrome gmail extension written in react where I'm overriding tab behavior. preventDefault and stopImmediatePropagation in the event listener seem to only work when tab is the only keyboard event clicked within a cursor blink. If tab is the last character pressed in a continuous series of keystrokes, the cursor resets to the start of the composed message although the event still has it's default prevented. This behavior is only for the tab key - for example the same event listener works correctly when preventing "q". Tab prevention only seems to work if I focus on the contentEditable after preventDefault
public componentDidMount () {
const { el } = this.props.editable;
el.addEventListener("keydown", this.testKeys.bind(this), true);
};
private testKeys (e:KeyboardEvent) {
if (e.key === "Tab") {
console.log("tab");
e.preventDefault();
e.stopImmediatePropagation();
this.props.editable.el.focus();
}
}
How do I keep the correct cursor position even when tab is the last of a series of key strokes?
Figured it out:
Gmail attaches its own listener for the tab keydown event to preventDefault() and change the focus to the next element, at the the window level. To catch the event first you need to set your event listener on the window with capture:true.
I'm building a widget akin to a datepicker, but I can't figure out how to make it disappear when when either (a) the user tabs out of the input box, or (b) clicks outside both the widget and input.
It's easy to bind a blur event to the input box, but the problem is that it will get triggered when you click on the widget, and there doesn't appear to be a reliable way to determine which element the focus was changed to from inside the blur event.
Closing the widget when the user clicks outside of the input is a bit sketchy too, but it's doable:
$('body').on('click', function(e) {
if(e.target != self.element[0] && e.target != self.clock[0] && !$.contains(self.clock[0], e.target)) {
self.clock.hide();
}
});
But I wouldn't need that if I could figure out how to handle the blur event properly (which may also be triggered by tabbing outside of the element).
Turns out the solution is actually quite simple. Thanks to rojoca's suggestion, I came up with this:
this.timepicker.on('mousedown', function(e) {
return false;
});
this.element.on('blur', function(e) {
self._parseInput();
self._refreshInput();
self._close();
});
The mousedown event fires first, and by returning false it prevents the blur event from triggering when clicking on the widget. Everything else (clicking outside the widget and input box, or tabbing away) causes a blur, which closes the widget as desired.
Further, it had the unintended by pleasant side-effect of keeping your cursor inside the textbox while interacting with the widget.
I have a usability concern on a web site of mine. I have a set of tabs, each containing a form. When you click on the tab link, it gives focus to the first textbox in the tab content body. Mouse-oriented people love this "feature". The problem is when keyboard-oriented users use the TAB key on their keyboard to go through the tabs. They hit enter on the tab they want to look at, the click event fires and the tab shows up, but focus is given to the textbox, adjusting their tab order completely. So when they hit tab again, they want to go to the next tab on the screen, but since focus was moved inside the form, they can't easily get to the next tab using the keyboard.
So, inside the click event I need to determine if they actually clicked on it with a mouse button. Is this possible? My first attempt was this:
$("#tabs li a").click(function(e) {
var tab = $(this.href);
if(e.keyCode != 13)
$("input:first", tab).focus();
});
But keyCode is always 0. The which property is also always 0. Please help!
Here's the solution I came up with, it's surprisingly simple. I trapped keydown on the tab links, and triggered the click event when keyCode was 13. Luckily, the trigger function allows us to pass extra parameters to the event handler...
$("#tabs li a").keydown(function(e) {
if(e.keyCode == 13) {
$(this).trigger("click", true);
e.preventDefault();
}
});
So I just had to change my click handler to receive the new parameter and use it...
$("#tabs li a").click(function(e, enterKeyPressed) {
if(enterKeyPressed)
alert("Enter key");
else
alert("Clicked");
});
I put up a demo on jsFiddle as well. Thanks to everyone who read the question.
An even simpler solution that worked for me was to just check whether there were any mouse coordinates passed with the event.
document.addEventListener('click', function(e) {
//if the event object is passed with mouse coordinates,
if(e.screenX && e.screenY){
//The mouse was clicked
}else{//The enter key was pressed}
});
Would a global "focus" variable work, which disable focus on mouse setting after tab usage on a given tab until a mouse is moved to a new block.
This would not be the feature your requesting, but I believe it might give you what your looking for.
eg. mouse hoovers option 5, you hit tab, now you store the 5 in the variable, disallowing focus to 5 until something else been focused on, but as soon something else is focused, global is turned back to -1.
Not cleanest workaround I admit that freely.
I have a JQuery scroller on a page where each item is a div with an id. each div has a link to the next div in the scroller (all on the same page)
$('a.panel').click(function () {
};
I have a click event to all links with the 'panel' class where I check which links was clicked and then do some ajax processing accordingly:
if($(this).attr('href')=="#item2")
{
//do some processsing
}
and once the processing is done I use the scrollTo JQuery method to scroll to the next div
I need to have it that the user can press the enter key instead of clicking on the link.
Now the problem is:
a. I have several links on the same page that all need to have this behaviour.
b. I need to differentiate which link triggered the click event and do some server-side processing.
Is this possible at all?
I appreciate the quick and helpful responses!!Thanks a million for the help!
Focus + enter will trigger the click event, but only if the anchor has an href attribute (at least in some browsers, like latest Firefox). Works:
$('<a />').attr('href', '#anythingWillDo').on('click', function () {
alert('Has href so can be triggered via keyboard.');
// suppress hash update if desired
return false;
}).text('Works').appendTo('body');
Doesn't work (browser probably thinks there's no action to take):
$('<a />').on('click', function () {
alert('No href so can\'t be triggered via keyboard.');
}).text('Doesn\'t work').appendTo('body');
You can trigger() the click event of whichever element you want when the enter key is pressed. Example:
$(document).keypress(function(e) {
if ((e.keyCode || e.which) == 13) {
// Enter key pressed
$('a').trigger('click');
}
});
$('a').click(function(e) {
e.preventDefault();
// Link clicked
});
Demo: http://jsfiddle.net/eHXwz/1/
You'll just have to figure out which specific element to trigger the click on, but that depends on how/what you are doing. I will say that I don't really recommend this, but I will give you the benefit of the doubt.
A better option, in my opinion, would be to focus() the link that should be clicked instead, and let the user optionally press enter, which will fire the click event anyways.
I would like to focus on the link, but am unfamiliar exactly how to do this, can you explain?
Just use $(element).focus(). But once again, you'll have to be more specific, and have a way to determine which element should receive focus, and when. Of course the user, may take an action that will cause the link to lose focus, like clicking somewhere else. I have no idea what your app does or acts like though, so just do what you think is best, but remember that users already expect a certain kind of behavior from their browsers and will likely not realize they need to press "enter" unless you tell them to.
If you do choose to use the "press enter" method instead of focusing the link, you'll likely want to bind() and unbind() the keypress function too, so it doesn't get called when you don't need it.
http://api.jquery.com/focus/
http://api.jquery.com/bind/
http://api.jquery.com/unbind/
Related:
Submitting a form on 'Enter' with jQuery?
jQuery Event Keypress: Which key was pressed?
Use e.target or this keyword to determine which link triggered the event.
$('a.panel').click(function (e) {
//e.target or this will give you the element which triggered this event.
};
$('a.panel').live('keyup', function (evt) {
var e = evt || event;
var code = e.keyCode || e.which;
if (code === 13) { // 13 is the js key code for Enter
$(e.target).trigger('click');
}
});
This will detect a key up event on any a.panel and if it was the enter key will then trigger the click event for the panel element that was focused.
I've got this issue (I'm using jQuery but I'm not restricted to it):
I'm using a combo of Anchor navigation (#id) and Ajax requests. To get the pages to move into place (using anchor navigation) or to fetch information (using Ajax), I use the onhashchange event.
EDIT: I had a little typo. I forgot to check if the mouseDown flag was true and the hashchange event was triggered so I added that if statement.
with jQuery it looks like this: (of course this code is wrapped in a function and initialized on DOM load but it doesn't matter for the question)
$(window).bind('hashchange', function(e) { }
To ensure only browsers supporting the onhashchange reads the code I encapsulate it like this:
if ('onhashchange' in window) {
$(window).bind('hashchange', function(e) { }
}
My web app is made in such way that I only want the onhashchange event to trigger when I hit the back/forward buttons in the browser. To do that I do like this:
if ('onhashchange' in window) {
$(window).bind('mousedown hashchange', function(e) { }
}
Now if I click within the viewport I will trigger the mousedown event. If the mousedown event is triggered I know that I didn't click the browser back/forward buttons and I can stop the onhashchange event using a flag like this:
var mouseDown = false;
if ('onhashchange' in window) {
$(window).bind('mousedown hashchange', function(e) {
if (e.type === 'mousedown') {
mouseDown = true;
}
if (mouseDown && e.type === 'hashchange') {
// if the mousedown event was triggered and when the haschange event triggers,
// we need to stop the hashchange event and restore the mouseDown flag
mouseDown = false;
e.stopPropagation();
}
if (!mouseDown && e.type === 'hashchange') {
// Do the onhashchange stuff here
}
}
}
This causes a problem for IE since it seams you cannot bind mouse events to the window object (?). IE will never "see" the mousedown event.
To solve this IE issue I can take the "clientY" property. This property is passed in all event calls in IE and tells you the coordinates of the mouse. If e.clientY is less then 0, the mouse is outside the viewport and I will know that I triggered the onhashchange by clicking the browser back/forward buttons. It now looks like this:
var mouseDown = false;
if ('onhashchange' in window) {
$(window).bind('mousedown hashchange', function(e) {
// IE: Use e.clientY to check if the mouse position was within the viewport (i.e. not a nagative value for Y)
// !IE: Use e.type
if (e.type === 'mousedown' || e.clientY > 0 ) {
mouseDown = true;
}
if (mouseDown && e.type === 'hashchange') {
// if the mousedown event was triggered and when the haschange event triggers,
// we need to stop the hashchange event and restore the mouseDown flag
mouseDown = false;
e.stopPropagation();
}
if (!mouseDown && e.type === 'hashchange') {
// Do the onhashchange stuff here
}
}
}
This solution was working like a charm until I had to add support for navigating with the arrows on the keyboard. Now it doesn't matter where on the screen the mouse is. As long as the IE window is "active", the keydown event listening for keyboard input triggers when hitting the keyboard. This means that the clientY check does not work anymore as intended.
The Problem:
As far as I know, the onhashchange must be bound to the window object. All events must be processed within the same callback function if I want to be able to control one event by listening for another.
How can I get this to work?
So, simply put-
"how do I distinguish between a back/forward button press vs. navigation coming from interacting with the DOM".
You may want to have a flag such that when you are changing the hash part of the URL from code, you set this flag, ignore the hashchange event, then unset the flag. In which case the event will be ignored ( a kind of reverse solution as to what you're trying to do). You'd obviously want to wrap this in a function.
In general however, applications that use the hashchange event for navigation will often use the hashchange event as a means for changing the state of the application. Therefore, there is only one entry point and you do not need to distinguish between whether the event is generated by browser navigation vs. dom interaction. I'd probably recommend changing your approach.
I'd also point you to the fact that history can be supported across all browsers (even IE6 and IE7 using an iFrame hack). Take a look at the jQuery history plugin
The reference library to achieve this:
http://benalman.com/projects/jquery-bbq-plugin/
I used it and it works great
Think about putting "!" after "#" in the url, so that google can discover the ajax pages
http://code.google.com/web/ajaxcrawling/docs/getting-started.html