How to Move Cursor to End of Contenteditable Entity

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

Move cursor to end of contenteditable

The first answer you looked at 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.

How to move cursor to end of contenteditable entity

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

Set cursor to the end of contenteditable div

you are using jQuery and getting the element from jQuery. You must convert it into a native DOM element (via the jQuery-get Function) to use the "setCurserToEnd"-Function:

your method-call:

setCursorToEnd(this.prev());

working solution:

setCursorToEnd($(this).prev().get(0));

see the updated fiddle: http://jsfiddle.net/dvH5r/4/

Change contenteditable div HTML and move cursor to end don't works

Your selector was wrong, you are using a div with the class .input rather than an actual input element.

Also, you should prefix your variables (by convention) with a $ if the value is going to be a jQuery element. This will reduce confusion later on in the code.

jQuery(document).ready(function($) {  let $input = $("#input");  setTimeout(function() {    $input.html($input.html() + "and I'm <b>very bold</b>");    placeCaretAtEnd($input[0]);  }, 1000);});
/** * @param {Element} el - A Native DOM Element */function placeCaretAtEnd(el) { el.focus(); if (typeof window.getSelection !== "undefined" && typeof document.createRange !== "undefined") { let range = document.createRange(); range.selectNodeContents(el); range.collapse(false); let sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } else if (typeof document.body.createTextRange !== "undefined") { let textRange = document.body.createTextRange(); textRange.moveToElementText(el); textRange.collapse(false); textRange.select(); }}
[contenteditable=true] {  border: 1px solid #aaaaaa;  padding: 8px;  border-radius: 12px;  margin-bottom: 20px;  white-space: pre-wrap;  word-wrap: break-word;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script><div id="input" contenteditable="true" spellcheck="true">I'm a <span>text</span> </div>

How to move cursor to beginning/end of ContentEditable Span

In your keydown function, you change your event target for the next (or previous) element when you reach the end (or the beginning) of it.

What is going on after you change the target? The keydown default behavior. So if you were typing the right arrow, you reach the end, you change your target... and your cursor is moved to the right since it is the default behavior.

What is the solution? You can prevent the default behavior using return false. So the code for the right arrow in your keydown event would be something like this:

        if (event.keyCode == 39) { //Right arrow
var nextSpan = selectedToken.next();
if (nextSpan.length) {
nextSpan.focus();
selectedToken.removeClass('selectedToken');
}
return false;
}

Here is your updated fiddle.

Set cursor position on contentEditable div

This is compatible with the standards-based browsers, but will probably fail in IE. I'm providing it as a starting point. IE doesn't support DOM Range.

var editable = document.getElementById('editable'),
selection, range;

// Populates selection and range variables
var captureSelection = function(e) {
// Don't capture selection outside editable region
var isOrContainsAnchor = false,
isOrContainsFocus = false,
sel = window.getSelection(),
parentAnchor = sel.anchorNode,
parentFocus = sel.focusNode;

while(parentAnchor && parentAnchor != document.documentElement) {
if(parentAnchor == editable) {
isOrContainsAnchor = true;
}
parentAnchor = parentAnchor.parentNode;
}

while(parentFocus && parentFocus != document.documentElement) {
if(parentFocus == editable) {
isOrContainsFocus = true;
}
parentFocus = parentFocus.parentNode;
}

if(!isOrContainsAnchor || !isOrContainsFocus) {
return;
}

selection = window.getSelection();

// Get range (standards)
if(selection.getRangeAt !== undefined) {
range = selection.getRangeAt(0);

// Get range (Safari 2)
} else if(
document.createRange &&
selection.anchorNode &&
selection.anchorOffset &&
selection.focusNode &&
selection.focusOffset
) {
range = document.createRange();
range.setStart(selection.anchorNode, selection.anchorOffset);
range.setEnd(selection.focusNode, selection.focusOffset);
} else {
// Failure here, not handled by the rest of the script.
// Probably IE or some older browser
}
};

// Recalculate selection while typing
editable.onkeyup = captureSelection;

// Recalculate selection after clicking/drag-selecting
editable.onmousedown = function(e) {
editable.className = editable.className + ' selecting';
};
document.onmouseup = function(e) {
if(editable.className.match(/\sselecting(\s|$)/)) {
editable.className = editable.className.replace(/ selecting(\s|$)/, '');
captureSelection();
}
};

editable.onblur = function(e) {
var cursorStart = document.createElement('span'),
collapsed = !!range.collapsed;

cursorStart.id = 'cursorStart';
cursorStart.appendChild(document.createTextNode('—'));

// Insert beginning cursor marker
range.insertNode(cursorStart);

// Insert end cursor marker if any text is selected
if(!collapsed) {
var cursorEnd = document.createElement('span');
cursorEnd.id = 'cursorEnd';
range.collapse();
range.insertNode(cursorEnd);
}
};

// Add callbacks to afterFocus to be called after cursor is replaced
// if you like, this would be useful for styling buttons and so on
var afterFocus = [];
editable.onfocus = function(e) {
// Slight delay will avoid the initial selection
// (at start or of contents depending on browser) being mistaken
setTimeout(function() {
var cursorStart = document.getElementById('cursorStart'),
cursorEnd = document.getElementById('cursorEnd');

// Don't do anything if user is creating a new selection
if(editable.className.match(/\sselecting(\s|$)/)) {
if(cursorStart) {
cursorStart.parentNode.removeChild(cursorStart);
}
if(cursorEnd) {
cursorEnd.parentNode.removeChild(cursorEnd);
}
} else if(cursorStart) {
captureSelection();
var range = document.createRange();

if(cursorEnd) {
range.setStartAfter(cursorStart);
range.setEndBefore(cursorEnd);

// Delete cursor markers
cursorStart.parentNode.removeChild(cursorStart);
cursorEnd.parentNode.removeChild(cursorEnd);

// Select range
selection.removeAllRanges();
selection.addRange(range);
} else {
range.selectNode(cursorStart);

// Select range
selection.removeAllRanges();
selection.addRange(range);

// Delete cursor marker
document.execCommand('delete', false, null);
}
}

// Call callbacks here
for(var i = 0; i < afterFocus.length; i++) {
afterFocus[i]();
}
afterFocus = [];

// Register selection again
captureSelection();
}, 10);
};

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>


Related Topics



Leave a reply



Submit