How to set the focus on a javascript modal window? - javascript

Our website involves some javascript that produces overlay modal windows.
There is one accessibility problem with this though, once the modal is triggered, the focus is still on the trigger element and not on the modal itself.
These modals can include all sorts of html elements, headings, paragraphs and form controls. What I would like is the focus to begin on the first element within the modal, so most likely to be a h4 tag.
I have explored using the focus() function however this does not work with a number of html elements.
One thought was to add an empty a tag in the window which could gain the focus, but I am unsure about this method.

very late to the party, but the existing answers do not respect accessibility.
The W3C wiki page on accessible modals offers more insight than what's asked in the OP, the relevant part is having tabindex=-1 on the modal container (which should also have an aria-dialog attribute) so it can get :focus.
This is the most accessible way of setting the focus on the modal, there is also more documentation about keeping it in the modal only - and returning it to the element that triggered the modal - quite a lot to be explained here, so I suggest anyone interested to check the link above.

You can append textbox to the beginning of the modal HTML, set focus then hide the textbox. Should have the desired effect as far as I understand your needs.

You could try to blur() the element that has the focus.

to trap focus inside the modal I have used this approach. So the basic idea behind it is exactly to trap the focus in the modal HTML elements and not allowing it to go out of the modal.
// add all the elements inside modal which you want to make focusable
const focusableElements =
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
const modal = document.querySelector('#exampleModal'); // select the modal by it's id
const firstFocusableElement = modal.querySelectorAll(focusableElements)[0]; // get first element to be focused inside modal
const focusableContent = modal.querySelectorAll(focusableElements);
const lastFocusableElement = focusableContent[focusableContent.length - 1]; // get last element to be focused inside modal
document.addEventListener('keydown', function(e) {
let isTabPressed = e.key === 'Tab' || e.keyCode === 9;
if (!isTabPressed) {
return;
}
if (e.shiftKey) { // if shift key pressed for shift + tab combination
if (document.activeElement === firstFocusableElement) {
lastFocusableElement.focus(); // add focus for the last focusable element
e.preventDefault();
}
} else { // if tab key is pressed
if (document.activeElement === lastFocusableElement) { // if focused has reached to last focusable element then focus first focusable element after pressing tab
firstFocusableElement.focus(); // add focus for the first focusable element
e.preventDefault();
}
}
});
firstFocusableElement.focus();
you can find it here trap focus inside the modal

Related

Using element on contentEditable requires two clicks for the first edit

I'm trying to edit data held in a table and choosing to use contentEditable on divs inside the <td>s, and I'm running into a really weird issue. When I go to edit it the first time, I have to click in twice, every other time after that I only have to click in once. But on the first attempt to edit I have to click in twice. Here is an example table row from my HTML:
<tr style="width:100%"> <td><div class="results" onblur="blurMe(event)" onclick="darkenBox(event)" onkeypress="enterKey(event)" >Adam McGurk</div></td><td><div class="results" onblur="blurMe(event)" onkeypress="enterKey(event)" onclick="darkenBox(event)">amcgurk#shinesolar.com</div></td><td class="delete-sales-person"><span class="delete-icon"></span></td></tr>
And here is the appropriate JS:
//Only to be used with changing data to gray on click and border on hover.
function darkenBox(e){
const ele = e.path[0];
ele.setAttribute("contenteditable", true);
console.log("Editing cell data");
ele.classList.add("darkenBox")
}
// Allows user to edit content by click
function clickEdit(e) {
e.path[0].setAttribute("contenteditable", true);
}
//Deleted placeholder text on click
function clearText(e) {
const ele = e.path[0];
ele.setAttribute("contenteditable", true);
ele.innerText='';
}
// Allows user to stop editing by pressing the, "Enter" key.
function enterKey(e){
const keyCode = e.keyCode;
const ele = e.path[0];
if (keyCode === 13) {
ele.classList.remove("darkenBox");
ele.setAttribute("contenteditable", false);
}
}
function blurMe(e) {
const editedElement = e.path[0];
editedElement.classList.remove("darkenBox");
}
Why is it requiring me to click it twice to edit the first time?
The first time you click it, it sets contenteditable to true. Clicking away doesn't unset that. The second time you click, contenteditable is still true. This allows you to activate the element and edit it right away. If you want to edit it on the first click, you can have all elements you intend to edit start with contenteditable enabled. Or, you can focus the element after enabling contenteditable.
ele.setAttribute("contenteditable", true);
ele.focus();
On an unrelated note, this currently doesn't work at all on firefox and I recommend that you use e.target instead of e.path[0].
Your divs start without contenteditable set. When you click on them (the first time) you set contenteditable to true, but the click is already handled so it won’t trigger an edit.
Only on the next click is the browser gonna allow for edit as now contenteditable is true.
Set contenteditable to true directly in the html and should work.

JavaScript - JQuery - focus() method - infinite loop

I'm trying to build a functionality that allows keyboard tabbing between two buttons (CodePen below). More specifically I would like the user to be able to tab onto "button1" and on tab, jump to "button2" and then on tab jump back to button 1.
My solution is to put an event listener on "button1" and listen for a tab keyboard event. When that is triggered, use JQuery's focus() method to shift focus to "button2". On "button2" there is an identical listener that listens for tab event and shift focus back to "button1".
The problem is that when I tab onto "button1", the listener records focus and tab event and shift focus onto "button2" which in turn records focus and tab event and shift it back to "button1" again, creating an infinite loop.
Could I please get suggestions in how to solve this problem?
The real world application of this would be to restrict tabbing within a specific module or section of a page.
Thanks!
Steve
https://codepen.io/steveliu7/pen/WOoMJY
var $button1 = $('.b1');
var $button2 = $('.b2');
var checkButton = function(event) {
if ($button1.is(':focus') && event.which === 9){
console.log($(this))
$('.b2').focus();
return;
};
if ($button2.is(':focus') && event.which === 9){
console.log($(this))
$('.b1').focus();
return;
};
}
$('button').on('keydown', checkButton);
You want to restrict tab navigation between two buttons.
Note that it won't restrict screenreaders navigation to those two buttons.
You have to consider TAB navigation but also SHIFT+TAB navigation
On a purely technical point of view event.preventDefault() is what your are searching for:
var checkButton = function(event) {
if (event.which === 9) {
if ($button1.is(':focus')) {
$button2.focus();
event.preventDefault();
} else if ($button2.is(':focus')){
$button1.focus();
event.preventDefault();
}
}
}
I think what you are trying to do can be achieved much easier with the tabindex property in HTML. If you want to restrict tabbing to certain elements only, you can set tabindex="-1" for those elements that you do not want focused.
Source: https://www.w3schools.com/tags/att_global_tabindex.asp

JavaScript / DOM: how to prevent blur event if focus is lost to another window (application)

I want to show certain content within an HTML document if the user clicks into a certain form field within this document, and I want to hide that certain content if the user leaves that form field (either by activating another one or by clicking somewhere else onto the page).
I have tried to implement that behavior using the focus and blur events. This works in principle, but there is a problem: The blur event on the respective field is fired not only when the user moves the focus within the same document, but as well when another window (which could be from a different application) becomes activated (gets focus).
How could I avoid that? I don't want to see any changes in the page if the focus goes to a different application (or another browser window or tab).
Thank you very much!
Guard the blur event handler with if ( document.activeElement === this ) { return; }.
The next step will be to prevent the focus event handler from activating when the window regains focus. This can be done using a small pattern:
function onFocus(e) {
if ( this._isFocused ) { return; }
this._isFocused = true;
...
}
function onBlur(e) {
if ( document.activeElement === this ) { return; }
this._isFocused = false;
...
}
Maybe this could work:
function onFocus(element) {
document.getElementById('element').doStuffHere('whateverYouWant');
}
function onBlur(element) {
if (document.hasFocus()) {
document.getElementById('element').doStuffHere('whateverYouWant');
} else {
alert('Please come back!')
}
}
The onBlur() function is executed as soon as the element loses focus and first checks if the document still has the focus. If yes it does the elementLostFocus tasks (I'll call them like that here to make it easy), otherwise it (at least in this example) alerts the user, or you can make it do nothing or just the same elementLostFocus tasks or anthing you want.
The only problem with that solution is that you don't do the elementLostFocus tasks when the window regains focus by clicking outside the desired element after it lost the focus directly from the desired element to another window. But here's a fix for that:
document.onfocus = function() {
if (document.getElementById('element').hasFocus() == false) {
document.getElementById('element').doStuffHere('whateverYouWant');
}
}
It can be that that code doesn't work but it should. At least it should give you an idea based on which you can solve the problem yourself.

Detecting when an element is about to get focus

I have an ASP.NET page with a Telerik RadEditor (rich text box). When tabbing through a page, when a user gets to the text box, focus gets set to the various toolbar icons before it goes to the textarea. I added some jQuery to one page to set the focus on the text area when tabbing out of the last cell on a form:
$('input[type=text][id*=tbCost]').keydown(function (e) {
var keyCode = e.keyCode || e.which;
if (keyCode == 9) { //If TAB key was pressed
e.preventDefault();
var editor = $('body').find("<%=RadEditor1.ClientID%>"); //get a reference to RadEditor client object
editor.setFocus(); //set the focus on the the editor
}
});
I am looking for a way to implement this functionality in the control so that it will work regardless of the page it is on. For example, in the above code, focus is only set if the user is tabbing out of the tbCost cell. I would like to be able to set the focus to the text area when a user tabs into the toolbar items.
Is there any way to detect when an element is about to get focus? I know I can see if an element has focus, but I can't think of a way to implement this functionality.
Thanks
Solution:
If anybody has this same question in the future and wants an example, here is the code I used:
$(document).ready(function () {
$('.reToolCell').focusin(function () {
var editor = $('body').find("<%=RadEditor1.ClientID%>");
editor.setFocus();
});
});
You might consider binding to a focus on the toolbar icons and redirecting focus to the text area. Although this might have unintended side effects if users are trying to tab-focus these tools in order to use them.
//on focus eventHandler for all your icons that calls a function
#('.elementtype, class or a generic way of identifying the icons'.onfocus(myFunction(this))
//the function take a parameter of your element, moves to the next sibling element and sets the focus
myFunction = (element) {
element.next().focus();
}

Jump to next input field with arrow keys using jQuery in form

I want to use the arrow keys to navigate between the input text fields in my form (next, previous). I found this simple method to implement it: link to it but for me it doesn't work... I tried it in the HEAD and in the BODY after the FORM as well, but no luck...
I think the problem could be, that my form is send back to the page via AJAX...
I'm not that familiar with jQuery, can someone please help me out here?
This is the jQuery code:
<script>
$(document).ready(function(){
$('input').keyup(function(e){
if(e.which==39)
$(this).closest('td').next().find('input').focus();
else if(e.which==37)
$(this).closest('td').prev().find('input').focus();
else if(e.which==40)
$(this).closest('tr').next().find('td:eq('+$(this).closest('td').index()+')').find('input').focus();
else if(e.which==38)
$(this).closest('tr').prev().find('td:eq('+$(this).closest('td').index()+')').find('input').focus();
});
});
</script>
if your inputs are dynamically created after domready event you should change
$('input').keyup(function(e){
...
into
$('body').on('keyup', 'input', function(e) {
...
doing so the keyup event will be captured on body element using event delegation
For further info see the documentation here: http://api.jquery.com/on/
Event handlers are bound only to the currently selected elements; they must exist on the page at the time your code makes the call to .on(). To ensure the elements are present and can be selected, perform event binding inside a document ready handler for elements that are in the HTML markup on the page. If new HTML is being injected into the page, select the elements and attach event handlers after the new HTML is placed into the page. Or, use delegated events to attach an event handler...
If your script is loaded before the form is on the page then the keyup binding would not be able to bind on load. Try using:
$('input').live('keyup', function(e) { code here });
Just in case you want to bind more than one event, this is how you do it:
$('body').on({
'keyup' : function(e) {
...
},
'keydown' : function(e) {
...
}
}, 'input');
Also 'body' can be swapped with any parent element of 'input' that will not be dynamically added to the page (i.e. it exists on page load). So if you have some div containing each input, you might want to bind that instead.
I've got a slight improvement to the code above. The problem with the code is that you cannot navigate inside an input field. E.g. you have a value of '100.00|' with the cursor currently at the end (indicated with |). If you press the left key it will jump to the prev input field instead of moving the caret by one position to '100.0|0'.
In order to do that you need to check the current caret position with e.target.selectionStart. But you also need the prev caret position as otherwise you can't identify whether the caret went from 1 to 0 (no jump) or the caret was already on 0 and the user pressed left again (jump).
Another change I've added is that only input fields with the class tableInput are considered. In case you want to exclude some fields.
function(e){
var charPos = e.target.selectionStart;
var strLength = e.target.value.length;
var prevPos = $(this).data('prevPos');
if(e.which==39){
//only go right if we really reached the end, that means the prev pos is the same then the current pos
if(charPos==strLength && (prevPos ==null || prevPos == charPos)){
$(this).closest('td').next().find('input.tableInput').focus();
$(this).data('prevPos',null);
}else{
$(this).data('prevPos',charPos);
}
}else if(e.which==37){
//only go left if we really reached the beginning, that means the prev pos is the same then the current pos
if(charPos == 0 && (prevPos ==null || prevPos == charPos)){
$(this).closest('td').prev().find('input.tableInput').focus();
$(this).data('prevPos',null);
}else{
$(this).data('prevPos',charPos);
}
}else if(e.which==40){
$(this).closest('tr').next().find('td:eq('+$(this).closest('td').index()+')').find('input.tableInput').focus();
$(this).data('prevPos',null);
}else if(e.which==38){
$(this).closest('tr').prev().find('td:eq('+$(this).closest('td').index()+')').find('input.tableInput').focus();
$(this).data('prevPos',null);
}
});

Categories

Resources