9

First of all, this is similar to these:

Editing Iframe Content in IE - problem in maintaining text selection

How to get caret position within contenteditable div with html child elements?

Basically, I'm writing something similar to the new tweet editor in twitter. Inside a div with contentEditable set on, I am parsing the text. When I detect what I think is a URL, I strip that text out and enter into an <a> tag inside the div (e.g. entered message of go to www.google.com becomes Go to <a href='..'>www.google.com</a>. BUT, the caret position is lost when I do that.

Using the suggestion by @Tim-Down in those other SO posts will only work as long as you don't edit the DOM (as he says). I AM editing the DOM, so the caret position is lost.

Is it possible (ideally without using the Rangy library referenced) to achieve this by working on the current code.

The code that I have at the moment (from the other SO posts):

var saveSelection, restoreSelection;
if (window.getSelection) {
    // IE 9 and non-IE
    saveSelection = function(win) {
        var sel = win.getSelection(), ranges = [];
        if (sel.rangeCount) {
            for (var i = 0, len = sel.rangeCount; i < len; ++i) {
                ranges.push(sel.getRangeAt(i));
            }
        }
        return ranges;
    };

    restoreSelection = function(win, savedSelection) {
        var sel = win.getSelection();
        sel.removeAllRanges();
        for (var i = 0, len = savedSelection.length; i < len; ++i) {
            sel.addRange(savedSelection[i]);
        }
    };
} else if (document.selection && document.selection.createRange) {
    // IE <= 8
    saveSelection = function(win) {
        var sel = win.document.selection;
        return (sel.type != "None") ? sel.createRange() : null;
    };

    restoreSelection = function(win, savedSelection) {
        if (savedSelection) {
            savedSelection.select();
        }
    };
}

And I use it like this:

$('div').on('keyup paste', function(e) {
    var ranges = saveSelection(window);
    var newContent = /* Get the new HTML content */;
    $('div').html(newContent);
    restoreSelection(window, ranges);
});
Community
  • 1
  • 1
Matt Roberts
  • 26,371
  • 31
  • 103
  • 180

1 Answers1

6

If the text the user sees remains the same (which your question seems to imply is the case), you could use a character offset-based solution. I've posted some code for that elsewhere on Stack Overflow:

https://stackoverflow.com/a/13950376/96100

I also answered a related question quite recently:

jQuery: Convert text URL to link as typing

Community
  • 1
  • 1
Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • That's perfect (The first link did the job) – Matt Roberts Apr 25 '13 at 08:19
  • Just spotted a bug with this solution. All works but if you hit enter for a new line in the div, the cursor is set back on the previous line (presumably because of the divs that get entered). – Matt Roberts Apr 25 '13 at 09:11
  • @MattRoberts: Yes. There are limitations to this method, which is based only on text nodes and therefore doesn't take into account line breaks implied by `
    ` and block elements.
    – Tim Down Apr 25 '13 at 09:13
  • Perhaps if I detect a keyCode of 13 (enter key) then write some logic to go to the last position in the div... – Matt Roberts Apr 25 '13 at 09:20
  • @MattRoberts: I have written a fuller solution for my Rangy library (http://rangy.googlecode.com/svn/trunk/demos/textrange.html) but the code is enormous. I may try and come up with a compromise some day. – Tim Down Apr 25 '13 at 09:26
  • So if I use your rangy lib, would it solve all these issues? Would I need to use the saverestore module, the textrange module or both? – Matt Roberts Apr 25 '13 at 10:23
  • @MattRoberts: Just the TextRange module. It handles all the line break issues. There are `saveCharacterRanges()` and `restoreCharacterRanges()` methods of Rangy's selection object. Docs: https://code.google.com/p/rangy/wiki/TextRangeModule – Tim Down Apr 25 '13 at 10:42
  • 1
    It seems to play well in FF, but not in chrome - added a simple jsfiddle for you : http://jsfiddle.net/mattwoberts/xjhsh/ - just try hitting return in chrome in that editable div. – Matt Roberts Apr 25 '13 at 15:44
  • @MattRoberts: Hmm, you're right. I thought it handled that case. I'll look into it. – Tim Down Apr 25 '13 at 21:54