ripper234
ripper234

Reputation: 230038

Is there a jQuery selector to get all elements that can get focus?

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.

Upvotes: 35

Views: 25775

Answers (8)

Lee
Lee

Reputation: 13542

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.

Upvotes: 44

Mariusz Charczuk
Mariusz Charczuk

Reputation: 509

var allElementsThatCanBeFocused = $(':focusable');

Upvotes: -1

Adam Leggett
Adam Leggett

Reputation: 4113

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().

Upvotes: 3

RobG
RobG

Reputation: 147383

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?.

Upvotes: -1

tzi
tzi

Reputation: 9459

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')

Upvotes: 7

Xavin
Xavin

Reputation: 66

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/

Upvotes: 1

Dennis
Dennis

Reputation: 32598

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
});

Upvotes: 0

Gus
Gus

Reputation: 7349

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.

Upvotes: 5

Related Questions