0

I am trying to find the selection index of the caret inside a editable div that has nested nodes in it.

Example ( | is the cursor):

<div contenteditable="true">1234<span>5678|9</span></div> //Returns 4

I want the index of all the characters in the div, so the above example should return 8.

This is what I'm using at the moment.

var sel = window.getSelection();
    return sel.anchorOffset;

I have tried using commonAncestor, and other selection & range methods, but I am unsure how to find this.

gkiely
  • 2,987
  • 1
  • 23
  • 37
  • getRangeAt() returns the same value – gkiely Dec 18 '12 at 02:43
  • try this snippet [Get/set Cursor In Html TextArea - JavaScript - Snipplr Social Snippet Repository](http://snipplr.com/view/5144/getset-cursor-in-html-textarea/) – MikeM Dec 18 '12 at 02:47
  • possible duplicate of [Get a range's start and end offset's relative to its parent container](http://stackoverflow.com/questions/4811822/get-a-ranges-start-and-end-offsets-relative-to-its-parent-container) – Tim Down Dec 18 '12 at 10:00

1 Answers1

2

Traverse the tree! Here’s a demo.

function getSelectionOffsetFrom(parent) {
    var sel = window.getSelection();
    var current = sel.anchorNode;
    var offset = sel.anchorOffset;

    while(current && current !== parent) {
        var sibling = current;

        while(sibling = sibling.previousSibling) {
            if(sibling.nodeType === 3) {
                offset += sibling.nodeValue.length;
            } else if(sibling.nodeType === 1) {
                offset += getContentLength(sibling);
            }
        }

        current = current.parentNode;
    }

    if(!current) {
        return null;
    }

    return offset;
}

function getContentLength(element) {
    var stack = [element];
    var total = 0;
    var current;

    while(current = stack.pop()) {
        for(var i = 0; i < current.childNodes.length; i++) {
            if(current.childNodes[i].nodeType === 1) {
                stack.push(current.childNodes[i]);
            } else if(current.childNodes[i].nodeType === 3) {
                total += current.childNodes[i].nodeValue.length;
            }
        }
    }

    return total;
}
Ry-
  • 218,210
  • 55
  • 464
  • 476
  • Thanks for the example! Only problem is if there is text after the span. Put the caret in 10. http://jsfiddle.net/ybn6e/1/ – gkiely Dec 18 '12 at 03:02
  • @GrantKiely: Aaah. Right. Just a minute! What kind of browser compatibility do you need? – Ry- Dec 18 '12 at 03:09
  • 1
    @GrantKiely: Ah, well, you could just use `offset += sibling.textContent.length;` instead of the `getContentLength()` thing I just added, then :) – Ry- Dec 18 '12 at 03:15
  • 1
    Works an absolute treat! I can't believe you are 15. Yeah I used the `+= textContent.length` it's neater :) – gkiely Dec 18 '12 at 03:29
  • This is good, but there's actually no need to do the traversal: you can create a range that spans from the start of the contenteditable element until the caret position and use its `toString()` method. See http://stackoverflow.com/questions/4811822/get-a-ranges-start-and-end-offsets-relative-to-its-parent-container/4812022#4812022 – Tim Down Dec 18 '12 at 09:58