JavaScript Contenteditable - Set Cursor/Caret to Index

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>

Set caret position in a content editable element

Try this:

Just replace range.setStart(el, 2) with range.setStart(el.childNodes[0], 2)

var el = document.getElementsByTagName('div')[0];var range = document.createRange();var sel = window.getSelection();range.setStart(el.childNodes[0], 2);range.collapse(true);sel.removeAllRanges();sel.addRange(range);el.focus();
<div contenteditable>Hi ! How are you doing ?</div>

Javascript Contenteditable - set Cursor / Caret to index

Here's an answer adapted from Persisting the changes of range objects after selection in HTML. Bear in mind that this is less than perfect in several ways (as is MaxArt's, which uses the same approach): firstly, only text nodes are taken into account, meaning that line breaks implied by <br> and block elements are not included in the index; secondly, all text nodes are considered, even those inside elements that are hidden by CSS or inside <script> elements; thirdly, consecutive white space characters that are collapsed on the page are all included in the index; finally, IE <= 8's rules are different again because it uses a different mechanism.

var setSelectionByCharacterOffsets = null;

if (window.getSelection && document.createRange) {
setSelectionByCharacterOffsets = function(containerEl, start, end) {
var charIndex = 0, range = document.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl], node, foundStart = false, stop = false;

while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && start >= charIndex && start <= nextCharIndex) {
range.setStart(node, start - charIndex);
foundStart = true;
}
if (foundStart && end >= charIndex && end <= nextCharIndex) {
range.setEnd(node, end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}

var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
} else if (document.selection) {
setSelectionByCharacterOffsets = function(containerEl, start, end) {
var textRange = document.body.createTextRange();
textRange.moveToElementText(containerEl);
textRange.collapse(true);
textRange.moveEnd("character", end);
textRange.moveStart("character", start);
textRange.select();
};
}

Set Caret Position in 'contenteditable' div that has children

So, I was experiencing the same issue and decided to write my own routine quickly, it walks through all the child nodes recursively and set the position.
Note how this takes a DOM node as argument, not a jquery object as your original post does

// Move caret to a specific point in a DOM element
function SetCaretPosition(el, pos){

// Loop through all child nodes
for(var node of el.childNodes){
if(node.nodeType == 3){ // we have a text node
if(node.length >= pos){
// finally add our range
var range = document.createRange(),
sel = window.getSelection();
range.setStart(node,pos);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
return -1; // we are done
}else{
pos -= node.length;
}
}else{
pos = SetCaretPosition(node,pos);
if(pos == -1){
return -1; // no need to finish the for loop
}
}
}
return pos; // needed because of recursion stuff
}

I hope this'll help you!

Set caret position at a specific position in contenteditable div

You need to position the caret within the text node inside your element, not the element itself. Assuming your HTML looks something like <div contenteditable="true">Some text</div>, using the firstChild property of the element will get the text node.

Updated jsFiddle:

http://jsfiddle.net/xgz6L/8/

Code:

var node = document.querySelector("div");
node.focus();
var textNode = node.firstChild;
var caret = 10; // insert caret after the 10th character say
var range = document.createRange();
range.setStart(textNode, caret);
range.setEnd(textNode, caret);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);

How to set caret position in contenteditable span and then sendKeys?

You can perform a click on the element with an offset, e.g.:

var elm = element(by.name("workitemContent"));
browser.actions().mouseMove(elm, 5, 0).click().perform();

But, I'm not sure whether you can call it a reliable approach.


Another option would be to follow the solution provided in the question you've linked and execute javascript via browser.executeScript():

var elm = element(by.name("workitemContent"));

function setCursor(arguments) {
var range = document.createRange();
var sel = window.getSelection();
range.setStart(arguments[0].childNodes[2], arguments[1]);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
browser.executeScript(setCursor, elm.getWebElement(), 5);

Get caret (cursor) position in contentEditable area containing HTML content

UPDATE

I've written a simpler version of this that also works in IE < 9:

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

Old Answer

This is actually a more useful result than a character offset within the text of the whole document: the startOffset property of a DOM Range (which is what window.getSelection().getRangeAt() returns) is an offset relative to its startContainer property (which isn't necessarily always a text node, by the way). However, if you really want a character offset, here's a function that will do it.

Here's a live example: http://jsfiddle.net/timdown/2YcaX/

Here's the function:

function getCharacterOffsetWithin(range, node) {
var treeWalker = document.createTreeWalker(
node,
NodeFilter.SHOW_TEXT,
function(node) {
var nodeRange = document.createRange();
nodeRange.selectNode(node);
return nodeRange.compareBoundaryPoints(Range.END_TO_END, range) < 1 ?
NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
},
false
);

var charCount = 0;
while (treeWalker.nextNode()) {
charCount += treeWalker.currentNode.length;
}
if (range.startContainer.nodeType == 3) {
charCount += range.startOffset;
}
return charCount;
}

Get contentEditable caret position

The following code assumes:

  • There is always a single text node within the editable <div> and no other nodes
  • The editable div does not have the CSS white-space property set to pre

If you need a more general approach that will work content with nested elements, try this answer:

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

Code:

function getCaretPosition(editableDiv) {  var caretPos = 0,    sel, range;  if (window.getSelection) {    sel = window.getSelection();    if (sel.rangeCount) {      range = sel.getRangeAt(0);      if (range.commonAncestorContainer.parentNode == editableDiv) {        caretPos = range.endOffset;      }    }  } else if (document.selection && document.selection.createRange) {    range = document.selection.createRange();    if (range.parentElement() == editableDiv) {      var tempEl = document.createElement("span");      editableDiv.insertBefore(tempEl, editableDiv.firstChild);      var tempRange = range.duplicate();      tempRange.moveToElementText(tempEl);      tempRange.setEndPoint("EndToEnd", range);      caretPos = tempRange.text.length;    }  }  return caretPos;}
#caretposition {  font-weight: bold;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script><div id="contentbox" contenteditable="true">Click me and move cursor with keys or mouse</div><div id="caretposition">0</div><script>  var update = function() {    $('#caretposition').html(getCaretPosition(this));  };  $('#contentbox').on("mousedown mouseup keydown keyup", update);</script>

Place cursor(caret) in specific position in pre contenteditable element

With some help from these functions from here ->

Add element before/after text selection

I've created something I think your after.
I basically place some temporary tags into the html where the current cursor is. I then render the new HTML, I then replace the tags with the span with a data-cpos attribute. Using this I then re-select the cursor.

var insertHtmlBeforeSelection, insertHtmlAfterSelection;
(function() { function createInserter(isBefore) { return function(html) { var sel, range, node; if (window.getSelection) { // IE9 and non-IE sel = window.getSelection(); if (sel.getRangeAt && sel.rangeCount) { range = window.getSelection().getRangeAt(0); range.collapse(isBefore); // Range.createContextualFragment() would be useful here but is // non-standard and not supported in all browsers (IE9, for one) var el = document.createElement("div"); el.innerHTML = html; var frag = document.createDocumentFragment(), node, lastNode; while ( (node = el.firstChild) ) { lastNode = frag.appendChild(node); } range.insertNode(frag); } } else if (document.selection && document.selection.createRange) { // IE < 9 range = document.selection.createRange(); range.collapse(isBefore); range.pasteHTML(html); } } } insertHtmlBeforeSelection = createInserter(true); insertHtmlAfterSelection = createInserter(false);})();

function refreshInnerHtml() { var tag_start = '⇷', //lets use some unicode chars unlikely to ever use.. tag_end = '⇸', sel = document.getSelection(), input = document.getElementById('textInput'); //remove old data-cpos [].forEach.call( input.querySelectorAll('[data-cpos]'), function(e) { e.remove() }); //insert the tags at current cursor position insertHtmlBeforeSelection(tag_start); insertHtmlAfterSelection(tag_end); //now do our replace let html = input.innerText.replace(/(ab)/g, '<span style="background-color: lightgreen">$1</span>'); input.innerHTML = html.replace(tag_start,'<span data-cpos>').replace(tag_end,'</span>'); //now put cursor back var e = input.querySelector('[data-cpos]'); if (e) { var range = document.createRange(); range.setStart(e, 0); range.setEnd(e, 0); sel.removeAllRanges(); sel.addRange(range); }}
refreshInnerHtml();
Type some text below, with the letters 'ab' somewhere within it. <br>
<pre contenteditable onkeyup="refreshInnerHtml()" id="textInput" style="border: 1px solid black;" >It's about time.. above and beyond</pre>


Related Topics



Leave a reply



Submit