Set cursor position in content-editable div Set cursor position in content-editable div google-chrome google-chrome

Set cursor position in content-editable div


Here's a much simpler approach. There are a few things to note:

  • keypress is the only key event in which you can reliably detect which character has been typed. keyup and keydown won't do.
  • The code handles the insertion of parentheses/braces manually by preventing the default action of the keypress event.
  • The selection/caret stuff is simple when using DOM methods.
  • This won't work in IE <= 8, which have different range and selection APIs. If you need support for those browsers, I'd suggest using my own Rangy library. It is possible without it but I really don't want to write the extra code.

Demo:

http://jsfiddle.net/HPeb2/

Code:

var editableEl = document.getElementById("editable");editableEl.addEventListener("keypress", function(e) {    var charTyped = String.fromCharCode(e.which);    if (charTyped == "{" || charTyped == "(") {        // Handle this case ourselves        e.preventDefault();        var sel = window.getSelection();        if (sel.rangeCount > 0) {            // First, delete the existing selection            var range = sel.getRangeAt(0);            range.deleteContents();            // Insert a text node at the caret containing the braces/parens            var text = (charTyped == "{") ? "{}" : "()";            var textNode = document.createTextNode(text);            range.insertNode(textNode);            // Move the selection to the middle of the inserted text node            range.setStart(textNode, 1);            range.setEnd(textNode, 1);            sel.removeAllRanges();            sel.addRange(range);        }    }}, false);


To accompish the goal stated in your summary, try altering the node value at the current cursor position. Since your code is attached to a keyup event, you can be assured that the range is already collapsed and on a text node (all of that happens on key down, which would have already fired).

function insertChar(char) {    var range = window.getSelection().getRangeAt(0);    if (range.startContainer.nodeType === Node.TEXT_NODE) {        range.startContainer.insertData(range.startOffset, char);    }}function handleKeyUp(e) {    e = e || window.event;    var char, keyCode;    keyCode = e.keyCode;    char =        (e.shiftKey ? {            "222": '"',            "57":  ')',            "219": '}'        } : {            "219": "]"        })[keyCode] || null;    if (char) {        insertChar(char);    }}document.getElementById("editable").onkeyup = handleKeyUp;

Fiddle

Also, I see that you were using innerHTML to set the new value (in Element.prototype.setText). This sounds alarming to me! innerHTML totally nukes whatever contents were previously in the container. Since the cursor is bound to a particular element, and those elements just got nuked, what is the browser supposed to do? Try to avoid using this if you care at all about where your cursor ends up afterwards.

As for the highlightText issue, it's hard to say why it is broken. Your fiddle does not show it being used anywhere, and I would need to see its usage to further diagnose it. However, I have an idea about what might be going wrong:

I think you should take a close look at getCaretPosition. You are treating this as though it returns the cursor position, but that isn't what it does. Remember, to a browser, your cursor position is always a range. It always has a beginning and an end. Sometimes, when the range is collapsed, the beginning and the end are the same point. However, the idea that you could get a cursor position and treat it as a single point is a dangerous oversimplification.

getCaretPosition has another issue. For your editable div, it does this:

  1. Selects all of the text in a new range
  2. Resets the new range's end position to equal the cursor's end position (so all text up to the cursor's end position is selected).
  3. Calls toString() and returns the length of the resulting string.

As you have noted, some elements (such as <br />) affect the results of toString(). Some elements (such as <span></span>) do not. In order to get this calculation right, you'll need to nudge it for some element types and not for others. This is going to be messy and complicated. If you want a number that you can feed into highlightText and have it work as expected, your current getCaretPosition is unlikely to be helpful.

Instead, I think you should try working directly with the cursor's start and end points as two separate locations, and update highlightText accordingly. Discard the current getCaretPosition and use the browser's native concepts of range.startContainer, range.startOffset, range.endContainer, and range.endOffset directly.