When a user tabs through form input boxes it goes in order. I figured out that you could order them though when using tabindex=x. I have 5 inputs on my form, so I have tabindex 5 different times. After that it goes to different items on the page, so is there a way to make it just loop through those 5 items? By the way I can not use tabindex=0 because then I would have to add that to 100s of items. So basically my question is how can I make a so called "tab loop".
You can't declaratively set up a tab loop. That feature is designed to work within the normal tabbing behavior of the browser, which is to visit all tab-able elements in both the page and the browser chrome.
If you want to prevent tabbing away from a chosen subset of elements, you'll need a little JavaScript and you better know very well why you're doing it and what it'll break, give some visual cues that the form is focused to the detriment of the rest of the UI, and think of some non-visual clients for whom keyboard navigation is even more important.
Assuming that you have indeed knowingly judged that it's OK to hijack tabbing, one relatively safe way to do this is to create a dummy tabbable element (has to be displayed, but could be practically invisible) with a tabIndex that makes it the next after the last item in your form, and another right before the first in your form.
dummyItemBeforeForm.addEventListener('focus', function(e){
lastItemOfMySuperImportantForm.focus() }, true )
dummyItemAfterForm.addEventListener('focus', function(e){
firstItemOfMySuperImportantForm.focus() }, true )
That will make the focus loop back to the beginning of the form when tabbing away from the last item, and loop to the end when shift-tabbing from the first.
Make sure that the dummy items are disabled by default and only become focusable once the user focuses your form and gets the visual cues that the form is now kind of modal, and disable the dummies again once the user is done with the form.
And please, please, test this with real users, and see if they like it or if they panic because you broke their expected tab behavior.
I found an easy way to achieve this using jQueryUI. I created a .tabloop class and I'm using the following snippet.
The :tabbable selector is not natively included in jQuery, it's part of jQueryUI but you can easily make your own.
// Focus loop inside element with class tabloop
$(function() {
$(document).on('keydown', '.tabloop :tabbable:not([readonly])', function(e) {
// Tab key only (code 9)
if (e.keyCode != 9)
return;
// Get the loop element
var loop = $(this).closest('.tabloop');
// Get the first and last tabbable element
var firstTabbable = loop.find(':tabbable:not([readonly])').first();
var lastTabbable = loop.find(':tabbable:not([readonly])').last();
// Leaving the first element with Tab : focus the last one
if (firstTabbable.is(e.target) && e.shiftKey == true) {
e.preventDefault();
lastTabbable.focus();
}
// Leaving the last element with Tab : focus the first one
if (lastTabbable.is(e.target) && e.shiftKey == false) {
e.preventDefault();
firstTabbable.focus();
}
});
});
.tabloop {
border: 1px red solid;
padding: 1ch;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="//code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<p>
<label for="text1">Field 1:</label>
<input type="text" id="text1">
</p>
<p>
<label for="text2">Field 2:</label>
<input type="text" id="text2">
</p>
<p>
Link
</p>
<p>
<button type="button">Button</button>
</p>
<div class="tabloop">
<p>
<label for="text3">Field 3:</label>
<input type="text" id="text3">
</p>
<p>
<label for="text4">Field 4:</label>
<input type="text" id="text4">
</p>
<p>
Link
</p>
<p>
<button type="button">Button</button>
</p>
</div>
As a follow up to #NicolasBernier JqueryUI answer, this is the code that worked w/o the ':tabbable' keyword.
// first block deals with first tabbable element
$('uniqueContainerOfElementInQuestion').find('firstTabbableElement(ex."a")').first().on('keydown', function (e) {
// first ele w/ shift and tab
if (e.shiftKey == true && e.keyCode == 9) {
console.log('Shift tab on first')
e.preventDefault();
// focus on element
// if it isn't working uncomment out timeout as possible test/quickfix
// setTimeout(()=>{
$('uniqueContainerOfElementInQuestion').find('lastTabbableElement(ex."button")').last().focus();
// })
}
});
$('uniqueContainerOfElementInQuestion').find('lastTabbableElement(ex."button")').last().on('keydown', function (e) {
// Leaving the last element with Tab : focus the first one
if (e.shiftKey == false && e.keyCode == 9) {
console.log('tab on last')
e.preventDefault();
// setTimeout(()=>{
$('uniqueContainerOfElementInQuestion').find('firstTabbableElement(ex."a")').first().focus();
// })
}
});
Related
How to prevent keydown event when input is focused?
I have a function that assigns some action to keyboard keys (mainly player control), but when using <input> all assigned keys trigger that action as well. Therefore it is impossible to use <input> properly when typing.
function keyboardShortcuts(e){
//Do some stuff when keyboard keys are pressed
switch(e.code){
case 'KeyJ':
console.log('KeyJ');
break;
//...
}
}
document.addEventListener('keydown',keyboardShortcuts);
<input id="search" placeholder="search...">
How to fix it? Should I check if input is not focused?
function keyboardShortcuts(e){
if(document.getElementById('search') !== document.activeElement){
switch(e.code){
//...
}
}
}
Or maybe there is some method similar to e.preventDefault()?
document.getElementById('search').addEventListener('focus',function(){
//... prevent keyboardShortcuts frum running ?????
});
Answer:
Since you're applying the keydown event handler to the entire document, the best way to handle this is to diffuse the function based upon what element initiated the event. You can find this out by using event.target
Combining this with HTMLElement.prototype.matches can allow you to avoid elements that match a selector, for instance any input elements.
if(e.target.matches("input")) return;
Example:
function keyboardShortcuts(e){
//Do some stuff when keyboard keys are pressed
if(e.target.matches("input")) return;
switch(e.code){
case 'KeyJ':
console.log('KeyJ');
break;
//...
}
}
document.addEventListener('keydown',keyboardShortcuts);
<h3> Will Escape Custom KeyDown </h3>
<input id="search" placeholder="search...">
<hr />
<h3> Will Trigger Custom KeyDown </h3>
<textarea></textarea>
<hr/>
<small> Pressing <kbd>J</kbd> will fire <highlight>console.log</highlight> from Custom KeyDown Event</small>
You'll notice, in the above, input doesn't fire a console log when pressing j, but textarea still does.
Adding a Class:
I would suggest adding a specific class to your markup that is used for avoiding the global keydown handler. For instance something like defaultKeys
Example:
function keyboardShortcuts(e){
//Do some stuff when keyboard keys are pressed
if(e.target.matches(".defaultKeys")) return;
switch(e.code){
case 'KeyJ':
console.log('KeyJ');
break;
//...
}
}
document.addEventListener('keydown',keyboardShortcuts);
<h3> Defaulted KeyDown Events </h3>
<hr/>
<input id="search" class="defaultKeys" placeholder="search...">
<br>
<textarea class="defaultKeys"></textarea>
<hr/>
<h3> Custom KeyDown Events </h3>
<input />
<br>
<textarea></textarea>
<hr/>
<small> Pressing <kbd>J</kbd> will fire <highlight>console.log</highlight> from Custom KeyDown Event</small>
Avoiding Groups of Elements:
It may be beneficial to avoid entire containers of elements - a.e. if you have a submission form or a player chat, etc.
You can utilize the same basic principles, but because currently selectors cannot look up the tree( they can't say am I a great-grandchild of an element that does match .defaultKeys ? ) we have to recursively check up the tree.
This is done with a simple do...while loop that continues until it finds a match for the specified selector OR it hits the body tag.
The second out( the body tag check ) is normally not necessary as parentElement should only be able to go up to the HTML element, however, when dealing with IFrames, DOMParser, and, in the future, Realms - you may have multiple DOMs injected into one another, and therefore, multiple body tags.
Basically, we simply want to give a for-sure recursive out when it reaches the top of the current DOM.
function keyboardShortcuts(e) {
// get init element and set default flag
let element = e.target,
useDefault = false;
// if this element has defaultKeys class
// OR if any parent element has defaultKeys class
// set default flag to true
do {
if (element.matches(".defaultKeys")) {
useDefault = true;
break;
}
if ( element.tagName === "BODY" ) break;
} while ((element = element.parentElement));
// if flag is true, escape function
if (useDefault) return;
// check for custom key events
switch (e.code) {
case "KeyJ":
console.log("KeyJ");
}
}
document.addEventListener("keydown", keyboardShortcuts);
<div id="group" class="defaultKeys">
<h3> These elements are in the same parent div </h3>
<input />
<br/>
<textarea></textarea>
</div>
<h3> Outside of Group </h3>
<textarea></textarea>
Conclusion
Hope this helps! Happy coding!
I've got a problem: I'm building an in-page multistage form. Every 'stage' is on its own panel (a div). I'll move through panels via javascript (next/prev buttons). Every panel has its own validation, and it's not possibile to jump to the next panel if the current is not succefully validated. All fine.
The problem is when the user starts using tabs to jump from a field to another. When he reaches the last field of the current panel, the tab will bring him to the next panel.
So what I would need is to avoid the last field of each form to accept tab events, or, the first field of each form to be reachable from a tab. Or the like.
In other words: how disable tab key only for certain fields?
Or, how to limit tab action only to a certain group of fields in a form?
I've tried a couple of ways, no luck:
// on last panel field
$('.block-tab').on('keypress', function(e) {
if (e.keyCode == 9) e.preventDefault();
});
// on first panel field
$('.block-tab').on('keyup', function(e) {
if (e.keyCode == 9) e.preventDefault();
});
As suggested by #Teemu, onkeydown will work. So basically, on the last field of each panel/tab I'll assign a class such as 'block-tab'.
<input class="block-tab" name="email" id="email" type="text" value="" />
Then:
$('.block-tab').on('keydown', function(e) {
if (e.keyCode == 9) e.preventDefault();
});
This will prevend the user to access the next panel without clicking the 'next' button and thus validating the current panel first.
Imagine I have a div A, which is absolute positioned. Inside this div A, there may be several input controls. I want all of these controls to be inaccessible through click events. I also want to prevent them to be accessible through tab, that is, gaining focus. I want them to be like dead, but not visually disabled.
What is the best way to achieve that?
You can prevent mouse actions with pointer-events: none:
To prevent the contents from being focusable, you can do some of these:
Set a negative tabindex to each element which has a tabindex focus flag set by default
Technically the element will still be focusable, but it won't be reached using sequential focus navigation.
Make them inert
However, I don't know any way do do this except with a modal dialog, which makes the entire document inert.
Disable each element
Note not all elements can be disabled
Use JS to undo focus.
Note the focus (nor focusin) event is not cancelable, but you can undo it with .blur().
Since focus doesn't bubble, if you want event delegation you must listen to capture phase
element.addEventListener('focus', function(event) {
event.target.blur();
}, true);
Example:
document.querySelector('div').addEventListener('focus', function(e) {
e.target.blur();
}, true);
div {
pointer-events: none;
}
<div>
<button type="button">I am a button</button>
<input type="button" value="I am an input button" />
<input type="text" value="I am a text button" />
I am an anchor
</div>
There are a couple of ways to do it. One is to set tabindex of all the elements to -1. The other is to not worry about tabindex and just block tab if you are not in the modal.
This is the basic idea, not a full solution for allowing tabs. This allows you to see how to detect if you are in the modal to allow for tab and cancels it when you are not in the modal.
You would need to add more logic to the code to see if you are in the last textbox on the modal or if you are on the first with shift tab.
(function () {
var allow = false,
isModalActive = true;
document.getElementById("overlay").addEventListener("keydown", function(evt){
var keyCode = evt.keyCode || evt.which;
if (keyCode === 9) {
allow=true;
}
//TODO FOR YOU determine if the current element is on the last textbox and than you would need to loop to the first element in the textbox. Also need to detect if it is shift tab for the first element...
});
document.body.addEventListener("keydown", function(evt){
var keyCode = evt.keyCode || evt.which;
if (isModalActive && keyCode === 9 && !allow) {
evt.preventDefault();
}
allow=false;
});
}());
#overlay{
border: 1px solid black;
background-color: #CCC;
padding: 2em;
}
<input type="text">
<div id="overlay">
<input type="text">
<input type="text">
<input type="text">
</div>
<input type="text">
I have an onblur event that will cause an error window to popup if incorrect data is input. The problem is, when I try to close the webpage, or click any other window, the onblur event will activate and the popup will come up again... Anyone have an idea how to fix this?
Here is a simplified version of the code
<input id="M3_pos_tab" type="text" maxlength="5" name="Blank" size="5" onblur="CheckFS();"/>
<script type="text/javascript">
function CheckFS()
{
var FS_pos = parseFloat(FS_tab.value);
if ((FS_pos == 0) || (FS_pos == parseFloat(L_tab.value)) || (isNaN(FS_pos))) {
} else {
window.alert("Error");
FS_tab.select();
FS_tab.focus();
return;
}
}
I think your problem is that when the error box appears, it takes the focus and therefore it triggers onblur at the element. I found this out at https://javascript.info/focus-blur, which says
A focus loss can occur for many reasons. One of them is when the visitor clicks somewhere else. But also JavaScript itself may cause it, for instance:
An alert moves focus to itself, so it causes the focus loss at the element (blur event), and when the alert is dismissed, the focus comes back (focus event).
I therefore suggest removing the line
window.alert("Error");
and to see if the problems persists.
If you are using a form you can utilize onsubmit.
<form action="onsubmit.htm" onsubmit="return CheckInput();">
<!-- input -->
<input type="submit" value="send">
</form>
<script type="text/javascript">
function CheckInput () {
// things you want to check
</script>
If CheckInput() returns true the form will be submitted, if it returns false the form will not be sumbitted.
I'm not sure how foolproof this is, but you could check the active element on blur. At least in Chrome, the onblur event will still fire when you click on a different window or tab, but the active element will still be the input, and not something else on the page. Kinda hacky solution, but it might work for you:
Sample fiddle
function CheckFS() {
if (document.activeElement == FS_tab) {
// if the input is still the active element,
// then we haven't focused anything else on the page,
// so we must have focused something else off of the page
return;
}
var FS_pos = parseFloat(FS_tab.value);
if ((FS_pos == 0) || (FS_pos == parseFloat(L_tab.value)) || (isNaN(FS_pos))) {
} else {
window.alert("Error");
FS_tab.select();
FS_tab.focus();
return;
}
}
I have several divs within the same form. What I am trying to do is to disable the Tab key in one of the divs in the form without disabling the tab in the other divs in the same form.
Example Form:
div1 - disable Tab
div2 - Tab works
div3 - Tab works
A simple way is to put tabindex="-1" in the field(s) you don't want to be tabbed to.
Eg
<input type="text" tabindex="-1" name="f1">
Similar to Yipio, I added notab="notab" as an attribute to any element I wanted to disable the tab too. My jQuery is then one line.
$('input[notab=notab]').on('keydown', function(e){ if (e.keyCode == 9) e.preventDefault() });
Btw, keypress doesn't work for many control keys.
Building on Terry's simple answer I made this into a basic jQuery function
$.prototype.disableTab = function() {
this.each(function() {
$(this).attr('tabindex', '-1');
});
};
$('.unfocusable-element, .another-unfocusable-element').disableTab();
My case may not be typical but what I wanted to do was to have certain columns in a TABLE completely "inert": impossible to tab into them, and impossible to select anything in them. I had found class "unselectable" from other SO answers:
.unselectable {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
This actually prevents the user using the mouse to put the focus in the TD ... but I couldn't find a way on SO to prevent tabbing into cells. The TDs in my TABLE actually each has a DIV as their sole child, and using console.log I found that in fact the DIVs would get the focus (without the focus first being obtained by the TDs).
My solution involves keeping track of the "previously focused" element (anywhere on the page):
window.currFocus = document;
//Catch any bubbling focusin events (focus does not bubble)
$(window).on('focusin', function () {
window.prevFocus = window.currFocus;
window.currFocus = document.activeElement;
});
I can't really see how you'd get by without a mechanism of this kind... jolly useful for all sorts of purposes ... and of course it'd be simple to transform it into a stack of recently focused elements, if you wanted that...
The simplest answer is then just to do this (to the sole DIV child in every newly created TD):
...
jqNewCellDiv[ 0 ].classList.add( 'unselectable' );
jqNewCellDiv.focus( function() {
window.prevFocus.focus();
});
So far so good. It should be clear that this would work if you just have a TD (with no DIV child).
Slight issue: this just stops tabbing dead in its tracks. Clearly if the table has any more cells on that row or rows below the most obvious action you'd want is to making tabbing tab to the next non-unselectable cell ... either on the same row or, if there are other rows, on the row below. If it's the very end of the table it gets a bit more tricky: i.e. where should tabbing go then. But all good clean fun.
You have to disable or enable the individual elements. This is how I did it:
$(':input').keydown(function(e){
var allowTab = true;
var id = $(this).attr('name');
// insert your form fields here -- (:'') is required after
var inputArr = {username:'', email:'', password:'', address:''}
// allow or disable the fields in inputArr by changing true / false
if(id in inputArr) allowTab = false;
if(e.keyCode==9 && allowTab==false) e.preventDefault();
});
If you're dealing with an input element, I found it useful to set the pointer focus to back itself.
$('input').on('keydown', function(e) {
if (e.keyCode == 9) {
$(this).focus();
e.preventDefault();
}
});
$('.tabDisable').on('keydown', function(e)
{
if (e.keyCode == 9)
{
e.preventDefault();
}
});
Put .tabDisable to all tab disable DIVs
Like
<div class='tabDisable'>First Div</div> <!-- Tab Disable Div -->
<div >Second Div</div> <!-- No Tab Disable Div -->
<div class='tabDisable'>Third Div</div> <!-- Tab Disable Div -->