34

This answer tells which HTML elements can receive focus. Is there a jQuery selector that matches exactly these elements?

For now I'm just using $('input,select,textarea,a'), but I wonder if there's something more precise.

Community
  • 1
  • 1
ripper234
  • 222,824
  • 274
  • 634
  • 905
  • Probably not. Perhaps try the [`:input`](http://api.jquery.com/input-selector/) selector? – Bojangles Oct 05 '11 at 22:34
  • What are you trying to do once you have the list of focusable elements? – Dennis Oct 05 '11 at 23:50
  • @Dennis - scroll down to make sure they're visible when they are focused. http://stackoverflow.com/questions/7650892/how-to-scroll-down-the-page-when-a-covered-input-box-is-focused – ripper234 Oct 06 '11 at 00:50

8 Answers8

43

From the other SO answer referred to by the OP:

Today's browsers define focus() on HTMLElement, ...

So, this means testing for focus as a member of the element is not effective, because all elements will have it, regardless of whether they actually accept focus or not.

...but an element won't actually take focus unless it's one of:

  • HTMLAnchorElement/HTMLAreaElement with an href
  • HTMLInputElement/HTMLSelectElement/HTMLTextAreaElement/HTMLButtonElement but not with disabled (IE actually gives you an error if you try), and file uploads have unusual behaviour for security reasons
  • HTMLIFrameElement (though focusing it doesn't do anything useful). Other embedding elements also, maybe, I haven't tested them all.
  • Any element with a tabindex

So, what about naming all those explicitly in a jQuery Selector?

$('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]')

Update #1:

I updated your jsFiddle here. It appears to work.

I also added elements with attribute contenteditable to the list above.


Update #2:

As @jfriend00 pointed out, "Depending upon the use, one may want to filter out elements that aren't visible". To accomplish this, simply apply .filter(':visible') to the set generated from the above selector.


Update #3:

As Xavin pointed out: jQuery UI now has a selector, :focusable, that performs this function. If you're already using jQuery UI, this might be the way to go. If not, then you might want to check out how jQuery UI does it. In any case, the description on jQuery UI's page for :focusable is helpful:

Elements of the following type are focusable if they are not disabled: input, select, textarea, button, and object. Anchors are focusable if they have an href or tabindex attribute. area elements are focusable if they are inside a named map, have an href attribute, and there is a visible image using the map. All other elements are focusable based solely on their tabindex attribute and visibility.

So, the selector I proposed above is close, but it fails to account for a few nuances.

Here's the function ripped from jQuery UI, with minor adaptations to make it self-contained. (the adaptations are untested, but should work):

function focusable( element ) {
    var map, mapName, img,
        nodeName = element.nodeName.toLowerCase(),
        isTabIndexNotNaN = !isNaN( $.attr( element, "tabindex" ) );
    if ( "area" === nodeName ) {
        map = element.parentNode;
        mapName = map.name;
        if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
            return false;
        }
        img = $( "img[usemap=#" + mapName + "]" )[0];
        return !!img && visible( img );
    }
    return ( /input|select|textarea|button|object/.test( nodeName ) ?
        !element.disabled :
        "a" === nodeName ?
            element.href || isTabIndexNotNaN :
            isTabIndexNotNaN) &&
        // the element and all of its ancestors must be visible
        visible( element );

    function visible( element ) {
      return $.expr.filters.visible( element ) &&
        !$( element ).parents().addBack().filter(function() {
          return $.css( this, "visibility" ) === "hidden";
        }).length;
    }
}

Note: the above function still depends on jQuery, but should not require jQuery UI.

jlh
  • 4,349
  • 40
  • 45
Lee
  • 13,462
  • 1
  • 32
  • 45
  • Depending upon the use, one may want to filter out elements that aren't visible. – jfriend00 Oct 05 '11 at 23:16
  • Perhaps there is no better way, but the problem with this type of code is that it's brittle. It will have to be maintained every time there's a new type of editable element or a new attribute that makes something editable. – jfriend00 Oct 05 '11 at 23:17
  • @jfriend00 - good point. I updated my answer to include your suggestion. – Lee Oct 05 '11 at 23:28
  • `input[type="hidden"]` isn't focusable, so one would have to exclude those elements manually if not going the `:visible` route. – Andy E Jul 04 '12 at 12:03
  • 2
    The current implementation in JQUI is nice and self-contained: https://github.com/jquery/jquery-ui/blob/master/ui/focusable.js – Ian Kemp Mar 14 '16 at 13:41
  • 2
    Guys do not forget about `:not([tabindex=-1])`. It is technically focusable, but unreachable with tabs. – daniel.gindi Dec 12 '16 at 09:11
7

Another simple, but complete, jQuery selector could be this one:

$('a[href], area[href], input, select, textarea, button, iframe, object, embed, *[tabindex], *[contenteditable]')
.not('[tabindex=-1], [disabled], :hidden')
tzi
  • 8,719
  • 2
  • 25
  • 45
5

You could check for elements that have the focus() function:

$('*').each(function() {
  if(typeof this.focus == 'function') {
    // Do something with this element
  }
}) ;

Edit Thinking a little more, it would probably makes sense to have *:visible rather than just * as the selector for most applications of this.

Gus
  • 7,289
  • 2
  • 26
  • 23
  • Definitely the way to go. I don't think it's possible as far as explicit selectors go, since jQuery selectors are built on http://sizzlejs.com. – ifightcrime Oct 05 '11 at 22:43
  • except, if you read the post linked in the OP, it says: `the only elements that have a focus() method are HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement and HTMLAnchorElement. This notably omits HTMLButtonElement and HTMLAreaElement.` So testing for `.focus()` won't do it (apparently). – Lee Oct 05 '11 at 22:57
  • DOM elements are host objects, they do not need to conform to the rules of ECMAScript and therefore their response to the *typeof* operator can be anything, including throwing an error. In IE 8, `typeof element.focus` returns *object* on a text input, so the above test will fail in about 1:5 browsers in use, possibly more. – RobG Oct 05 '11 at 22:59
  • 1
    This is a cool idea, but when I test it, it seems to collect every single element on the page in Chrome 14, even divs and spans that are not editable or focusable. See here for test results: http://jsfiddle.net/jfriend00/vHNSX/ – jfriend00 Oct 05 '11 at 23:02
  • @jfriend00 - http://jsfiddle.net/vHNSX/4/ -- It's brute force, but it works. (see my answer below). – Lee Oct 05 '11 at 23:11
  • @jfriend00 - precisely. If no listener has been attached, why would it return *function*? And even if a listener is attached, there is no requirement for the browser to return *function* anyway. ECMAScript rules can't be applied to host objects with any certainty. – RobG Oct 05 '11 at 23:12
  • @lee - it doesn't work in IE 8, it returns no elements. The test would be better as `if ('focus' in this) return this;` so that it works in more browsers. – RobG Oct 05 '11 at 23:18
  • @RobG - according to [the info linked by the OP](http://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus/1600194#1600194), the presence/absence of `focus` is not a good indicator because some browsers put that on all HTMLElements, while the DOM Level2 Spec omits that from some elements that are actually focusable. I can't test in IE8, but it's using standard jquery selectors; so as long as jQuery supports IE8, the core of the answer should hold. I've [revised the jsFiddle](http://jsfiddle.net/vHNSX/6/) to eliminate an unnecessary `map()` left over from the original. – Lee Oct 05 '11 at 23:44
  • @Lee - the selector really has nothing to do with jQuery, that's just the mechanism being used to run the selector. You're probably right that the best way is to just collect the elments that have a *focus* method in the relevant markup standard. At least that has some basis to expect it to work. Incidentally, `'focus' in this` works in IE 8, but I still don't think it's reliable enough. – RobG Oct 05 '11 at 23:57
  • @RobG - no, I'm saying that relying on the focus method **won't** work because that method is present on *all* elements, not just those that will accept focus. / Also, why do you say that the selector has nothing to do with jQuery? Consider that jQuery has a great deal of code that ensures that the various selectors work consistently across all the supported browser platforms. How would you execute that selector without jQuery or some other selector lib? Even in up-level browsers, jQuery applies adjustments to account for differences between various implementations of `querySelectorAll()`. – Lee Oct 06 '11 at 04:34
  • Because jQuery uses CSS (more or less) as its selectors, you could use qSA (in compatible browsers) or any method to get the elements, it's the test for the focus method that is the real issue. The only reasonable method is to get the limited number of elements that HTML 4.01 says have an [onfocus attribute](http://www.w3.org/TR/html401/index/attributes.html). HTML5 adds an *onfocus* property to nearly all elements, so pretty useless. – RobG Oct 06 '11 at 06:20
  • Sorry to disappoint, but this method doesn't work. As @Lee pointed out, the `focus` method will be present on elements that aren't focusable (e.g. ``) because assigning a tabIndex to them makes them focusable elements. – Andy E Jul 04 '12 at 12:01
3

I have a relatively simple solution that returns all tabbable children, in their tab order, without using jQuery.

function tabbable(el) {
    return [].map.call(el.querySelectorAll([
        'input',
        'select',
        'a[href]',
        'textarea',
        'button',
        '[tabindex]'
    ]), function(el, i) { return { el, i } }).
        filter(function(e) {
            return e.el.tabIndex >= 0 && !e.el.disabled && e.el.offsetParent; }).
        sort(function(a,b) {
            return a.el.tabIndex === b.el.tabIndex ? a.i - b.i : (a.el.tabIndex || 9E9) - (b.el.tabIndex || 9E9); });
}

For IE, consider implementing a different visibility check than e.el.offsetParent. jQuery can help you here.

If you don't need the elements sorted, leave out the call to sort().

Adam Leggett
  • 3,714
  • 30
  • 24
1

In jQuery not exists the selector you're finding.

If you're already using jQueryUI, you can use :focusable selector.

http://api.jqueryui.com/focusable-selector/

Xavin
  • 66
  • 4
0

Instead of getting a list of focusable elements, you may want to try setting up a focus handler at the body element that captures focus events.

$(document.body).on("focus", "*", function(e) {
    //Scroll to e.target
});
Dennis
  • 32,200
  • 11
  • 64
  • 79
-1
var allElementsThatCanBeFocused = $(':focusable');
-1

A general test to know whether an element supports a particular type of listener is to see if it has a related property, e.g. to test for support for the focus event, use:

if ('focus' in element) {
  // element supports the focus event
}

However, there are some exceptions. See the answers to How do you programmatically determine to which events an HTML object can listen for?.

jobmo
  • 835
  • 1
  • 9
  • 19
RobG
  • 142,382
  • 31
  • 172
  • 209