1

basically i am trying to get the web page to navigate between fields with arrow keys. Ive got most if it down, just that the variable I has to be increased or decreased by 1 to get the index of the array of elements accessed by class, as such:

var isFocus;

var I = 0;

const prev = document.getElementsByClassName('prev');
const curr = document.getElementsByClassName('curr');

prev[0].focus()

var isFocused1;
var isFocused2;

document.addEventListener('keydown', fX)

function fX(event) {
  key = event.key

  isFocused1 = document.activeElement.classList.contains("prev");
  isFocused2 = document.activeElement.classList.contains("curr");

  if (isFocused1 == true) {
    isFocus = prev
  } else if (isFocused2 == true) {
    isFocus = curr
  }

  if (key == 'ArrowDown' && I < 5) {
    I++;
    isFocus[I].focus();
  } else if (key == 'ArrowUp' && I > 0) {
    I--;
    isFocus[I].focus();
  }
}
<p> try selecting first column's top input box, click down 3 times to get to the fourth one, then click the second column's first input box. Now click down once and you'll see the cursor skip to the fifth box. </p>
<table>
  <tr>
    <td><input type="text" name="" class="prev"></td>
    <td><input type="text" name="" class="curr"></td>
    <td>
      <p class="mtr-Result"></p>
    </td>
  </tr>

  <tr>
    <td><input type="text" name="" class="prev"></td>
    <td><input type="text" name="" class="curr"></td>
    <td>
      <p class="mtr-Result"></p>
    </td>
  </tr>

  <tr>
    <td><input type="text" name="" class="prev"></td>
    <td><input type="text" name="" class="curr"></td>
    <td>
      <p class="mtr-Result"></p>
    </td>
  </tr>

  <tr>
    <td><input type="text" name="" class="prev"></td>
    <td><input type="text" name="" class="curr"></td>
    <td>
      <p class="mtr-Result"></p>
    </td>
  </tr>
  <tr>
    <td><input type="text" name="" class="prev"></td>
    <td><input type="text" name="" class="curr"></td>
    <td>
      <p class="mtr-Result"></p>
    </td>
  </tr>
  <tr>
    <td><input type="text" name="" class="prev"></td>
    <td><input type="text" name="" class="curr"></td>
    <td>
      <p class="mtr-Result"></p>
    </td>
  </tr>
</table>

https://jsfiddle.net/nw259to6/1/

The cursor will move down the fields, however upon switching to the other column input class = 'curr' , I's value keeps on iterating from whatever value it left of from. Meaning the next field to be active would be curr[I], clicking on the first input text box, then pressing down, makes the cursor skip a few input boxes.

Id like that every time i switch fields I goes back to 0 so that i can scroll from top to bottom. THX!

InSync
  • 4,851
  • 4
  • 8
  • 30
shuhei m
  • 67
  • 5

2 Answers2

1

That's because you're using the same variable for both types of <input>s. Also, you need to reset the index when an element is focused.

['prev', 'curr'].forEach(selector => {
  // selector is either 'prev' or 'curr'

  // Selects all inputs with that class
  const inputs = [...document.querySelectorAll(`.${selector}`)];
  let index = 0;
  
  inputs[index].focus();
  
  // On keydown, we move focus to another <input> if possible.
  function onkeydown(event) {
    const key = event.key;
  
    if (key == 'ArrowDown' && index < 5) {
      index++;
    } else if (key == 'ArrowUp' && index > 0) {
      index--;
    }
    
    inputs[index].focus();
  }
  
  // On focus, we find the index of the target in the original list
  // which always consists of <input>s with the same class
  function onfocus(event) {
    index = inputs.indexOf(event.target);
  }
  
  // Iterate over the list, add event listeners for those <input>s
  inputs.forEach(input => {
    input.addEventListener('keydown', onkeydown);
    input.addEventListener('focus', onfocus);
  });
});
<p>Try selecting first column's top input box, click down 3 times to get to the fourth one, then click the second column's first input box.</p>
<table>
  <tr>
    <td><input type="text" name="" class="prev"></td>
    <td><input type="text" name="" class="curr"></td>
    <td>
      <p class="mtr-Result"></p>
    </td>
  </tr>

  <tr>
    <td><input type="text" name="" class="prev"></td>
    <td><input type="text" name="" class="curr"></td>
    <td>
      <p class="mtr-Result"></p>
    </td>
  </tr>

  <tr>
    <td><input type="text" name="" class="prev"></td>
    <td><input type="text" name="" class="curr"></td>
    <td>
      <p class="mtr-Result"></p>
    </td>
  </tr>

  <tr>
    <td><input type="text" name="" class="prev"></td>
    <td><input type="text" name="" class="curr"></td>
    <td>
      <p class="mtr-Result"></p>
    </td>
  </tr>
  <tr>
    <td><input type="text" name="" class="prev"></td>
    <td><input type="text" name="" class="curr"></td>
    <td>
      <p class="mtr-Result"></p>
    </td>
  </tr>
  <tr>
    <td><input type="text" name="" class="prev"></td>
    <td><input type="text" name="" class="curr"></td>
    <td>
      <p class="mtr-Result"></p>
    </td>
  </tr>
</table>

The above will not work if you were to override ArrowRight and ArrowLeft's behaviour. In such a case, you will need a 2D coordinate.

InSync
  • 4,851
  • 4
  • 8
  • 30
  • I went over your code, and id like to see if ive got what each statement does. The for each loop runs on both classes. The ```const input``` declaration creates the array with all the input boxes. the for each loop at the bottom adds event listeners to every input box, that fires either the onkeydown or onfocus function depending on the event, hence updating index. Would it be safe to assume that the second 2 statements (```let index``` & ```inputs.focus()```) would be fine out of the for each loop? – shuhei m May 30 '23 at 20:12
  • sorry meant the ```let index = 0``` only, you need the ```inputs.focus``` in the loop lol. – shuhei m May 30 '23 at 20:19
  • 1
    @shuheim No, because each class needs their own `index`; using the same variable for both just doesn't work. I'm basically creating 2 `index`es; it's just that each of them is inside a different iteration of the `forEach` loop. Each iteration keeps its own [*closure*](https://stackoverflow.com/q/111102), which nothing else can interact with. – InSync May 30 '23 at 21:33
  • If i could give you 2 upvotes my man, still wouldn't be enough. this gives me room for more classes. ***** – shuhei m May 30 '23 at 22:07
1

You can make the navigation agnostic - independent of row and column count in the keydown handler:

const inputCols = document.querySelector("input#colCount");
const inputRows = document.querySelector("input#rowCount");
inputCols.addEventListener("input", buildTable);
inputRows.addEventListener("input", buildTable);
buildTable();

function buildTable() {
  const html = Array.from({length: inputRows.value},
      _ => 
        "<tr>" +
          Array.from({length: inputCols.value},
            _ => "<td><input></td>"
            ).join("") +
        "</tr>"
    )
    .join("");

  const table = document.querySelector("table");
  table.innerHTML = html;
  table.querySelector("input").focus();
}

document.addEventListener("keydown", ({key}) => {

  const delta = { ArrowDown: 1, ArrowUp: -1 }[key];

  if (!delta) {
    return; // not an arrow up/down key
  }

  const table = document.querySelector("table");

  const focused = table.querySelector("input:focus");

  if (!focused) {
    table.querySelector("input").focus(); // focus the first input
    return;
  }

  // find next input with given column count
  const colCount = table.querySelectorAll("tr:first-child > td > input").length;
  const inputs = [...table.querySelectorAll("td > input")];
  const rowCount = inputs.length / colCount;

  let idx = inputs.indexOf(focused);
  const rowIdx = Math.floor(idx / colCount);

  // last cell, go to first
  if (idx === inputs.length - 1 && delta > 0) { 
    idx = 0;
    
  // first cell, go to last
  } else if (idx === 0 && delta < 0) { 
    idx = inputs.length - 1;
    
  // last row, go to next column / first row
  } else if (rowIdx === rowCount - 1 && delta > 0 && idx) { 
    idx = (idx % colCount) + 1;
    
  // first row, go to last row / prevous column
  } else if (rowIdx === 0 && delta < 0 && idx) { 
    idx = (idx % colCount) - 1 + (rowCount - 1) * colCount;
    
  // go to next row
  } else { 
    idx += delta * colCount;
  }

  inputs[idx].focus();
});
<p>
  <label>Number of cols
    <input id="colCount" type="number" min="2" max="4" value="2">
  </label>
  <label>Number of rows
    <input id="rowCount" type="number" min="2" max="10" value="6">
  </label>
</p>
<table>
</table>
Alexander Nenashev
  • 8,775
  • 2
  • 6
  • 17