13

I'm looking to move the caret exactly four spaces ahead of its current position so that I can insert a tab properly. I've already got the HTML insertion at the caret's position working, but when I insert the HTML, the caret is left behind. I've spent the past hour or so looking at various ways to do this and I've tried plenty of them, but I can't get any of them to work for me. Here's the most recent method I've tried:

function moveCaret(input, distance) {
    if(input.setSelectionRange) {
        input.focus();
        input.setSelectionRange(distance, distance);
    } else if(input.createTextRange) {
        var range = input.createTextRange();
        range.collapse(true);
        range.moveEnd(distance);
        range.moveStart(distance);
        range.select();
    }
}

It does absolutely nothing--doesn't move the caret, throw any errors or anything. This leaves me baffled. And yes, I know that the above method set (is supposed to) set the caret at a certain position from the beginning of the specified node (that is, input), but even that's not working. So, what exactly am I doing wrong, and how can I do it right?


Edit: Based on the links that o.v. provided, I've managed to cobble something together that's finally doing something: throwing an error. Yay! Here's the new code:

this.moveCaret = function(distance) {
    if(that.win.getSelection) {
        var range = that.win.getSelection().getRangeAt(0);
        range.setStart(range.startOffset + distance);
    } else if (that.win.document.selection) {
        var range = that.win.document.selection.createRange();
        range.setStart(range.startOffset + distance);
    }
}

Now, this gives the error Uncaught Error: NOT_FOUND_ERR: DOM Exception 8. Any ideas why?

Elliot Bonneville
  • 51,872
  • 23
  • 96
  • 123
  • Did you try setting the `element.selectionStart` and `element.selectionEnd`? It should work cross-browser for input and textareas as far as I remember. – Fabrício Matté May 28 '12 at 00:49
  • I was avoiding it because I believe it doesn't working in earlier versions of IE8. I'll take a look at it anyhow, though. – Elliot Bonneville May 28 '12 at 00:50
  • The only IE version I test with is IE9, best of luck though. :) – Fabrício Matté May 28 '12 at 00:51
  • Yes, it would appear `selectionStart` doesn't work in earlier versions of IE, but thanks anyhow. =) – Elliot Bonneville May 28 '12 at 00:52
  • @ElliotBonneville: umm, what's `that` referring to in your edit? I don't see it being assigned – Oleg May 28 '12 at 02:01
  • @o.v.: Oh, sorry. `that` is a reference to an object that has a `window` object on it. `win` is either an opened window or the standard window. – Elliot Bonneville May 28 '12 at 03:11
  • The DOM error is probably if the new offset doesn't exist (is past the end of the element). Also note that start offset refers to the number from the characters the start of the node if the selection is a text node, but the number of nodes from the start if the node is an element (confusing I know). I might try and write an answer to this, but it's a pretty difficult task to acheive, especially if you want to cover cases with formatted text (e.g. tags, etc) as well as just plain text. – Nico Burns May 28 '12 at 03:18
  • @ElliotBonneville: looking through the other answers I've linked to, I'm strongly under the impression that an explicit reference to a contenteditable element has to be maintained. Are you able to add a barebones jsfiddle with your method? This is rather intriguing – Oleg May 28 '12 at 03:44
  • @NicoBurns: Yes, I realized that the offset being past the end of the element might cause the error. I tried with different offsets but came to the conclusion that that wasn't the source of the error. And no, I'm not planning on writing a WYSIWYG editor, this is for something else. There won't be other elements in the text (style or otherwise). I can give you a sample of the HTML if you need it to write an answer. – Elliot Bonneville May 28 '12 at 04:48
  • Possible duplicate: http://stackoverflow.com/questions/1181700/set-cursor-position-on-contenteditable-div [This](http://stackoverflow.com/a/1192681/1081234) and [this](http://stackoverflow.com/a/3866442/1081234) answer appear to have received a lot of recognition. – Oleg May 28 '12 at 00:55

1 Answers1

25

The code snippet you have is for text inputs and textareas, not contenteditable elements.

Provided that all your content is in a single text node and the selection is completely contained within it, the following will work in all major browsers, including IE 6.

Demo: http://jsfiddle.net/9sdrZ/

Code:

function moveCaret(win, charCount) {
    var sel, range;
    if (win.getSelection) {
        // IE9+ and other browsers
        sel = win.getSelection();
        if (sel.rangeCount > 0) {
            var textNode = sel.focusNode;
            var newOffset = sel.focusOffset + charCount;
            sel.collapse(textNode, Math.min(textNode.length, newOffset));
        }
    } else if ( (sel = win.document.selection) ) {
        // IE <= 8
        if (sel.type != "Control") {
            range = sel.createRange();
            range.move("character", charCount);
            range.select();
        }
    }
}
Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • 1
    +1 because it's a really useful snippet of code. Just a question: why do you access the global via the win argument? – Andrea Parodi Feb 03 '14 at 19:23
  • 1
    @AndreaParodi: To allow use with an iframe. In this case it's the `window` host object we're interested in rather than its other role as global object. For an iframe, you'd call `moveCaret(iframeElement.contentWindow, charCount)`. – Tim Down Feb 03 '14 at 21:02
  • 1
    However if you need to move to the previous text node, this fails as `sel.focusNode` == 0 – adib Nov 18 '15 at 14:20
  • @adib: Yep, hence the proviso at the start of the answer. – Tim Down Nov 18 '15 at 15:51
  • Somehow this is an acceptable answer on Stackoverflow, without any kind of explanation whatsoever. – dmr07 Jul 01 '16 at 05:08
  • 1
    @danm07: What kind of explanation do you want? The Range and Selection methods are standard and well documented. The code is short and simple. Also, if you want to annotate it with comments, it's possible for you to do so. The only comments I feel might be useful would be labels for the two main code branches (IE 9+ and other browsers, IE 6 - 8). – Tim Down Jul 01 '16 at 13:47
  • 1
    Giving pre-made solution does little in filling knowledge gaps. It would have been nice for you to point out what went wrong in the questioner's code, I'm pretty sure this is Stacks etiquette. – dmr07 Jul 01 '16 at 19:58
  • 2
    @danm07: I'm guilty of that in hundreds of answers. I don't think it's universal practice to add a running commentary to all code on SO; I'm not sure whether I think it should be. Does more hand-holding encourage or discourage people from looking up the browser APIs I've used here? I don't know. Anyway, I've added a little detail in this answer. – Tim Down Jul 05 '16 at 16:31
  • This does not work if the ContentEditable has multiple text nodes in it already and you need to move the cursor outside it. – bnovc Jan 21 '18 at 20:03
  • @bnovc: Yes. The answer explicitly says that. – Tim Down Jan 23 '18 at 10:18
  • Is it possible to crank the same trick only for a text field? – Pablo Sep 27 '18 at 08:18