42

Using jQuery, what's the best way to find the next form element on the page, starting from an arbitrary element? When I say form element I mean <input>, <select>, <button> or <textarea>.

In the following examples, the element with the id "this" is the arbitrary starting point, and the element with the id "next" is the one I want to find. The same answer should work for all examples.

Example 1:

<ul>
 <li><input type="text" /></li>
 <li><input id="this" type="text" /></li>
</ul>

<ul>
 <li><input id="next" type="text" /></li>
</ul>

<button></button>

Example 2:

<ul>
 <li><input id="this" type="text" /></li>
</ul>

<button id="next"></button>

Example 3:

<input id="this" type="text" />
<input id="next" type="text" />

Example 4:

<div>
  <input id="this" type="text" />
  <input type="hidden" />
  <div>
    <table>
      <tr><td></td><td><input id="next" type="text" /></td></tr>
    </table>
  </div>
  <button></button>
</div>

EDIT: The two answers provided so far both require writing a sequence number to all input elements on the page. As I mentioned in the comments of one of them, this is kind of what I'm already doing and I would much prefer have a read-only solution since this will be happening inside a plugin.

12 Answers12

54

kudos,

What about using .index?

e.g $(':input:eq(' + ($(':input').index(this) + 1) + ')');

Simon
  • 1,849
  • 3
  • 22
  • 29
redsquare
  • 78,161
  • 20
  • 151
  • 159
  • This works great. Thanks to redsquare, bobbytek4 and grault on freenode #jquery for their help. –  Nov 15 '08 at 02:16
  • 8
    Filter out the disabled fields $(":input:not(:disabled):eq(" + ($(":input:not(:disabled)").index(currentElement) + 1) – Sentient Apr 19 '11 at 18:33
  • 5
    I needed to add another set of parens, otherwise it did a string concat on the index. Ended up with: $(":input:eq(" + ($(":input").index(this) + 1) + ")"); – James Hollister May 24 '12 at 23:16
  • 2
    This works well but shouldn't identical DOM traversals be avoided? I stored the list of inputs like var $inputs = $("input, select, etc"); and then reuse that array: var $nextInput = $inputs.eq($inputs.index($myElement) + 1); – profexorgeek Apr 30 '15 at 19:05
  • Answer should accommodate Sentient's warning above, but should also include ":visible" because otherwise the list will include hidden-type input fields. – Blaine Oct 03 '16 at 16:37
14

redsquare is absolutely right, and a great solution also, which I also used in one of my project.

I just wanted to point out that he is missing some parentheses, since the current solution concatenates the index with 1, instead of adding them together.

So the corrected solution would look like:

$(":input:eq(" + ($(":input").index(this) + 1) + ")");

Sorry about the double-post, but I couldn't find a way to comment his post...

reSPAWNed
  • 1,089
  • 14
  • 21
11

This solution does not require indexes, and also plays nicely with tabindex - in other words, it gives you the exact element that the browser would give you on tab, every time, without any extra work.

function nextOnTabIndex(element) {
      var fields = $($('form')
                    .find('a[href], button, input, select, textarea')
                    .filter(':visible').filter('a, :enabled')
                    .toArray()
                    .sort(function(a, b) {
                      return ((a.tabIndex > 0) ? a.tabIndex : 1000) - ((b.tabIndex > 0) ? b.tabIndex : 1000);
                    }));


      return fields.eq((fields.index(element) + 1) % fields.length);
    }

It works by grabbing all tabbable fields in the form (as allowed by http://www.w3.org/TR/html5/editing.html#focus-management), and then sorting the fields based on (http://www.w3.org/TR/html5/editing.html#sequential-focus-navigation-and-the-tabindex-attribute) to work out the next element to tab to. Once it has that, it looks at where the passed in field is in that array, and returns the next element.

A few things to note:

  • jQuery appears to support sort() on a jQuery object, but I can't find it explicitly in the documentation, hence calling toArray() and then rewrapping the array in a jQuery object.
  • There are other fields that it is okay to tab to, but I left them out as they aren't standard form fields.

The code I used to test this was (using jQuery 1.7):

<script>
  $(function() {
    $('a[href], button, input, select, textarea').click(function() {
      console.log(nextOnTabIndex($(this)).attr('name'));
    })
  });
</script>

<form>
  <input type='text' name='a'/>
  <input type='text' name='b' tabindex='1' />
  <a>Hello</a>
  <input type='text' name='c'/>
  <textarea name='d' tabindex='2'></textarea>
  <input id='submit' type='submit' name='e' tabindex='1' />
</form>
Jai
  • 897
  • 9
  • 15
  • 1
    This is almost perfect. The second filter() should be filter('a, :enabled') otherwise the selection of the 'a[href]' tags will be removed from the array. Other than that minor bug, this code is pretty nifty. – CubicleSoft Dec 19 '14 at 04:34
8

After trying every code I could find (and having issues between browsers), I found one that works in the top browsers. Couldn't use the previous routines because of some weird issues.

$(document.body).keydown(function(event) {
    if(event.keyCode == 13 ) {
        $(":input")[$(":input").index(document.activeElement) + 1].focus();
        return false;
    }
});

Hope this helps someone else. Enjoy.

Jeff Mathews
  • 81
  • 1
  • 1
3

You can do this to take a complete list of the form elements you are looking for:

var yourFormFields = $("yourForm").find('button,input,textarea,select');

Then, should be easy find the next element:

var index = yourFormFields.index( this ); // the index of your current element in the list. if the current element is not in the list, index = -1

if ( index > -1 && ( index + 1 ) < yourFormFields.length ) { 

                    var nextElement = yourFormFields.eq( index + 1 );

                    }
alexmeia
  • 5,241
  • 4
  • 24
  • 24
2

You could give each form item an id (or unique class name) that identified it as a form element and also gave it an index. For example:

<div>
    <input id="FormElement_0" type="text" />
    <input id="FormElement_1" type="text" />
<div>

Then, if you want to traverse from the first element to the second you can do something like this:

//I'm assuming "this" is referring to the first input

//grab the id
var id = $(this).attr('id');

//get the index from the id and increment it
var index = parseInt(id.split('_')[0], 10);
index++;

//grab the element witht that index
var next = $('#FormElement_' + index);

The benefit of this is that you can tag any element to be next, regardless of location or type. You can also control the order of your traversal. So, if for any reason you want to skip an element and come back to it later, you can do that too.

jckeyes
  • 620
  • 1
  • 7
  • 20
  • Thanks for the reply. This is unfortunately exactly what I'm trying to get rid of :) I'm moving my code into a plugin and I'd rather it was usable without clobbering the user's own choice of id's. The unique class name is interesting but I'd prefer a more "read-only" solution if possible. –  Nov 14 '08 at 16:28
  • Understandable. I realize the solution I provided was a bit "dirty." Makes a lot less sense from a plugin standpoint. – jckeyes Nov 14 '08 at 17:07
1

Or you could use the html attribute 'tabindex' which is for when a user tabs around a form, it goes to tabindex="i" to tabindex="i+1". You can use jQuery to get the attribute very easily. Would make for a nice fall back to users without javascript enabled, also.

Chris Serra
  • 13,226
  • 3
  • 25
  • 25
1

I came up with a function that does the job without explicitly defining indexes:

function nextInput(form, id) {
    var aInputs = $('#' + form).find(':input[type!=hidden]');
    for (var i in aInputs) {
        if ($(aInputs[i]).attr('id') == id) {
            if (typeof(aInputs[parseInt(i) + 1]) != 'undefined') {
                return aInputs[parseInt(i) + 1];
            }
        }
    }
}

And here's a working example. The form tags are for consistency. All you really need is a common parent and could even just use the body tag as the parent (with a slight modification to the function).

Paste this into a file and open with firefox / firebug and you'll see it returns the correct element for all your examples:

<html>
  <head>
    <script src="http://www.google.com/jsapi"></script>
    <script>
      function nextInput(form, id) {
        var aInputs = $('#' + form).find(':input[type!=hidden]');
        for (var i in aInputs) {
          if ($(aInputs[i]).attr('id') == id) {
            if (typeof(aInputs[parseInt(i) + 1]) != 'undefined') {
              return aInputs[parseInt(i) + 1];
            }
          }
        }
      }

      google.load("jquery", "1.2.6");
      google.setOnLoadCallback(function() {
        console.log(nextInput('myform1', 'this1'));
        console.log(nextInput('myform2', 'this2'));
        console.log(nextInput('myform3', 'this3'));
        console.log(nextInput('myform4', 'this4'));
      });
    </script>
  </head>
  <body>
    <form id="myform1">
      <ul>
         <li><input type="text" /></li>
         <li><input id="this1" type="text" /></li>
      </ul>
      <ul>
        <li><input id="next1" type="text" /></li>
      </ul>
    </form>

    <form id="myform2">
      <ul>
         <li><input type="text" /></li>
         <li><input id="this2" type="text" /></li>
      </ul>
      <ul>
        <li><input id="next2" type="text" /></li>
      </ul>
    </form>

    <form id="myform3">
      <input id="this3" type="text" />
      <input id="next3" type="text" />
    </form>

    <form id="myform4">
      <div>
        <input id="this4" type="text" />
        <input type="hidden" />
        <div>
        <table>
          <tr><td></td><td><input id="next4" type="text" /></td></tr>
        </table>
        </div>
        <button></button>
      </div>
    </form>
  </body>
</html>
enobrev
  • 22,314
  • 7
  • 42
  • 53
0
var elementSelector = "input:visible,textarea:visible";
var nextSibling = $(elementSelector )[$(elementSelector ).index() + 1];
//$(nextSibling).focus(); possible action

I just think above solution is simpler, or you can just add it all in one line if you want :-)

var nextSibling = $("input:visible,textarea:visible")[$("input:visible,textarea:visible").index() + 1];
NicoJuicy
  • 3,435
  • 4
  • 40
  • 66
0

You can use jQuery field plugin which allows you to do that.

Anton Kuzmin
  • 821
  • 1
  • 10
  • 26
0

This worked well for me, and it correctly skips over hidden inputs:

input_el.nextAll( 'input:visible:first' ).focus();
Jay Haase
  • 1,979
  • 1
  • 16
  • 36
0

All solutions using index (or nextAll) will only work where all the form inputs are siblings, e.g. within the same <div> block. The following gets round that by creating an array of ids of all visible, non-readonly inputs on the page and picks out the first one after the current control, wrapping round if the current control is the last one on the page.

ids = $(":input:visible:not([readonly])").map(function () { return this.id });
nextId = ids[($.inArray($(this).attr("id"), ids) + 1) % ids.length];
$("#" + nextId).focus();

Using the map function makes it a little more succinct than solutions involving iterators.

PiersB
  • 151
  • 2
  • 3
  • It doesn't look like he specified any conditions (e.g., *readonly*, *visible*). Otherwise you could also look at the element's *tabIndex* property and navigate that way. – vol7ron Jun 25 '14 at 13:39