skip to Main Content

I am trying to add go to line and find text functionalities on an editor that employs textarea and a numbering div.

I have tried the following code but it is not scrolling the editor to focus on the line or text entered by the user in the prompt;

Please help me on how to implement the go to line and find text functions on the editor.

const ta = document.querySelector('textarea')

function updateRowNumbering() {
  const num = ta.value.split("n").length;
  const numbers = ta.parentNode.querySelector(".numbers");
  numbers.innerHTML = Array(num).fill("<span></span>").join("");
}

ta.value = `One
Two
Three
Four
Five
Six
Seven
Eight
Nine
Ten
Eleven
Kumi na Mbili
`
updateRowNumbering()


//Trying to implement go to line and find text;
const numbers = ta.parentNode.querySelector('.numbers');
const gotoLinePrompt = document.querySelector('.goto-line-prompt');
const findPrompt = document.querySelector('.find-prompt');

// Hide the prompts by default.
gotoLinePrompt.style.display = 'none';
findPrompt.style.display = 'none';

// Add event listeners to the "Go to Line" and "Find" buttons.
document.querySelector('.goto-line-button').addEventListener('click', function() {
  gotoLinePrompt.style.display = 'block';
});

document.querySelector('.find-button').addEventListener('click', function() {
  findPrompt.style.display = 'block';
});

// Add event listeners to the "OK" buttons in the prompts.
gotoLinePrompt.querySelector('.ok-button').addEventListener('click', function() {
  const lineNumber = gotoLinePrompt.querySelector('input').value;
  if (lineNumber !== null) {
    const lineHeight = ta.scrollHeight / ta.clientHeight;
    ta.scrollTop = (lineNumber - 1) * lineHeight;
    numbers.scrollTop = ta.scrollTop;
  }

  gotoLinePrompt.style.display = 'none';
});

findPrompt.querySelector('.ok-button').addEventListener('click', function() {
  const searchTerm = findPrompt.querySelector('input').value;
  if (searchTerm !== null) {
    const text = ta.textContent;
    const index = text.indexOf(searchTerm);
    if (index !== -1) {
      const lineNumber = Math.floor(index / ta.value.split('n')[0].length) + 1;
      ta.scrollTop = (lineNumber - 1) * lineHeight;
      numbers.scrollTop = ta.scrollTop;
    }
  }

  findPrompt.style.display = 'none';
});
body, textarea {
  font-family: Consolas, "Courier New", Courier, monospace;
}

.editor {
  display: inline-grid;
  grid-template-columns: 3em auto;
  gap: 10px;
  line-height: 21px;
  background: rgb(40 42 58);
  border-radius: 2px;
  overflow-y: auto;
  max-height: 250px;
}

.editor>* {
  padding-top: 10px;
  padding-bottom: 10px;
}

.numbers {
  text-align: right;
  background: #333;
  padding-right: 5px;
}

.numbers span {
  counter-increment: linenumber;
}

.numbers span::before {
  content: counter(linenumber);
  display: block;
  color: #888;
}

textarea {
  line-height: 21px;
  border: 0;
  background: transparent;
  color: #fff;
  min-width: 500px;
  outline: none;
  resize: none;
  padding-right: 10px;
}
<div class="actions">
<div class="goto-line-prompt" style="display: none;">
  <h1>Go to Line</h1>
  <input type="number" placeholder="Enter the line number to go to:" />
  <button type="button" class="ok-button">OK</button>
</div>
<div class="find-prompt" style="display: none;">
  <h1>Find</h1>
  <input type="text" placeholder="Enter the text to find:" />
  <button type="button" class="ok-button">OK</button>
</div>
<button class="goto-line-button">Go to Line</button>
<button class="find-button">Find</button>
</div>
<div class="editor">
 <div class="numbers">
      <span></span>
   </div>
 <textarea wrap="off" onkeyup="updateRowNumbering()"></textarea>
</div>

2

Answers


  1. .value is NEVER going to be null so that check is useless. There is no textContent for a text area.

    There are built in apis to scroll to an element into the view so there is no reason to reinvent the wheel.

    const ta = document.querySelector('textarea')
    
    function updateRowNumbering() {
      const num = ta.value.split("n").length;
      const numbers = ta.parentNode.querySelector(".numbers");
      numbers.innerHTML = Array(num).fill("<span></span>").join("");
    }
    
    ta.value = `One
    Two
    Three
    Four
    Five
    Six
    Seven
    Eight
    Nine
    Ten
    Eleven
    Kumi na Mbili
    a
    b
    c
    d
    e
    f
    g
    h
    i
    j
    k
    l
    foo bar
    `
    updateRowNumbering()
    
    
    //Trying to implement go to line and find text;
    const numbers = ta.parentNode.querySelector('.numbers');
    const gotoLinePrompt = document.querySelector('.goto-line-prompt');
    const findPrompt = document.querySelector('.find-prompt');
    
    // Hide the prompts by default.
    gotoLinePrompt.style.display = 'none';
    findPrompt.style.display = 'none';
    
    // Add event listeners to the "Go to Line" and "Find" buttons.
    document.querySelector('.goto-line-button').addEventListener('click', function() {
      gotoLinePrompt.style.display = 'block';
    });
    
    document.querySelector('.find-button').addEventListener('click', function() {
      findPrompt.style.display = 'block';
    });
    
    function scrollToLine(lineNumber) {
      if (!lineNumber || lineNumber < 1) return;
      const nums = document.querySelectorAll(".editor > .numbers > span");
      const selectedNum = nums[lineNumber - 1];
      if (!selectedNum) return;
      selectedNum.scrollIntoView();
    }
    
    // Add event listeners to the "OK" buttons in the prompts.
    gotoLinePrompt.querySelector('.ok-button').addEventListener('click', function() {
      const lineNumber = Number(gotoLinePrompt.querySelector('input').value.trim());
      scrollToLine(lineNumber);
      gotoLinePrompt.style.display = 'none';
    });
    
    findPrompt.querySelector('.ok-button').addEventListener('click', function() {
      const searchTerm = findPrompt.querySelector('input').value.trim();
      if (!searchTerm.length) return;
      const textLines = ta.value.split('n');
      
      const lineNumber = textLines.findIndex(text => text.includes(searchTerm));
      
      if (lineNumber > -1) {
        scrollToLine(lineNumber + 1);
      }
    
      findPrompt.style.display = 'none';
    });
    body,
    textarea {
      font-family: Consolas, "Courier New", Courier, monospace;
    }
    
    .editor {
      display: inline-grid;
      grid-template-columns: 3em auto;
      gap: 10px;
      line-height: 21px;
      background: rgb(40 42 58);
      border-radius: 2px;
      overflow-y: auto;
      max-height: 250px;
    }
    
    .editor>* {
      padding-top: 10px;
      padding-bottom: 10px;
    }
    
    .numbers {
      text-align: right;
      background: #333;
      padding-right: 5px;
    }
    
    .numbers span {
      counter-increment: linenumber;
    }
    
    .numbers span::before {
      content: counter(linenumber);
      display: block;
      color: #888;
    }
    
    textarea {
      line-height: 21px;
      border: 0;
      background: transparent;
      color: #fff;
      min-width: 500px;
      outline: none;
      resize: none;
      padding-right: 10px;
    }
    <div class="actions">
      <div class="goto-line-prompt" style="display: none;">
        <h1>Go to Line</h1>
        <input type="number" placeholder="Enter the line number to go to:" />
        <button type="button" class="ok-button">OK</button>
      </div>
      <div class="find-prompt" style="display: none;">
        <h1>Find</h1>
        <input type="text" placeholder="Enter the text to find:" />
        <button type="button" class="ok-button">OK</button>
      </div>
      <button class="goto-line-button">Go to Line</button>
      <button class="find-button">Find</button>
    </div>
    <div class="editor">
      <div class="numbers">
        <span></span>
      </div>
      <textarea wrap="off" onkeyup="updateRowNumbering()"></textarea>
    </div>
    Login or Signup to reply.
  2. There are a few issues I can see here, but I think the core of your problem is that you’re targeting the textarea to scroll when you should be targeting the editor component. You’ll notice that if you set the height/min-height on .editor (not .editor>*) to something tiny like 40px, you get a double scrollbar scenario where the textarea scrolls, as well as the editor – and if you try to scroll the numbers, the entire editor scrolls, whereas when focused on the textarea, only the text scrolls.

    Directionally, something like this may help:

    const editor = document.querySelector('.editor')
    // ^ add another selector for editor
    const ta = document.querySelector('textarea')
    ...
    
    gotoLinePrompt.querySelector('.ok-button').addEventListener('click', function() {
      const lineNumber = gotoLinePrompt.querySelector('input').value;
      if (lineNumber !== null) {
        const lineHeight = 21; // I hard-coded this to test, but your math may still work
        editor.scrollTop = (lineNumber - 1) * lineHeight;
        // ^ scroll the whole editor instead
    ...
    
    .editor {
      ...
      /* Add a specific height property here to test */
      height: 70px; 
    }
    
    textarea {
      ...
      /* Add overflow: hidden here to make sure the text area doesn't scroll on its own */
      overflow: hidden;
    }
    

    +1 also to @epascarello’s answer with points on not reinventing scrolling APIs, this will help make sure things scroll correctly whichever element you end up scrolling.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search