0

I want to create a type ahead mechanism for emails in a textarea.

If I type into a textarea the control will automatically word wrap the text, so for me to know where the cursor currently is (that is, its x,y postion) depends on where these line breaks take place. (The selection position being just how many characters from the start the cursor is at.)

I need the x any y position so that I can position a list of possible completions below the cursor.

Is there a way to extract this line break information from the control, or do I have to modify it and do a "roll your own" text wrapping algorithm (something that is tricky since it isn't easy to measure text width in javascript.)

Any help would be appreciated.

Fraser Orr
  • 361
  • 1
  • 3
  • 19
  • Few ways to do this. Replicate the text to a hidden or off viewport `div` and get it's width. If the width of the `div` is greater than the width of the text field, you know the text will wrap. Your Y will increase by the `line-height`. Repeat for each line, and then you can detect the width of the line too for the final X. Not exactly easy, but might work. – Twisty May 01 '18 at 05:13
  • Possible suggestion: https://stackoverflow.com/questions/7609564/how-to-detect-if-a-text-field-needs-to-wrap-in-javascript-dojo – Twisty May 01 '18 at 05:44
  • Also: https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript – Twisty May 01 '18 at 06:24

1 Answers1

0

Putting everything together with jQuery. Let me know if you need a JavaScript block.

$(function() {
  var textProps = [0, 0];
  $("textarea.allow-overflow").keyup(function(e) {
    var self = $(this);
    var text_width = $(".hidden").text(self.val()).css({
      'font-weight': self.css("font-weight"),
      'font-size': self.css("font-size"),
      'font-family': self.css("font-family"),
      'white-space': 'nowrap',
      'position': 'absolute',
      'display': 'block',
      'width': 'auto'
    }).hide().width();
    textProps[0] = $(".hidden").width();
    textProps[1] = $(".hidden").height();
    var overflows = text_width > self.width();
    var lines = 1;
    if (overflows) {
      lines = Math.floor(self.prop("scrollHeight") / $(".hidden").height());
      textProps[0] = textProps[0] - (self.width() * (lines - 1));
      textProps[1] = $(".hidden").height() * lines;
    }
    $(".report").html("X (Left): " + textProps[0] + "px, Y (Top): " + textProps[1] + "px, Wrap: " + overflows.toString() + ", Lines: " + lines);
  });
});
.widget label {
  display: block;
}

.widget .allow-overflow,
.report {
  width: 240px;
  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
  font-size: 13px;
  font-weight: 400;
}

.report {
  border: 1px dashed #ccc;
  font-size: 9px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="widget">
  <label>Type in me</label>
  <textarea class="allow-overflow"></textarea>
</div>
<div class="report">&nbsp;</div>
<div class="hidden"></div>

We need to know a few things.

  1. When we have overflowed and are wrapping in the text box.
  2. The width and font properties of the text box
  3. The line height of the text

We can then calculate the current cursor position from the [X (Left), Y (Top)] of the text box. When we enter text, it then increases the width and height of the hidden div.

On the first line, this is easy, x = width and y = height. Once we have wrapped, we now have to calculate an offset and the number of lines.

number of lines = floor( text box scroll height / hidden height )
x pos on line = hidden width - (width of text box * number of lines)
y pos = line height * number of lines

You can push this into a function and cleanup the resulting data into an array or object.

If you allow for resizing, then we cannot count on static width and height of text box. So, for my example, I grab that detail each time.

Hope that helps.

Twisty
  • 30,304
  • 2
  • 26
  • 45
  • This helps a little, and thanks for putting it together, however, the problem is that I need to know where the line breaks are, not just the total number of lines. The cursor may be anywhere in the text and ultimately it is the cursor position I need. – Fraser Orr May 01 '18 at 13:42
  • @FraserOrr technically, there are no line breaks. A line break or Carriage Return & New Line (`\r\n`) is the only indicator of the End of Line (EOL). You're trying to identify the overflow point where text is wrapped to the next line. There is no delimiter for this, you can only do it by measurement. – Twisty May 01 '18 at 16:29
  • OK, thanks all, FYI, I copped out and changed the font to a monospaced font which makes it quicker to do the measurement arithmetic. Screwing around with the DOM for measuring was just too slow. – Fraser Orr May 01 '18 at 20:52