Simulating a tab keypress using JavaScript

38,738

Solution 1

The solution I ended up going with is to create a "focus stealer" div (with tabindex = -1--can have the focus but can't be tabbed to initially) on either side of the area in which I want to manually manage the focus. Then I put a bubbling-true event listener for focus and blur on the whole area. When any focus occurs on the area, the tabindex values are changed to -1, and when any blur occurs, they're changed to 0. This means that while focused in the area, you can tab or shift-tab out of it and correctly end up on other page elements or browser UI elements, but as soon as you focus out of there, the focus stealers become tabbable, and on focus they set up the manual area correctly and shunt the focus over to the element at their end, as if you had clicked on one end or the other of the manual area.

Solution 2

This is the solution I used on our webapp for two custom controls, a pop-up calendar and a pop-up unit / value weight selector (clicking the text box pops up a div with two selects)

function tab_focus(elem)
  var fields = elem.form.getElements()
  for(var i=0;i<fields.length;i++) {
    if(fields[i].id == elem.id){
      for(i=i+1;i<fields.length;i++){
        if(fields[i].type != 'hidden'){
          fields[i].focus()
          return    
        }
      }
      break;
    }
  }
  elem.form.focusFirstElement();
}

This is using the Prototype framework and expects an extended element(ie $('thing_id')) as its parameter.

It gets the form the element belongs to, and loops through the elements of the form until it finds itself.

It then looks for the first element after it that is not hidden, and passes it the focus.

If there are no elements after it in the form, it moves the focus back the to first element in the form. I could instead find the next form on the page through document.forms, but most of our pages use a single form.

Solution 3

I created a simple jQuery plugin which does solve this problem. It uses the ':tabbable' selector of jQuery UI to find the next 'tabbable' element and selects it.

Example usage:

// Simulate tab key when element is clicked 
$('.myElement').bind('click', function(event){
    $.tabNext();
    return false;
});

Solution 4

Actually, I guess there is a way, even if it's a major PITA. I can make sure that every element, even if naturally a tab-stop, has an Xtabindex, somehow in the proper order even though I'll be dropping in other people's widgets and so using jQuery to add these after the fact, rather than being able to specify it right in the HTML or other initial building code. Then, my entire form will have a real tabindex. While it has the focus, it will absorb keypresses, and if they're tab or shift+tab, move the fake focus based on Xtabindex. If tab is pressed on the last (or shift+tab on the first) element in the form, it won't gobble the keystroke, thus allowing the browser to properly focus on other page or browser UI elements outside the form using the keyboard.

I can only guess what kinds of unintended side-effects this approach will introduce.

Actually, it's not even a solution, because I still can't fake a tab on the last element using it.

Share:
38,738
Kev
Author by

Kev

I have been programming almost since the cradle. I am mostly based in Europe and remote (or mostly remote) programming jobs for EUR or CHF are my ideal. If you don't mind the time zone difference, I have successfully worked with North American companies from here before (EST and PST, but it depends on your requirements.)

Updated on July 09, 2022

Comments

  • Kev
    Kev almost 2 years

    I'd like to have the browser act as if the user had pressed the Tab key when they click on something. In the click handler I've tried the following approaches:

    var event = document.createEvent('KeyboardEvent');
    event.initKeyEvent("keypress", true, true, null, false, false, false, false, 9, 0);
    this.input.focus()[0].dispatchEvent(event);
    

    And jQuery:

    this.input.focus().trigger({ type : 'keypress', which : 9 });
    

    ...which I took from here.

    The first approach seems to be the best bet, but doesn't quite work. If I change the last two parameters to 98, 98, indeed, a 'b' is typed into the input box. But 9, 0 and 9, 9 (the former of which I took right from the MDC web site) both give me these errors in firebug under FF3:

    Permission denied to get property XULElement.popupOpen
    [Break on this error] this.input.focus()[0].dispatchEvent(event);
    
    Permission denied to get property XULElement.overrideValue
    [Break on this error] this.input.focus()[0].dispatchEvent(event);
    
    Permission denied to get property XULElement.selectedIndex
    [Break on this error] this.input.focus()[0].dispatchEvent(event);
    
    Permission denied to set property XULElement.selectedIndex
    [Break on this error] this.input.focus()[0].dispatchEvent(event);
    

    I've heard such (with no clear definition of 'such') events are 'untrusted', which might explain these errors.

    The second approach causes whatever value I put as event.which to be passed as event.which, but to no effect (even if I use 98 instead of 9, no 'b' is typed in the box.) If I try setting event.data in the object I'm passing, it ends up undefined when the event is triggered. What follows is the code I'm using to view that:

    $('#hi').keypress(function(e) {
      console.log(e);
    });
    

    Any other ideas?

  • Kev
    Kev about 15 years
    lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2008-December/… brought up an interesting point: "But then what if the user agent doesn't do things using a cycle but instead uses directional focus management, like many phones?"
  • Kev
    Kev almost 15 years
    The thing is, some of my focusable UI elements are not form elements, because they are custom widgets.
  • Nico
    Nico almost 15 years
    For jquery users replace the first line of this function with: var fields = $('form')[0]; To loop through the form elements.