How to move cursor to end of contenteditable entity

JavascriptContenteditableCursor Position

Javascript Problem Overview


I need to move caret to end of contenteditable node like on Gmail notes widget.

I read threads on StackOverflow, but those solutions are based on using inputs and they doesn't work with contenteditable elements.

Javascript Solutions


Solution 1 - Javascript

Geowa4's solution will work for a textarea, but not for a contenteditable element.

This solution is for moving the caret to the end of a contenteditable element. It should work in all browsers which support contenteditable.

function setEndOfContenteditable(contentEditableElement)
{
	var range,selection;
	if(document.createRange)//Firefox, Chrome, Opera, Safari, IE 9+
	{
		range = document.createRange();//Create a range (a range is a like the selection but invisible)
		range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range
		range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
		selection = window.getSelection();//get the selection object (allows you to change selection)
		selection.removeAllRanges();//remove any selections already made
		selection.addRange(range);//make the range you have just created the visible selection
	}
	else if(document.selection)//IE 8 and lower
	{ 
		range = document.body.createTextRange();//Create a range (a range is a like the selection but invisible)
		range.moveToElementText(contentEditableElement);//Select the entire contents of the element with the range
		range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
		range.select();//Select the range (make it the visible selection
	}
}

It can be used by code similar to:

elem = document.getElementById('txt1');//This is the element that you want to move the caret to the end of
setEndOfContenteditable(elem);

Solution 2 - Javascript

If you don't care about older browsers, this one did the trick for me.

// [optional] make sure focus is on the element
yourContentEditableElement.focus();
// select all the content in the element
document.execCommand('selectAll', false, null);
// collapse selection to the end
document.getSelection().collapseToEnd();

Solution 3 - Javascript

There is also another problem.

The Nico Burns's solution works if the contenteditable div doesn't contain other multilined elements.

For instance, if a div contains other divs, and these other divs contain other stuff inside, could occur some problems.

In order to solve them, I've arranged the following solution, that is an improvement of the Nico's one:

//Namespace management idea from http://enterprisejquery.com/2010/10/how-good-c-habits-can-encourage-bad-javascript-habits-part-1/
(function( cursorManager ) {

    //From: http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
    var voidNodeTags = ['AREA', 'BASE', 'BR', 'COL', 'EMBED', 'HR', 'IMG', 'INPUT', 'KEYGEN', 'LINK', 'MENUITEM', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR', 'BASEFONT', 'BGSOUND', 'FRAME', 'ISINDEX'];
    
    //From: https://stackoverflow.com/questions/237104/array-containsobj-in-javascript
    Array.prototype.contains = function(obj) {
        var i = this.length;
        while (i--) {
            if (this[i] === obj) {
                return true;
            }
        }
        return false;
    }
    
    //Basic idea from: https://stackoverflow.com/questions/19790442/test-if-an-element-can-contain-text
    function canContainText(node) {
        if(node.nodeType == 1) { //is an element node
            return !voidNodeTags.contains(node.nodeName);
        } else { //is not an element node
            return false;
        }
    };
    
    function getLastChildElement(el){
        var lc = el.lastChild;
        while(lc && lc.nodeType != 1) {
            if(lc.previousSibling)
                lc = lc.previousSibling;
            else
                break;
        }
        return lc;
    }
    
    //Based on Nico Burns's answer
    cursorManager.setEndOfContenteditable = function(contentEditableElement)
    {

        while(getLastChildElement(contentEditableElement) &&
              canContainText(getLastChildElement(contentEditableElement))) {
            contentEditableElement = getLastChildElement(contentEditableElement);
        }
    
        var range,selection;
        if(document.createRange)//Firefox, Chrome, Opera, Safari, IE 9+
        {    
            range = document.createRange();//Create a range (a range is a like the selection but invisible)
            range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range
            range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
            selection = window.getSelection();//get the selection object (allows you to change selection)
            selection.removeAllRanges();//remove any selections already made
            selection.addRange(range);//make the range you have just created the visible selection
        }
        else if(document.selection)//IE 8 and lower
        { 
            range = document.body.createTextRange();//Create a range (a range is a like the selection but invisible)
            range.moveToElementText(contentEditableElement);//Select the entire contents of the element with the range
            range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
            range.select();//Select the range (make it the visible selection
        }
    }

}( window.cursorManager = window.cursorManager || {}));

Usage:

var editableDiv = document.getElementById("my_contentEditableDiv");
cursorManager.setEndOfContenteditable(editableDiv);

In this way, the cursor is surely positioned at the end of the last element, eventually nested.

EDIT #1: In order to be more generic, the while statement should consider also all the other tags which cannot contain text. These elements are named void elements, and in this question there are some methods on how to test if an element is void. So, assuming that exists a function called canContainText that returns true if the argument is not a void element, the following line of code:

contentEditableElement.lastChild.tagName.toLowerCase() != 'br'

should be replaced with:

canContainText(getLastChildElement(contentEditableElement))

EDIT #2: The above code is fully updated, with every changes described and discussed

Solution 4 - Javascript

It's possible to do set cursor to the end through the range:

setCaretToEnd(target/*: HTMLDivElement*/) {
  const range = document.createRange();
  const sel = window.getSelection();
  range.selectNodeContents(target);
  range.collapse(false);
  sel.removeAllRanges();
  sel.addRange(range);
  target.focus();
  range.detach(); // optimization

  // set scroll to the end if multiline
  target.scrollTop = target.scrollHeight; 
}

Solution 5 - Javascript

Moving cursor to the end of editable span in response to focus event:

  moveCursorToEnd(el){
    if(el.innerText && document.createRange)
    {
      window.setTimeout(() =>
        {
          let selection = document.getSelection();
          let range = document.createRange();
      
          range.setStart(el.childNodes[0],el.innerText.length);
          range.collapse(true);
          selection.removeAllRanges();
          selection.addRange(range);
        }
      ,1);
    }
  }

And calling it in event handler (React here):

onFocus={(e) => this.moveCursorToEnd(e.target)}} 

Solution 6 - Javascript

A shorter and readable version using only selection (without range):

function setEndOfContenteditable(elem) {
    let sel = window.getSelection()
    sel.selectAllChildren(elem)
    sel.collapseToEnd()
}

<p id="pdemo" contenteditable>
A paragraph <span id="txt1" style="background-color: #0903">span text node <i>span italic</i></span> a paragraph.
<p>

<button onclick="pdemo.focus(); setEndOfContenteditable(txt1)">set caret</button>

Quite useful: https://javascript.info/selection-range

Solution 7 - Javascript

I had a similar problem trying to make a element editable. It was possible in Chrome and FireFox but in FireFox the caret either went to the beginning of the input or it went one space after the end of the input. Very confusing to the end-user I think, trying to edit the content.

I found no solution trying several things. Only thing that worked for me was to "go around the problem" by putting a plain old text-input INSIDE my . Now it works. Seems like "content-editable" is still bleeding edge tech, which may or may not work as you would like it to work, depending on the context.

Solution 8 - Javascript

The problem with contenteditable <div> and <span> is resolved when you start typing in it initially. One workaround for this could be triggering a focus event on your div element and on that function, clear, and refill what was already in the div element. This way the problem is resolved and finally you can place the cursor at the end using range and selection. Worked for me.

  moveCursorToEnd(e : any) {
    let placeholderText = e.target.innerText;
    e.target.innerText = '';
    e.target.innerText = placeholderText;

    if(e.target.innerText && document.createRange)
    {
      let range = document.createRange();
      let selection = window.getSelection();
      range.selectNodeContents(e.target);
      range.setStart(e.target.firstChild,e.target.innerText.length);
      range.setEnd(e.target.firstChild,e.target.innerText.length);
      selection.removeAllRanges();
      selection.addRange(range);
    }
  }

In HTML code:

<div contentEditable="true" (focus)="moveCursorToEnd($event)"></div>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionavsejView Question on Stackoverflow
Solution 1 - JavascriptNico BurnsView Answer on Stackoverflow
Solution 2 - JavascriptJuankView Answer on Stackoverflow
Solution 3 - JavascriptVito GentileView Answer on Stackoverflow
Solution 4 - Javascriptam0waView Answer on Stackoverflow
Solution 5 - JavascriptMaxim SaplinView Answer on Stackoverflow
Solution 6 - JavascriptFriedrichView Answer on Stackoverflow
Solution 7 - JavascriptPanu LogicView Answer on Stackoverflow
Solution 8 - JavascriptNidaView Answer on Stackoverflow