4

I have the following fragment of code to set the caret to a given index index

var el = $("#subjectMessage"), index = 9 ;
var range = rangy.createRange();
range.setStart(el[0].childNodes[0], index);
range.collapse(true);
var sel = rangy.getSelection();
sel.setSingleRange(range);

Issues arise when I add input elements to the contentEditable div, I can't consistently set the caret at the desired position anymore.

I want to treat these inputs as just one position in the div (as if they were just a single character).

Aside from this, I also would need similar code to replace the selection within this contentEditable div with some text of my own, and I'm not really familiar (at all) with rangy to understand how to make this work...

All help is desperately welcome!

Here's a fiddle you can toy with:

http://jsfiddle.net/ee93P/

bevacqua
  • 47,502
  • 56
  • 171
  • 285
  • `I want to treat these inputs as just one position in the div (as if they were just a single character).` It works that way for me, in Firefox. – ANeves Dec 13 '11 at 13:28
  • Yup, but the thing is I want to manipulate the div to edit the selection and place the caret where I feel like. – bevacqua Dec 13 '11 at 14:20
  • Ah, I understand now. (Niiice stuff you're working on. :) – ANeves Dec 13 '11 at 14:43

1 Answers1

7

Rangy's Selection and Range APIs are supersets of the standard DOM Selection and Range APIs, so documentation from places like MDN (Range, Selection) apply.

The problem you're having is that range boundaries are expressed as an offset within a containing DOM node. For example, in the HTML below where the caret is denoted as a pipe character:

<p>Foo<br>b|ar</p>

... the caret range's start and end boundaries are identical and are set to offset 1 in the text node "bar".

If you wanted to set the position as an offset within the text content of the <p> element, you would need to do some DOM traversal. I've written an implementation of this, based on another answer of mine. This is naive implementation: it does not take into account any text that may be made invisible (either by CSS or by being inside a or element, for example) and may have browser discrepancies (IE versus everything else) with line breaks, and takes no account of collapsed whitespace (such as 2 or more consecutive space characters collapsing to one visible space on the page). This is a tricky thing to get right, which is why I wouldn't generally recommend it. I am planning to write a text-based module for Rangy that will handle all this, but I haven't started it yet.

http://jsfiddle.net/ee93P/2/

Code:

function setCaretCharIndex(containerEl, index) {
    var charIndex = 0, stop = {};

    function traverseNodes(node) {
        if (node.nodeType == 3) {
            var nextCharIndex = charIndex + node.length;
            if (index >= charIndex && index <= nextCharIndex) {
                rangy.getSelection().collapse(node, index - charIndex);
                throw stop;
            }
            charIndex = nextCharIndex;
        }
        // Count an empty element as a single character. The list below may not be exhaustive.
        else if (node.nodeType == 1
                 && /^(input|br|img|col|area|link|meta|link|param|base)$/i.test(node.nodeName)) {
            charIndex += 1;
        } else {
            var child = node.firstChild;
            while (child) {
                traverseNodes(child);
                child = child.nextSibling;
            }
        }
    }

    try {
        traverseNodes(containerEl);
    } catch (ex) {
        if (ex != stop) {
            throw ex;
        }
    }
}
Community
  • 1
  • 1
Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • How could I do something similar, to get the selected range in this same div? – bevacqua Dec 13 '11 at 16:14
  • @Nico: You could adapt the `saveSelection()` function in this answer: http://stackoverflow.com/questions/5595956/replace-innerhtml-in-contenteditable-div/5596688#5596688. If I get a moment, I'll try to look at it. – Tim Down Dec 13 '11 at 17:16
  • You saved my life. Thank you sir, I'll give +1 to all your answers :D – Muhammet Göktürk Ayan May 22 '12 at 13:09