Need to set cursor position to the end of a contentEditable div, issue with selection and range objects

DavidR picture DavidR · May 31, 2010 · Viewed 8.8k times · Source

I'm forgetting about cross-browser compatibility for the moment, I just want this to work. What I'm doing is trying to modify a script (and you probably don't need to know this) located at typegreek.com The basic script is found here. Basically what it does is when you type in characters, it converts the character your are typing into greek characters and prints it onto the screen. What I'm trying to do is to get it to work on contentEditable div's (It only works for Textareas)

My issue is with this one function: The user types a key, it get's converted to a greek key, and goes to a function, it gets sorted through some if's, and where it ends up is where I can add div support. Here is what I have so far,

myField is the div, myValue is the greek character.

//Get selection object...
var userSelection
 if (window.getSelection) {userSelection = window.getSelection();}
 else if (document.selection) {userSelection = document.selection.createRange();}

//Now get the cursor position information...
var startPos = userSelection.anchorOffset;
var endPos = userSelection.focusOffset;
var cursorPos = endPos;

//Needed later when reinserting the cursor...
var rangeObj = userSelection.getRangeAt(0) 
var container = rangeObj.startContainer

//Now take the content from pos 0 -> cursor, add in myValue, then insert everything after myValue to the end of the line.
myField.textContent = myField.textContent.substring(0, startPos) + myValue + myField.textContent.substring(endPos, myField.textContent.length);

//Now the issue is, this updates the string, and returns the cursor to the beginning of the div. 
//so that at the next keypress, the character is inserted into the beginning of the div.
//So we need to reinsert the cursor where it was.

//Re-evaluate the cursor position, taking into account the added character.
  var cursorPos = endPos + myValue.length;

  //Set the caracter position.
  rangeObj.setStart(container,cursorPos) 

Now, this works only as long as I don't type more than the size of the original text. Say I had 30 characters in the div before hand. If I type more than that 30, it adds character 31, but places the cursor back at 30. I can type character 32 at pos.31, then character 33 at pos.32, but if I try to put character 34 in, it adds the character, and sets the cursor back at 32. The issue is that the function for adding the new character screws up if cursorPos is greater than what is defined in the range. Any ideas?

Answer

Tim Down picture Tim Down · May 31, 2010

It's easier to do this cross-browser in a contenteditable div than a textarea. The following assumes your contenteditable div has an id of "greek".

var greekChars = {
    "a": "\u03b1"
    // Add character mappings here
};

function convertCharToGreek(charStr) {
    return greekChars[charStr] || "[Greek]";
}

function insertTextAtCursor(text) {
    var sel, range, textNode;
    if (window.getSelection) {
        sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) {
            range = sel.getRangeAt(0);
            range.deleteContents();
            textNode = document.createTextNode(text);
            range.insertNode(textNode);

            // Move caret to the end of the newly inserted text node
            range.setStart(textNode, textNode.length);
            range.setEnd(textNode, textNode.length);
            sel.removeAllRanges();
            sel.addRange(range);
        }
    } else if (document.selection && document.selection.createRange) {
        range = document.selection.createRange();
        range.pasteHTML(text);
    }
}

var div = document.getElementById("greek");

div.onkeypress = function(evt) {
    evt = evt || window.event;
    var charCode = (typeof evt.which == "undefined") ? evt.keyCode : evt.which;
    if (charCode) {
        var charStr = String.fromCharCode(charCode);
        var greek = convertCharToGreek(charStr);
        insertTextAtCursor(greek);
        return false;
    }
}