contenteditable, set caret at the end of the text (cross-browser)
Asked Answered
K

4

132

output in Chrome:

<div id="content" contenteditable="true" style="border:1px solid #000;width:500px;height:40px;">
    hey
    <div>what's up?</div>
<div>
<button id="insert_caret"></button>

I believe in FF it would look something like this:

hey
<br />
what's up?

and in IE:

hey
<p>what's up?</p>

unfortunately, there is no nice way of making it so that every browser inserts a <br /> instead of a div- or p-tag, or at least I couldn't find anything online.


ANYWAY, what I am trying to do now is, when I hit the button, I want the caret to be set at the end of the text, so it should look something like this:

hey
what's up?|

any way to do this so it works in all browser?

example:

$(document).ready(function()
{
    $('#insert_caret').click(function()
    {
        var ele = $('#content');
        var length = ele.html().length;

        ele.focus();

        //set caret -> end pos
     }
 }
Katelin answered 20/11, 2010 at 14:49 Comment(1)
This worked best for meConsolidate
K
321

The following function will do it in all major browsers:

function placeCaretAtEnd(el) {
    el.focus();
    if (typeof window.getSelection != "undefined"
            && typeof document.createRange != "undefined") {
        var range = document.createRange();
        range.selectNodeContents(el);
        range.collapse(false);
        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    } else if (typeof document.body.createTextRange != "undefined") {
        var textRange = document.body.createTextRange();
        textRange.moveToElementText(el);
        textRange.collapse(false);
        textRange.select();
    }
}

placeCaretAtEnd( document.querySelector('p') );
p{ padding:.5em; border:1px solid black; }
<p contentEditable>foo bar </p>

Placing the caret at the start is almost identical: it just requires changing the Boolean passed into the calls to collapse(). Here's an example that creates functions for placing the caret at the start and at the end:

function createCaretPlacer(atStart) {
    return function(el) {
        el.focus();
        if (typeof window.getSelection != "undefined"
                && typeof document.createRange != "undefined") {
            var range = document.createRange();
            range.selectNodeContents(el);
            range.collapse(atStart);
            var sel = window.getSelection();
            sel.removeAllRanges();
            sel.addRange(range);
        } else if (typeof document.body.createTextRange != "undefined") {
            var textRange = document.body.createTextRange();
            textRange.moveToElementText(el);
            textRange.collapse(atStart);
            textRange.select();
        }
    };
}

var placeCaretAtStart = createCaretPlacer(true);
var placeCaretAtEnd = createCaretPlacer(false);
Kinin answered 21/11, 2010 at 16:46 Comment(14)
Does not work with Chrome because createTextRange is not a standard function. See https://mcmap.net/q/83404/-createtextrange-is-not-working-in-chrome.Tamekia
@Lee: It works fine in Chrome, which supports window.getSelection and document.createRange. The createTextRange branch is for old versions of Internet Explorer.Kinin
at the time of writing window.getSelection is not supported by 0.29% of all browsers (IE>8). see: caniuse.com/#search=window.getSelectionOballa
@TimDown this works. +1. But can you also explain the code by adding comments please? Would be greatWolsky
Anyone knows how to set the caret to the end (use this code) when trying to target an iframe > body element? The body element has editablecontent="true"...? placeCaretAtEnd(this); won't work for me.Leonelleonelle
@TimDown - Took the liberty adding a demo. I face a difficult problem where such a solution doesn't work when there's a trailing space. I saw you faced a similar problem, some years back in one of the github repos. any insight on the problem?Elvaelvah
@TimDown This solution is working chrome, but in my case it does not work in firefox. Can you please help ?..Issue in firefox is like cursor is pointing to last character but when i type a single character, cursor jumps on to first character and writing after will add characters from right to left(Islamic language)..Adoptive
@AlpeshPrajapati: Do you have a demo page I can look at?Kinin
@TimDown Thank you very much for your response. But now I think I got the issue why it was like that. It was data-binding issue and updating at the same time. Thanks to the blog: mutuallyhuman.com/blog/2018/05/03/… ...Read the section "Asynchronous saves and data refreshes to the server"Adoptive
Does it work with nested hey <p>what's up?</p>? Where the caret is put: right after "?" mark or after closing "</p>" tag?Bedclothes
@user1432181: You've got a typo in "contenteditable" and you can't call focus() on an element within the editable content. Here's a working example: jsfiddle.net/c0md8u5nKinin
Here's the exact same function, compressed: function placeCaretAtEnd(el){el.focus();var u="undefined",w=window,r,s,t;if(typeof w.getSelection!=u&&typeof document.createRange!=u){r=document.createRange();r.selectNodeContents(el);r.collapse(false);s=w.getSelection();s.removeAllRanges();s.addRange(r)}else if(typeof document.body.createTextRange!=u){t=document.body.createTextRange();t.moveToElementText(el);t.collapse(false);t.select()}}. Works great for me, everywhere.Ascanius
I was about to correct another question that used this code, to cite this answer as the source, but then realized it's been quoted several times, so I'm not sure whether or not @TimDown wrote it and don't know who to credit... alas, I tried.Ascanius
@ashleedawg: I definitely wrote this code.Kinin
D
10

Unfortunately Tim's excellent answer worked for me only for placing at the end, for placing at the start I had to modify it slightly.

function setCaret(target, isStart) {
  const range = document.createRange();
  const sel = window.getSelection();
  if (isStart){
    const newText = document.createTextNode('');
    target.appendChild(newText);
    range.setStart(target.childNodes[0], 0);
  }
  else {
    range.selectNodeContents(target);
  }
  range.collapse(isStart);
  sel.removeAllRanges();
  sel.addRange(range);
  target.focus();
  target.select();
}

Not sure though if focus() and select() are actually needed.

Drachm answered 14/6, 2017 at 10:25 Comment(3)
You also introduced an external library. If you don't think focus and select is required why not comment out the lines and test it?Zetes
Thanks, removed jquery. I remember I had some contradicting results, I'll try to isolate them again.Drachm
focus() places the caret at the beginning for me with no additional code requiredAscanius
E
6

This (live) example shows a short simple function, setCaretAtStartEnd, which takes two arguments; A (editable) node to place the caret at & a Boolean indicating where to place it (start or end of the node)

const editableElm = document.querySelector('[contenteditable]');

document.querySelectorAll('button').forEach((elm, idx) => 
  elm.addEventListener('click', () => {
    editableElm.focus()
    setCaretAtStartEnd(editableElm, idx) 
  })
)

function setCaretAtStartEnd( node, atEnd ){
  const sel = document.getSelection();
  node = node.firstChild;

  if( sel.rangeCount ){
      ['Start', 'End'].forEach(pos =>
        sel.getRangeAt(0)["set" + pos](node, atEnd ? node.length : 0)
      )
  }
}
[contenteditable]{ padding:5px; border:1px solid; }
<h1 contenteditable>Place the caret anywhere</h1>
<br>
<button>Move caret to start</button>
<button>Move caret to end</button>
Elvaelvah answered 3/12, 2019 at 12:12 Comment(1)
This seems to work ok : jsfiddle.net/Abeeee/7me8dkpbRagnar
F
-1

If you are using the google closure compiler, you can do the following (somewhat simplified from Tim's answer):

function placeCaretAtEnd(el) {
    el.focus();
    range = goog.dom.Range.createFromNodeContents(el);
    range.collapse(false);
    range.select();
}

Here's the same thing in ClojureScript:

(defn place-caret-at-end [el] 
   (.focus el)
   (doto (.createFromNodeContents goog.dom.Range el)
         (.collapse false)
         .select))

I have tested this in Chrome, Safari and FireFox, not sure about IE...

Flippant answered 18/2, 2016 at 16:0 Comment(2)
range = goog.dom.Range.createFromNodeContents(el); what's goog.dom?Linalool
This worked for me. @AnhTú: goog.dom is a namespace provided by closure-library.Drupelet

© 2022 - 2024 — McMap. All rights reserved.