Why doesn't the previous() function loop when the find term is the first word in a textarea?

I have a basic text editor where the user can search for a term in the text and navigate to the next or previous occurrence. The issue I’m facing is that when the search term is at the very beginning of the textarea (e.g., the first word “Love”), pressing the “Previous” button doesn’t loop back to the last occurrence. However, this works fine if the search term is not at the start of the text (e.g., searching for the term “never”).

Here is the relevant code:

let currentMatchIndex = -1; // Tracks the current match index

function resetIndex() {
  currentMatchIndex = -1;
}

function highlightMatch(startIndex, endIndex) {
  const textarea = document.getElementById('myTextarea');
  textarea.setSelectionRange(startIndex, endIndex);
  textarea.focus();
}

function next() {
  const findTerm = document.getElementById('findInput').value;
  const textarea = document.getElementById('myTextarea');
  const text = textarea.value;

  if (findTerm) {
    const startIndex = text.indexOf(findTerm, currentMatchIndex + 1);
    if (startIndex !== -1) {
      currentMatchIndex = startIndex;
    } else {
      currentMatchIndex = text.indexOf(findTerm); // Loop back to first occurrence
    }

    if (currentMatchIndex !== -1) {
      highlightMatch(currentMatchIndex, currentMatchIndex + findTerm.length);
    } else {
      alert("Find term not found.");
    }
  }
}

function previous() {
  const findTerm = document.getElementById('findInput').value;
  const textarea = document.getElementById('myTextarea');
  const text = textarea.value;

  if (findTerm) {
    const startIndex = text.lastIndexOf(findTerm, currentMatchIndex - 1);
    if (startIndex !== -1) {
      currentMatchIndex = startIndex;
    } else {
      currentMatchIndex = text.lastIndexOf(findTerm); // Loop to last occurrence
    }

    if (currentMatchIndex !== -1) {
      highlightMatch(currentMatchIndex, currentMatchIndex + findTerm.length);
    } else {
      alert("Find term not found.");
    }
  }
}
<input type="text" id="findInput" placeholder="Find" onchange="resetIndex()">
<button onclick="next()">Next</button>
<button onclick="previous()">Previous</button>
<textarea id="myTextarea" rows="1" cols="50">Love never dies. Love never dies. Love never dies.</textarea>

Issue

When the search term is the first word in the textarea (e.g., “Love”), and I press the “Previous” button, it does not loop back to the last occurrence. However, if I search for a term like “never,” it works fine and loops as expected.

Expected behavior

  • When the “Next” button is pressed, it finds the next occurrence.
  • When the “Previous” button is pressed, it loops to the last occurrence when it reaches the first match.

Has anyone encountered this issue before or knows how to fix it?

With “Love” highlighted, another press of ‘Previous’ will cause the starting position parameter of lastIndexOf on line 40 to be -1. The method then behaves as if the parameter is zero and looks for the start of the substring only at position 0. As the start of “Love” is at position 0, the search is successful and currentMatchIndex is set to 0, not to -1.

2 Likes

If currentMatchIndex is 0, your second parameter becomes -1, and a negative number in that parameter is treated as though it was a 0. So lastIndexOf will return 0 again, not a -1.

EDIT: Jinx, Archi! :wink:

1 Like

Thanks for the answer! I wonder how I can deal with the problem.

I was wondering that :grinning:

Try this:

if (findTerm) {
    const startIndex = text.lastIndexOf(findTerm, currentMatchIndex - 1);
    if (currentMatchIndex != 0 && startIndex != -1) {
      currentMatchIndex = startIndex;
    } else {
      currentMatchIndex = text.lastIndexOf(findTerm); // search whole string
    }

    if (currentMatchIndex !== -1) {
      highlightMatch(currentMatchIndex, currentMatchIndex + findTerm.length);
    } else {
      alert("Find term not found.");
    }
  }

Almost there! Just a problem: Type “Love” and then click the “Previous” button. It won’t go to the last occurrence. Again it works fine with “never”.

You are setting currentMatchIndex to -1 in the first line, so try:

if (currentMatchIndex > 0 && startIndex != -1)

1 Like
const indicies = [];
const regex = RegExp(findterm ?? "NotA123123RealString","gi");
let r;
while(r = regex.exec(text) != null) { indicies.push(r.index); }
if(indicies.length == 0) { //No Match Found. Handle appropriately. }
currentMatchIndex = indicies[(((indicies.indexOf(currentMatchIndex) == -1) ? -1 : indicies.indexOf(currentMatchIndex) -1 ) % indicies.length)]

(It’s 7 AM, and this is entirely spitballing, untested code. But the theory should hold.)

Basically: Fill an array with all indexes of the target string.
If your current index is a member of the array, choose the array member before it.
If your current index isnt a member of the array, choose the last member of the array.

How about this:

    if (findTerm) {
      // Search for the previous match
      const startIndex = text.lastIndexOf(findTerm, currentMatchIndex > 0 ? currentMatchIndex - 1 : text.length);

1 Like