Several days ago (last year, actually) I started researching the problem of implementing a browser-based rich text editor. Among other questions that need to be answered there is the following one: if we use a div element as the text editor or a text display widget (e.g. when capturing user input with a hidden textarea), how shall we track the current caret position?
As usual, there are two solutions: for Internet Explorer and everything else
Both involve installing keyup and click event handlers:
<div id="test" contenteditable="true" onclick="getCursorPos();" onkeyup="getCursorPos();">Lots of text for testing</div>
As a rule, cursor position and the current selection are tightly wired together: when nothing is selected, the current selection begins and ends at the cursor position. So, we retrieve the selection range (in the code below, we assume that there is only one selection range in the window. I leave proper handling of the situation where multiple selection ranges exist as an exercise for the reader).
Now, the actual findNode function depends on the method you build the text editor and on your algorithmic skills. When you use a single long text node which includes all the text in the editor, it will be different from the case when every character is a separate text node. Besides, you are welcome to use optimized search algorithms if you are going to work with large documents. The important part here is that the
anchorNode property references the first character in the selection, and you will only need to find its index among the other characters
[1]. An example findNode() function is given below.
The cursor position and the selection display some dexterity in their mutual dependency. The quirk is that when you click on a character, the cursor is sometimes positioned by different math than the selection’s anchor. For instance, the cursor is positioned before the character if you click on its left 50% and after the character if you click on its right 50%, while if it is included in the selection or not depends on its geometry or the percentages are merely different. So, in order to reflect this, we add the selection object’s
anchorOffset (actually, the selection length in characters)
[2] to the value returned by the findNode function.
To make things work for Internet Explorer, we have to rely on a proprietary undocumented possibility. It appears that the second Unicode character in the bookmark of the selection range contains the value we need plus 11. Currently, I don’t know the meaning of this number, and whether its exact value changes depending on circumstances and can be calculated somehow.
function getCursorPos() {
var cursorPos;
if (window.getSelection) {
var selObj = window.getSelection();
var selRange = selObj.getRangeAt(0);
cursorPos = findNode(selObj.anchorNode.parentNode.childNodes, selObj.anchorNode) + selObj.anchorOffset;
/* FIXME the following works wrong in Opera when the document is longer than 32767 chars */
alert(cursorPos);
}
else if (document.selection) {
var range = document.selection.createRange();
var bookmark = range.getBookmark();
/* FIXME the following works wrong when the document is longer than 65535 chars */
cursorPos = bookmark.charCodeAt(2) - 11; /* Undocumented function [3] */
alert(cursorPos);
}
}
function findNode(list, node) {
for (var i = 0; i < list.length; i++) {
if (list[i] == node) {
return i;
}
}
return -1;
}
Well, that’s all. Solution tested in Firefox 3, Opera 9, Google Chrome, Safari 3.1 and IE7.