views:

69

answers:

2

Hi,

I'm finding tons of good, crossbrowser anwers on how to SET the cursor or caret position in a contentEditable DIV, but none on how to GET or find its position...

What I want to do is know the position of the caret within this div, on keyup.

So, when the user is typing text, I can at any point know its cursor's position within the div.

EDIT: I'm looking for the INDEX within the div contents (text), not the cursor coordinates.

<div id="contentBox" contentEditable="true"></div>

$('#contentbox').keyup(function() { 
    // ... ? 
});
A: 
//global savedrange variable to store text range in
var savedrange = null;

function getSelection()
{
    var savedRange;
    if(window.getSelection && window.getSelection().rangeCount > 0) //FF,Chrome,Opera,Safari,IE9+
    {
        savedRange = window.getSelection().getRangeAt(0).cloneRange();
    }
    else if(document.selection)//IE 8 and lower
    { 
        savedRange = document.selection.createRange();
    }
    return savedRange;
}

$('#contentbox').keyup(function() { 
    var currentRange = getSelection();
    if(window.getSelection)
    {
        //do stuff with standards based object
    }
    else if(document.selection)
    { 
        //do stuff with microsoft object (ie8 and lower)
    }
});

Note: the range object its self can be stored in a variable, and can be re-selected at any time unless the contents of the contenteditable div change.

Reference for IE 8 and lower: http://msdn.microsoft.com/en-us/library/ms535872(VS.85).aspx

Reference for standards (all other) browsers: https://developer.mozilla.org/en/DOM/range (its the mozilla docs, but code works in chrome, safari, opera and ie9 too)

Nico Burns
Thanks, but how exactly do I get the 'index' of the caret position in the div contents?
Bertvan
OK, it looks like calling .baseOffset on .getSelection() does the trick. So this, together with your answer, answers my question. Thanks!
Bertvan
Unfortunately .baseOffset only work in webkit (i think). It also only gives you the offset from the imediate parent of the caret (if you have a <b> tag inside the <div> it will give the offset from the start of the <b>, not the start of the <div>. Standards based ranges can use range.endOffset range.startOffset range.endContainer and range.startContainer to get the offset from the parent *node* of the selection, and the node itself (this includes text nodes). IE provides range.offsetLeft which is the offset from the left in *pixels*, and so useless.
Nico Burns
It is best just to store the range object its self and use window.getSelection().addrange(range); <--standards and range.select(); <--IE for re-positioning the cursor in the same place. range.insertNode(nodetoinsert); <--standards and range.pasteHTML(htmlcode); <--IE to insert text or html at the cursor.
Nico Burns
The `Range` object returned by most browsers and the `TextRange` object returned by IE are extremely different things, so I'm not sure this answer solves much.
Tim Down
@Nico (immediate parent): I'm not planning to allow other tags within the <diV>, only text
Bertvan
@Tim: At this point, it's working in Webkit, so I'm happy, but if a better (cross browser) solution comes up I'll change the answer.
Bertvan
Answer changed to Tim's answer, this works cross browser
Bertvan
+1  A: 

The following code assumes:

  • There is always a single text node within the editable <div> and no other nodes
  • The editable div does not have the CSS white-space property set to pre

Code:

function getCaretPosition(editableDiv) {
    var caretPos = 0, containerEl = null, sel, range;
    if (window.getSelection) {
        sel = window.getSelection();
        if (sel.rangeCount) {
            range = sel.getRangeAt(0);
            if (range.commonAncestorContainer.parentNode == editableDiv) {
                caretPos = range.endOffset;
            }
        }
    } else if (document.selection && document.selection.createRange) {
        range = document.selection.createRange();
        if (range.parentElement() == editableDiv) {
            var tempEl = document.createElement("span");
            editableDiv.insertBefore(tempEl, editableDiv.firstChild);
            var tempRange = range.duplicate();
            tempRange.moveToElementText(tempEl);
            tempRange.setEndPoint("EndToEnd", range);
            caretPos = tempRange.text.length;
        }
    }
    return caretPos;
}

$('#contentbox').keyup(function() { 
    alert(getCaretPosition(this));
});
Tim Down
Sorry, had to undo the answer: I _am_ going to need other tags. There will be <a> tags within the <div>, but no nesting. Will test your solution, maybe this even works for what I need...
Bertvan
Oh, and I know I first said that there wouldn't be any other tags in there. Sorry, my mistake.
Bertvan
This won't work if there's any other tags in there. Question: if the caret's inside an `<a>` element inside the `<div>`, what offset do you want then? The offset within the text inside the `<a>`?
Tim Down
You might want to remove console.log as this will crash in browsers that do not have firebug installed.
Nico Burns
@Nico: Good spot, thanks. Removed now.
Tim Down