4

How do I make make an Enter keypress in an <input> element shift focus to the next <input> element on the page?

I have a for loop that creates <li> elements with <input> elements inside. I need to make so that when the user hits enter on their keyboard, the website will focus on the next input field so that the user can enter the next player name without having to toggle between using their mouse and their keyboard.

I thought using the nextSibling property was the solution but it wont work because the <input> elements technically dont have any siblings because each of them is inside/are children of diferent <li> elements.

Here is my JavaScript:

for ( var i = 1 ; i <= numberOfPlayers ; i++ ){
  var inputElement = document.createElement('input');
  var liElement = document.createElement('li');
  inputElement.setAttribute( 'type' , 'text' );
  inputElement.setAttribute ( 'id' , 'name-input-' + i );
  inputElement.setAttribute ( 'class' , 'name-input');
  inputElement.setAttribute ( 'placeholder' , 'Enter a name for player ' + i );
  liElement.appendChild(inputElement);
  nameInputArray[i] = inputElement;
  document.getElementById('name-list').appendChild(liElement);
  inputElement.addEventListener( 'keypress' , function(event){
    if ( event.which === 13 ) {
      alert(this);
      document.getElementById( 'name-input-' + (i+1)).focus();
    }
  } );
}

I tried using the "i" in the for loop and string concatenation to select the ID of the next element but the "i" variable isn't working either because by the time that code runs that "i" is equal to the highest number that it can be after the whole for loop has ran.

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
Osuriel
  • 102
  • 3
  • 12

4 Answers4

0

Problem:

The problem with your actual code is that i is always equal to numberOfPlayers+1 in the event handler callback function, so you were trying to focus on a null element, you can read more about JavaScript closures to see why i was always equal to numberOfPlayers+1.

Solution:

First you need to use the onkeypress event on your input, then test if the pressed key is the Enter, if it's pressed get the id of the current input, extract i value from it and focus on the next input element using the next id.

This is how should be your code:

inputElement.addEventListener('keypress', function(event) {
    if (event.which === 13) {
         var i = parseInt(this.id.charAt(this.id.length-1));
         console.log(i);
         if(i<=numberOfPlayers){
            document.getElementById('name-input-' + (i + 1)).focus();
         }
    }
});

This is a working snippet:

var numberOfPlayers = 5;
var nameInputArray = [];

for (var i = 1; i <= numberOfPlayers; i++) {
  var inputElement = document.createElement('input');
  var liElement = document.createElement('li');
  inputElement.setAttribute('type', 'text');
  inputElement.setAttribute('id', 'name-input-' + i);
  inputElement.setAttribute('class', 'name-input');
  inputElement.setAttribute('placeholder', 'Enter a name for player ' + i);
  liElement.appendChild(inputElement);
  nameInputArray[i] = inputElement;
  document.getElementById('name-list').appendChild(liElement);

  inputElement.addEventListener('keypress', function(event) {
    if (event.which === 13) {
      var i = parseInt(this.id.charAt(this.id.length - 1));
      console.log(i);
      if(i<numberOfPlayers){
         document.getElementById('name-input-' + (i + 1)).focus();
      }
    }
  });
}
<ul id="name-list"></ul>
cнŝdk
  • 31,391
  • 7
  • 56
  • 78
  • This throws an error after the last inputbox. "message": "Uncaught TypeError: Cannot read property 'focus' of null", You need a check against null there. – jrook Apr 07 '17 at 23:31
  • @jrook well pointed out my friend, I edited my answer to add atest for `i`. – cнŝdk Apr 07 '17 at 23:35
  • the error is because the if statment should be "<" not "<=" But thanks anyways. this was the solution to my problem. using the charAt() was the answer. thanks a lot! – Osuriel Apr 12 '17 at 00:10
0

If you want to stick with vanilla JS, use this:

for (var i = 1; i <= numberOfPlayers; i++) {
  var inputElement = document.createElement("input");
  var liElement = document.createElement("li");
  inputElement.type = "text";
  inputElement.id = "name-input-" + i;
  inputElement.className = "name-input";
  inputElement.placeholder = "Enter a name for player " + i;
  liElement.appendChild(inputElement);
  nameInputArray[i] = inputElement;
  document.getElementById("name-list").appendChild(liElement);
  (function(i) {
    inputElement.addEventListener("keypress", function(event) {
      if (event.which === 13) {
        alert(this);
        document.getElementById("name-input-" + (i + 1)).focus();
      }
    });
  })(i);
}
laruiss
  • 3,780
  • 1
  • 18
  • 29
0

This is my solution.

Do not forget that the created <li> element needs to be appended to something like <body>. I have actually added a <ul> element and appended it to the <body> and then appended the <li> elements to the <ul> element.

If you use nextSibling, you do not need to keep elements in nameInputArray. I have not removed it to show how it should be initialized before you can use it in your loop. Also, nextSibling works in my solution since I have put all the <li>s under one <ul> which I think is the correct thing to do anyway.

Other than that, I just corrected a few things here and there. Let me know if you have more questions about this code.

function eventFunc(event) {
  if (event.which === 13) {
    var nextInput = event.target.parentElement.nextSibling.childNodes[0];
    if (nextInput !== null)
      nextInput.focus();
  }
}

var numberOfPlayers = 4;
var nameInputArray = [];

var ulElement = document.createElement('ul');

document.body.append(ulElement);


for (var i = 0; i < numberOfPlayers; i++) {
  var liElement = document.createElement('li');
  ulElement.append(liElement);
  var inputElement = document.createElement('input');

  inputElement.setAttribute('type', 'text');
  inputElement.setAttribute('id', 'name-input-' + i);
  inputElement.setAttribute('class', 'name-input');
  inputElement.setAttribute('placeholder', 'Enter a name for player ' + i);
  inputElement.setAttribute('onkeypress', "eventFunc(event)");
  liElement.appendChild(inputElement);
  nameInputArray[i] = inputElement;
}

UPDATE: Getting each input box from parent <li> elements:

After comment from the OP, I see that they want a structure like this:

<ul>
    <li>input box1</li>
    <li>input box2</li>
    <li>input box3</li>
</ul>

In this structure, each input box is the first child node of its parent <li> element. Therefore, we can still use nextSibling (as the OP intended to use) in this way:

nextInput = event.target.parentElement.nextSibling.childNodes[0];

This line first finds the parent <li> element, applies nextSibling to get the next li element and then gets the input box inside that element.

Community
  • 1
  • 1
jrook
  • 3,459
  • 1
  • 16
  • 33
  • Thanks a lot an I do have a
      as a parent of the
    • elements. in my code, that is what the following line was for: document.getElementById('name-list').appendChild(liElement);
    – Osuriel Apr 11 '17 at 20:13
  • so i guess my question is: is focusing on the next li the same thing as focusing on the next input? – Osuriel Apr 11 '17 at 20:14
  • @Osuriel: I am not sure why you want to do this. But I do not think you can get focus on `
  • ` elements unless [you define a tabIndex for them](http://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus).
  • – jrook Apr 11 '17 at 20:30
  • And if you want to change your question, please consider posting it as a new question (as follow-up for example). Also, please consider accepting one of the answers if they solve the problem so other visitors can benefit from it. – jrook Apr 11 '17 at 20:34
  • @Osuriel: I updated my code. Now each input element is inside its own `
  • `. We can still use `nextSibling`, but we will need to get the parent element first and that is what the *updated* code does.
  • – jrook Apr 11 '17 at 20:45
  • Getting just the ~nextSibiling~ did not work for me but his did: event.target.parentElement.nextElementSibling.childNodes[0]; – rearThing Oct 14 '20 at 22:14