3

I'm looking for a pure javascript answer as that reflects my projects scope. jQuery answers will not be marked correct, but are welcomed for future question seekers. Side note: I am also not interested in third party libraries.

I'm trying to get the first position (0) of the current line (current is based of selectionStart the chance the user selects more than 1 line) in a multiline textarea, but translate that to caret index.

What have I tried? Nothing pretty:

for ( i = 0; i < this.selectionStart; i++ ) {      
  if (this.value.substr(i,1).match(/\r?\n|\r/)) {
    lineStartIndx = i + 1
  }
}

This is proving to be costly when iterating textareas with huge amounts of lines. My personal usage of this will not be executed every keydown, however, I just used it as an example. Are there any better methods, built in or otherwise to emulate this outcome?

My full example:

var input = document.getElementById("ta");
  var output = document.getElementById("output");
  let pattern = /\r?\n|\r/;
  var lineNum, lineStartIndx;
 
  input.addEventListener("keydown", function(e) {
    taHandler(this);  
  })

  input.addEventListener("click", function(e) {
    taHandler(this);
  })

  function taHandler(elem) {

    lineNum = getLineNumForSelection(elem);

    let caretPos = elem.selectionStart;
    lineStartIndx = 0;

    for ( i = 0; i < caretPos; i++ ) {

      if (elem.value.substr(i,1).match(pattern)) {
        lineStartIndx = i + 1
      }
    }

    output.innerHTML = "Selection Start: " + caretPos + " Selection End: " + elem.selectionEnd + 
    " <br> Line Number: "  + lineNum.start + 
    "<br>Line Start Position: " + lineStartIndx;
  }

  function getLineNumForSelection(sel) {
    return {
      'start' : sel.value.substr(0, sel.selectionStart).split(pattern).length,
      'end' : sel.value.substr(0,sel.selectionEnd).split(pattern).length
    };
  }
<textarea id="ta" rows="5" cols="50">
  Line one
    Line two
    Line three
  Line four  
</textarea>
<hr>
<div id="output"></div>
soulshined
  • 9,612
  • 5
  • 44
  • 79
  • So this code is working? Just slow? (If so you should post it to code review, not here) – Cody G Jan 14 '18 at 04:27
  • `document.getElementById("output").split('\n').charAt(0)`? – Meghan Jan 14 '18 at 04:30
  • Providing things we've tried is a core requirement of Stack Overflow questions. @CodyG. I could rephrase to ask for a specific answer "how to find...with Regex or textNode or offSets?" However, I feel that would suppress potential answers that generically provide a solution. I'm new to javascript so what I've tried is is not javascript specific. It's programming loop logic not specfic to javascript solutions. – soulshined Jan 14 '18 at 04:56
  • thanks for the suggestion @SeanDenny your feedback answer's a different kind of question. I don't want the character, rather the index of it, in textarea range values. (I dont know how to word it any better sorry) – soulshined Jan 14 '18 at 05:01
  • 1
    I think you should give a few "case examples" --- what's the input and what is the output – Cody G Jan 14 '18 at 05:07

1 Answers1

1

The method in my copy of the snippet splits the content into lines and uses the .length property instead of a loop so it looks "prettier" but according to time complexity of javascript's .length may not be any faster since there is nothing in the spec that prevents slow browser implementation.

Two side notes about the code, I'd use lineStartIndex, not lineStartIndx without the e. Since lineNum is an array, I'd use lineNumArray or selLineNums or something that is more obvious since variables ending in num are generally integers.

var input = document.getElementById("ta");
  var output = document.getElementById("output");
  let pattern = /\r?\n|\r/;
  var lineNum, lineStartIndx;
 
  input.addEventListener("keydown", function(e) {
    taHandler(this);  
  })

  input.addEventListener("click", function(e) {
    taHandler(this);
  })

  function taHandler(elem) {

    lineNum = getLineNumForSelection(elem);

    let caretPos = elem.selectionStart;
    lineStartIndx = 0;

    // begin modified code
    let lines = elem.value.split(pattern),
        lineIndex = 0;
        
    while ( (lineIndex + 1 ) < lineNum.start ) {
        lineStartIndx += parseInt( lines[lineIndex].length ) + 1;
        lineIndex++;
    }
    // end modified code
  
    // begin replaced code
    for ( i = 0; i < caretPos; i++ ) {

      if (elem.value.substr(i,1).match(pattern)) {
        lineStartIndx = i + 1
      }
    }
    // end replaced code

    output.innerHTML = "Selection Start: " + caretPos + " Selection End: " + elem.selectionEnd + 
    " <br> Line Number: "  + lineNum.start + 
    "<br>Line Start Position: " + lineStartIndx;
  }

  function getLineNumForSelection(sel) {
    return {
      'start' : sel.value.substr(0, sel.selectionStart).split(pattern).length,
      'end' : sel.value.substr(0,sel.selectionEnd).split(pattern).length
    };
  }
<textarea id="ta" rows="5" cols="50">
  Line one
    Line two
    Line three
  Line four  
</textarea>
<hr>
<div id="output"></div>
JasonB
  • 6,243
  • 2
  • 17
  • 27