contenteditable, set caret at the end of the text (cross-browser)
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>
Set the caret position always to end in contenteditable div
I got the solution here thanks to Tim down :). The problem was that I was calling
placeCaretAtEnd($('#result'));
Instead of
placeCaretAtEnd(($('#result').get(0));
as mentioned by jwarzech in the comments.
Working Fiddle
How to set caret position of a contenteditable div containing combination of text and element nodes
Your textNode
has 3 children (1 text, 1 element, 1 text) and therefore you can't just use firstChild
.
You need to iterate over the childNodes
of the <div>
and track the character count where the nodeType
of the childNode
equals Node.TEXT_NODE
(see here on MDN). Where the character count is less than the value of caret
you can deduct that from caret
and move onto the next text node.
Per your condition that:
I desire and each image would be treated as 1 character
The code will deduct 1 from caret
where nodeType == 1
i.e. Node.ELEMENT_NODE
Here is a code example with multiple icons:
var node = document.querySelector("div");node.focus();var caret = 24;
var child;var childNodeIndex = 0;for(var i=0; i<node.childNodes.length; i++) { child = node.childNodes[i]; // Node.ELEMENT_NODE == 1 // Node.TEXT_NODE == 3 if(child.nodeType == Node.TEXT_NODE) { // keep track of caret across text childNodes if(child.length <= caret) { caret -= child.length; } else { break; } } else if (child.nodeType == Node.ELEMENT_NODE) { // condition that 'each image would be treated as 1 character' if(caret > 0) { caret -= 1; } else { break; } }; childNodeIndex += 1;}
var textNode = node.childNodes[childNodeIndex];
// your original code continues here...var range = document.createRange();range.setStart(textNode, caret);range.setEnd(textNode, caret);var sel = window.getSelection();sel.removeAllRanges();sel.addRange(range);
<div id="text" contenteditable="true">a<img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/><img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/><img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/><img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/>b<img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/>cdefghijkl<img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/>mnopq<img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/>rst<img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/>uvw<img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/>xyz<img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/></div>
How to set the caret (cursor) position in a contenteditable element (div)?
In most browsers, you need the Range
and Selection
objects. You specify each of the selection boundaries as a node and an offset within that node. For example, to set the caret to the fifth character of the second line of text, you'd do the following:
function setCaret() {
var el = document.getElementById("editable")
var range = document.createRange()
var sel = window.getSelection()
range.setStart(el.childNodes[2], 5)
range.collapse(true)
sel.removeAllRanges()
sel.addRange(range)
}
<div id="editable" contenteditable="true">
text text text<br>text text text<br>text text text<br>
</div>
<button id="button" onclick="setCaret()">focus</button>
How to move cursor to end of contenteditable entity
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
Related Topics
Can Scripts Be Inserted With Innerhtml
How to Detect If JavaScript Is Disabled
How to Add Onload Event to a Div Element
Why Is the Value of My Input Always Empty If I Store It in a Variable
How to Properly Use Jspdf Library
Remember and Repopulate File Input
Getting the Parent Div of Element
Js Function Named 'Animate' Doesn't Work in Chrome, But Works in Ie
After Submitting a Post Form Open a New Window Showing the Result
How to Do a Jquery Callback After Form Submit
How to Save Canvas as Png Image
How to Add an "Add to Favorites" Button or Link on My Website
Submit Form Without Page Reloading