I’d recently searched around for some good-quality JavaScript snippets to determine the cursor position within an HTML TextArea, but haven’t had any luck. So, like any fellow geek would do, I came up with my own solution.
The DOM in IE does not provide information regarding relative position in terms of characters; however, it does provide bounding and offset values for browser-rendered controls. Thus, I used these values to determine the relative bounds of a character. Then, using the JavaScript TextRange, I created a mechanism for working with such measures to calculate the Line and Column position for fixed-width fonts within a given TextArea.
First, the relative bounds for the TextArea must be calculated based upon the size of the fixed-width font used. To do this, the original value of the TextArea must be stored in a local JavaScript variable and clear the value. Then, a TextRange is created to determine the Top and Left bounds of the TextArea.
var storedValue = textBox.value;
textBox.value = "";
textBox.select();
var caretPos = document.selection.createRange();
textBox.__boundingTop = caretPos.boundingTop;
textBox.__boundingLeft = caretPos.boundingLeft;
Next, in order to capture the bounding width of a single fixed-width character, the value of the TextArea is set to a single character, selected, and another TextRange is created.
textBox.value = " ";
textBox.select();
caretPos = document.selection.createRange();
textBox.__boundingWidth = caretPos.boundingWidth;
textBox.__boundingHeight = caretPos.boundingHeight;
textBox.value = storedValue;
The values obtained from the initialization will persist in the instance of the TextArea, textBox. Also, in order for this initialization to occur at least once, it must be registered with the onLoad event.
<body onload=”initPosition(document.forms[0].txtLayoutViewer)”>
Next, the TextArea must be configured to capture the cursor position. Mouse and keyboard activity will be captured as follows.
<textarea name="txtLayoutViewer"
onmouseup="updatePosition(this)"
onmousedown="updatePosition(this)"
onkeyup="updatePosition(this)"
onkeydown="updatePosition(this)"
onfocus="updatePosition(this)"
rows="15"
cols="75"></textarea>
When the updatePosition method is called, another TextRange is created to capture the cursor selection. Then, using the values calculated during the initialization and those in the TextRange, the Line and Column values are calculated as follows.
var caretPos = document.selection.createRange();
var boundingTop = (caretPos.offsetTop + textBox.scrollTop) – textBox.__boundingTop;
var boundingLeft = (caretPos.offsetLeft + textBox.scrollLeft) – textBox.__boundingLeft;
textBox.__Line = (boundingTop / textBox.__boundingHeight) + 1;
textBox.__Column = (boundingLeft / textBox.__boundingWidth) + 1;
As with the bounds captured during initialization, the Line and Column values persist in the instance of the TextArea. They can then be used be other elements throughout the DOM.
Below is the complete code listing.
<html>
<head>
<script language="JavaScript" type="text/javascript">
<!--
function initPosition(textBox) {
var storedValue = textBox.value;
textBox.value = "";
textBox.select();
var caretPos = document.selection.createRange();
textBox.__boundingTop = caretPos.boundingTop;
textBox.__boundingLeft = caretPos.boundingLeft;
textBox.value = " ";
textBox.select();
caretPos = document.selection.createRange();
textBox.__boundingWidth = caretPos.boundingWidth;
textBox.__boundingHeight = caretPos.boundingHeight;
textBox.value = storedValue;
}
function storePosition(textBox) {
var caretPos = document.selection.createRange();
var boundingTop = (caretPos.offsetTop + textBox.scrollTop) - textBox.__boundingTop;
var boundingLeft = (caretPos.offsetLeft + textBox.scrollLeft) - textBox.__boundingLeft;
textBox.__Line = (boundingTop / textBox.__boundingHeight) + 1;
textBox.__Column = (boundingLeft / textBox.__boundingWidth) + 1;
}
function updatePosition(textBox) {
storePosition(textBox);
document.forms[0].txtLine.value = textBox.__Line;
document.forms[0].txtColumn.value = textBox.__Column;
}
//-->
</script>
<style type="text/css">
body, td, tg, input, select {
font-family: Verdana;
font-size: 10px;
}
</style>
</head>
<body onload="initPosition(document.forms[0].txtLayoutViewer)">
<form>
<table cellspacing="0" cellpadding="3">
<tr>
<td colspan="3">
Change Font Size
<select onchange="this.form.txtLayoutViewer.style.fontSize = this.options[this.selectedIndex].value; initPosition(this.form.txtLayoutViewer);">
<option value="10">10px</option>
<option value="12">12px</option>
<option value="14">14px</option>
<option value="16">16px</option>
<option value="18">18px</option>
<option value="20">20px</option>
<option value="24">24px</option>
<option value="36">36px</option>
</select>
</td>
</tr>
<tr>
<td colspan="3">
<textarea name="txtLayoutViewer"
onmouseup="updatePosition(this)"
onmousedown="updatePosition(this)"
onkeyup="updatePosition(this)"
onkeydown="updatePosition(this)"
onfocus="updatePosition(this)"
rows="15"
cols="75">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec ornare aliquam quam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque et quam in dui consequat tempor. Etiam lorem lectus, sollicitudin laoreet, tincidunt nec, pharetra in, magna. Mauris accumsan velit et augue. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</textarea></td>
</tr>
<tr>
<td width="70%">
</td>
<td width="10%">
Line <input type="text" name="txtLine" style="width: 25px" readonly></td>
<td width="20%">
Column <input type="text" name="txtColumn" style="width: 25px" readonly></td>
</tr>
</table>
</form>
</body>
</html>
Please note that this has only been tested on IE 6. As always, I welcome your feedback and constructive criticism.
UPDATE 3/29/3005: A friend of mine filled me in on a limitation to this approach. If the TextArea extends beyond the screen, thus requiring scrolling, the calculated values will be incorrect due to the fact that the bounds used in the calculation change when the window has scrolled. Moreover, if the text you are working with requires that you scroll beyond the viewable portion of the TextArea, the calculated values will reverse. In order to fix this, the scolling index of the screen and/or TextArea must be obtained to offset the calcuation. Thus, if you plan on using this approach ensure that the requirements are such that the TextArea does not require scrolling. Otherwise, if you feel so inclined, email with your thoughts on obtaining the offset value required when scrolling. Thanks! UPDATE 4/12/2005: Thanks to Mr. Noisy, the previous bug listed (TextArea scrolling) has been fixed, and now accounts for the scrollTop and scrollLeft properties of the TextArea. I’ve updated the code in this post to reflect the changes.