Search code examples
javascripthtmlcss

Update textarea line numbers


I'm making a vertical bar that counts the number of rows as the rows are created. There are 2 ways to count rows: The first way is to press a key (addEventListener("keydown") to create a row. This happens correctly. The other way, which is what i need for the question, is to directly display all the line numbers as soon as i open the web page.

PROBLEM. The problem is that the number of rows is not displayed as soon as i open the web page. For example, if i have the text in 3 lines, as soon as i open the web page only 1 line is detected. Instead, I would like it to be displayed directly when I open the web page that there are 3 rows. The problem looks like this:

enter image description here

WHAT I WOULD LIKE. I would like to achieve this while also being able to count the rows by pressing a button (as I already do in the code), but I don't want to encounter a conflict between the two ways. I would like to get this as soon as i open the web page (without pressing any buttons):

enter image description here

How can i achieve this? I'm using this, but it doesn't work (I'm new to Javascript, sorry):

//VIEW DIRECTLY (WITHOUT PRESSING A BUTTON)
const start2 = textarea.selectionStart;
const end2 = textarea.selectionEnd;

textarea.value =
      textarea.value.substring(0, start2) +
      "\t" +
      textarea.value.substring(end2);

Complete code:

const textarea = document.querySelector("textarea");
const numbers = document.querySelector(".numbers");

//WHEN I PRESS KEY
textarea.addEventListener("keyup", (e) => {
  const num = e.target.value.split("\n").length;
  numbers.innerHTML = Array(num).fill("<span></span>").join("");
});

textarea.addEventListener("keydown", (event) => {
  if (event.key === "Tab") {
    const start = textarea.selectionStart;
    const end = textarea.selectionEnd;

    textarea.value =
      textarea.value.substring(0, start) +
      "\t" +
      textarea.value.substring(end);

    event.preventDefault();
  }
})

//VIEW DIRECTLY (WITHOUT PRESSING A BUTTON)
const start2 = textarea.selectionStart;
const end2 = textarea.selectionEnd;

textarea.value =
      textarea.value.substring(0, start2) +
      "\t" +
      textarea.value.substring(end2);
.editor {
  display: inline-grid;
  grid-template-columns: 3em auto;
  gap: 10px;
  line-height: 21px;
  border-radius: 2px;
  overflow-y: auto;
  width: 100%; 3 schermateeeeeeeeeeeeeeee */
}

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

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

.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;
}

textarea::placeholder{
  color: red;
  }

  textarea{
    background-color: black;
    color: green;
  }
<div class="code-area editor">
  <div class="numbers">
    <span></span>
  </div>

<textarea id="htmlCode" placeholder="HTML"  placeholder="PROVA" wrap="off">Test 1
Test 2
Test 3</textarea>
</div>                   
</div>


Solution

  • Here's a better suggestion
    There's three functions that you need to use accordingly for every editable parent element, the updateLineNumbers, tabToSpaces and updateTextareaHeight functions. The line numbers should also adjust if you rezize the app window. Also, see the improved CSS styles

    const updateLineNumbers = (elEditor) => {
      const elNumbers = elEditor.querySelector(".numbers");
      const elTextarea = elEditor.querySelector(".textarea");
      const scrollHeight = elTextarea.offsetHeight;
      const lineHeight = parseFloat(getComputedStyle(elTextarea).lineHeight);
      const totLines = Math.floor(scrollHeight / lineHeight);
      elNumbers.innerHTML = "<span></span>".repeat(totLines);
    };
    
    const tabToSpaces = (evt) => {
      if (evt.key !== "Tab") return;
      evt.preventDefault(); // this will prevent us from tabbing out of the editor
    
      const spaces = " ".repeat(4);
      document.execCommand("insertHTML", false, spaces);
    };
    
    const updateTextareaHeight = (elTextarea) => {
      elTextarea.style.height = 0;
      elTextarea.style.height = elTextarea.scrollHeight + "px";
    };
    
    const makeEditor = (elEditor) => {
      const elTextarea = elEditor.querySelector(".textarea");
    
      elTextarea.addEventListener("keydown", (evt) => {
        tabToSpaces(evt);
      });
    
      elTextarea.addEventListener("input", (evt) => {
        updateTextareaHeight(elTextarea);
        updateLineNumbers(elEditor);
      });
    
      addEventListener("resize", () => {
        updateTextareaHeight(elTextarea);
        updateLineNumbers(elEditor);
      }, true);
    
      updateTextareaHeight(elTextarea);
      updateLineNumbers(elEditor);
    
    };
    
    // Init for multiple .editor elements!
    document.querySelectorAll("[data-editor]").forEach(makeEditor);
    *,
    *::after,
    *::before {
      margin: 0;
      box-sizing: border-box;
    }
    
    body {
      font: 1em/1.4 sans-serif;
    }
    
    [data-editor] {
      display: flex;
      grid-template-columns: 3em auto;
      overflow-y: auto;
      height: 6rem;
      background: hsl(200 20% 10%);
      white-space: pre-wrap;
      font: normal normal 14px/1.4 monospace;
      .textarea,
      .numbers {
        /* inherit font size from parent */
        display: inline-block;
        font: inherit;
        height: max-content;
        padding: 0.5rem;
        border: 0;
        outline: 0;
        background: #0000;
        overflow: hidden;
        resize: none;
      }
      .textarea {
        flex: 1;
        [data-editor="html"] & {
          color: hsl(200 70% 60%);
        }
        [data-editor="css"] & {
          color: hsl(070 70% 60%);
        }
      }
      .numbers {
        text-align: right;
        display: flex;
        flex-direction: column;
        border-right: 1px solid hsl(200 90% 90% / 0.2);
        min-height: 100%;
      }
      .numbers span {
        counter-increment: linenumber;
        &::before {
          content: counter(linenumber);
          color: hsl(200 70% 90%);
        }
      }
    }
    <h3>HTML</h3>
    <div data-editor="html">
      <pre class="numbers"></pre>
      <textarea class="textarea" spellcheck="false" autocorrect="off" autocapitalize="off">&lt;h1 class="heading" title="test this" data-some='foo bar'>This is a Heading&lt;/h1>
    &lt;p>This is a paragraph.&lt;/p></textarea>
    </div>
    
    <h3>CSS</h3>
    <div data-editor="css">
      <pre class="numbers"></pre>
      <textarea class="textarea" spellcheck="false" autocorrect="off" autocapitalize="off">body {
      background: gold;
    }</textarea>
    </div>