Find the caret position in a contenteditable div - javascript

I'm using this code right now to find the caret position
getCaretCharacterOffsetWithin(element) {
let caretOffset = 0;
let doc = element.ownerDocument || element.document;
let win = doc.defaultView || doc.parentWindow;
let sel;
if (typeof win.getSelection != "undefined") {
sel = win.getSelection();
if (sel.rangeCount > 0) {
let range = this.window.getSelection().getRangeAt(0);
let selected = range.toString().length; // *
let preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
caretOffset = preCaretRange.toString().length - selected; // *
}
} else if ((sel = doc.selection) && sel.type != "Control") {
let textRange = sel.createRange();
let preCaretTextRange = doc.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToEnd", textRange);
caretOffset = preCaretTextRange.text.length;
}
return caretOffset;
}
which works if my text is on one line, but if I press enter and move to a new line with text like this;
test1 test2 test3 test4 test5 test6 test7 test8 test9 test10
test11 test12 test13 testy14 test15 #x| test16 test17 test18 test19 test20
where | is my caret position, the index is wrong by a few digits, here when it should be 100 I get the value 105.
Any changes I can make to this function to make it work?
I have an alternate function that inserts a special character at the caret, finds its index and then removes it, but that also gives almost the same result.

Related

Ignore span stag in window selection to get the start and end index

I have a html tag which is
<span>This is first text<span class="ignore">Second</span> This is third text<span>
I am trying to get the start and end index from the selected text. When I select third I get start and end index as 34 39
But I expect 27 32
I tried the below approach
export const findTextRange = (element) => {
if (!element) return;
let start = 0, end = 0;
let sel, range, priorRange, text;
if (typeof window.getSelection != "undefined") {
sel = window.getSelection();
text = sel + '';
if (window.getSelection().rangeCount <= 0) {
return;
}
range = window.getSelection().getRangeAt(0);
priorRange = range.cloneRange();
priorRange.selectNodeContents(element);
priorRange.setEnd(range.startContainer, range.startOffset);
start = priorRange.toString().length;
end = start + (sel + '').length;
} else if (typeof document.selection !== "undefined" &&
(sel = document.selection).type !== "Control") {
text = sel + '';
range = sel.createRange();
priorRange = document.body.createTextRange();
priorRange.moveToElementText(element);
priorRange.setEndPoint("EndToStart", range);
start = priorRange.text.length;
end = start + (sel + '').length;
}
return { start, end, text };
}
Is there any way where I can ignore the span element with ignore class.
Store the initial HTML, then remove all elements having the .ignore class:
const html = element.innerHTML;
element.querySelectorAll('.ignore').forEach((e) => e.remove());
After getting the range, restore the original HTML:
element.innerHTML = html;
Snippet
const findTextRange = (element) => {
if (!element) return;
const html = element.innerHTML; // store original HTML
element.querySelectorAll('.ignore').forEach((e) => e.remove()); // remove ignore elements
let start = 0, end = 0;
let sel, range, priorRange, text;
if (typeof window.getSelection != "undefined") {
sel = window.getSelection();
text = sel + '';
if (window.getSelection().rangeCount <= 0) {
return;
}
range = window.getSelection().getRangeAt(0);
priorRange = range.cloneRange();
priorRange.selectNodeContents(element);
priorRange.setEnd(range.startContainer, range.startOffset);
start = priorRange.toString().length;
end = start + (sel + '').length;
} else if (typeof document.selection !== "undefined" &&
(sel = document.selection).type !== "Control") {
text = sel + '';
range = sel.createRange();
priorRange = document.body.createTextRange();
priorRange.moveToElementText(element);
priorRange.setEndPoint("EndToStart", range);
start = priorRange.text.length;
end = start + (sel + '').length;
}
element.innerHTML = html; // restore HTML
console.log(start, end, text);
return { start, end, text };
}
document.querySelector('#P').addEventListener('click', function() {findTextRange(this)});
<span id="P">This is first text<span class="ignore">Second</span> This is third text<span>

Insert Using ExecCommand at Certain Point?

I'm currently making a WYSIWYG editor but having some problems. I'm trying to add links in but when I go to add a link it takes the focus away from the div as the user must type the link in a text box.
I've got a function that gets the last position of the cursor:
<div id="editor" contenteditable="true"></div>
function getCaretCharacterOffsetWithin(element) {
var caretOffset = 0;
var doc = element.ownerDocument || element.document;
var win = doc.defaultView || doc.parentWindow;
var sel;
if (typeof win.getSelection != "undefined") {
sel = win.getSelection();
if (sel.rangeCount > 0) {
var range = win.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
caretOffset = preCaretRange.toString().length;
}
} else if ( (sel = doc.selection) && sel.type != "Control") {
var textRange = sel.createRange();
var preCaretTextRange = doc.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToEnd", textRange);
caretOffset = preCaretTextRange.text.length;
}
return caretOffset;
}
var update = function() {
getCaretCharacterOffsetWithin(this);
};
$('#editor').on("mousedown mouseup keydown keyup", update);
Is there a way to ExecCommand at the caret point?
EDIT: Added a JSFiddle to see how things work - https://jsfiddle.net/hju3bLyx/2/
you need to save the selection when editor lost focus
var savedSel;
function createLink() {
$('#editor').focus();
var url = document.getElementById("url").value;
restoreSelection(savedSel);
document.execCommand("CreateLink", false, url);
}
// it saved here
$('#editor').focusout(function(){
savedSel = saveSelection();
})

Get Selected Element based on caret position in contenteditable Div

I'm trying to get the element that the caret is in at any given time within a contenteditable DIV.
So far, I am able to get the caret position within the text.
But I want to go a step further to actually get the element selected. For instance, if the caret is after the first letter in the word "wombat" in my example, the solution would also return the id of the selected element "wombatid" and not just the caret position. Any tips or solutions?
function getSelectionCharacterOffsetWithin(element) {
var start = 0;
var end = 0;
var doc = element.ownerDocument || element.document;
var win = doc.defaultView || doc.parentWindow;
var sel;
if (typeof win.getSelection != "undefined") {
sel = win.getSelection();
if (sel.rangeCount > 0) {
var range = win.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.startContainer, range.startOffset);
start = preCaretRange.toString().length;
preCaretRange.setEnd(range.endContainer, range.endOffset);
end = preCaretRange.toString().length;
}
} else if ( (sel = doc.selection) && sel.type != "Control") {
var textRange = sel.createRange();
var preCaretTextRange = doc.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToStart", textRange);
start = preCaretTextRange.text.length;
preCaretTextRange.setEndPoint("EndToEnd", textRange);
end = preCaretTextRange.text.length;
}
return { start: start, end: end };
}
function reportSelection() {
var selOffsets = getSelectionCharacterOffsetWithin( document.getElementById("editor") );
document.getElementById("selectionLog").innerHTML = "Selection offsets: " + selOffsets.start + ", " + selOffsets.end+" <br> Selected Element's id: 'element here'";
}
window.onload = function() {
document.addEventListener("selectionchange", reportSelection, false);
document.addEventListener("mouseup", reportSelection, false);
document.addEventListener("mousedown", reportSelection, false);
document.addEventListener("keyup", reportSelection, false);
};
#editor {
padding: 5px;
border: solid green 1px;
}
Select something in the content below:
<div id="editor" contenteditable="true">A <i id="wombatid">wombat</i> is a marsupial native to <b id="australiaid">Australia</b></div>
<div id="selectionLog"></div>
To get ID you can try something like:
document.querySelectorAll("#editor *").forEach(el => el.onclick = e => alert(e.target.id));
here e.target will be your HTML element, it should work for any HTML element
function getSelectionCharacterOffsetWithin(element) {
var start = 0;
var end = 0;
var doc = element.ownerDocument || element.document;
var win = doc.defaultView || doc.parentWindow;
var sel;
if (typeof win.getSelection != "undefined") {
sel = win.getSelection();
if (sel.rangeCount > 0) {
var range = win.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.startContainer, range.startOffset);
start = preCaretRange.toString().length;
preCaretRange.setEnd(range.endContainer, range.endOffset);
end = preCaretRange.toString().length;
}
} else if ( (sel = doc.selection) && sel.type != "Control") {
var textRange = sel.createRange();
var preCaretTextRange = doc.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToStart", textRange);
start = preCaretTextRange.text.length;
preCaretTextRange.setEndPoint("EndToEnd", textRange);
end = preCaretTextRange.text.length;
}
return { start: start, end: end };
}
function reportSelection() {
var selOffsets = getSelectionCharacterOffsetWithin( document.getElementById("editor") );
document.getElementById("selectionLog").innerHTML = "Selection offsets: " + selOffsets.start + ", " + selOffsets.end+" <br> Selected Element's id: 'element here'";
}
window.onload = function() {
document.addEventListener("selectionchange", reportSelection, false);
document.addEventListener("mouseup", reportSelection, false);
document.addEventListener("mousedown", reportSelection, false);
document.addEventListener("keyup", reportSelection, false);
document.querySelectorAll("#editor *").forEach(el => el.onclick = e => alert(e.target.id));
};
#editor {
padding: 5px;
border: solid green 1px;
}
Select something in the content below:
<div id="editor" contenteditable="true">A <i id="wombatid">wombat</i> is a marsupial native to <b id="australiaid">Australia</b></div>
<div id="selectionLog"></div>

Line breaks do not work in my JavaScript code

I have the following project which is based on highlighting the hashtags that are written within the contenteditable box.
The problem is that the code does not allow me to create line breaks.
I need to be able to create new lines and that the cursor position works well.
$(document).ready(function() {
function getCaretCharacterOffsetWithin(element) {
var caretOffset = 0;
var doc = element.ownerDocument || element.document;
var win = doc.defaultView || doc.parentWindow;
var sel;
if (typeof win.getSelection != "undefined") {
sel = win.getSelection();
if (sel.rangeCount > 0) {
var range = win.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
caretOffset = preCaretRange.toString().length;
}
} else if ((sel = doc.selection) && sel.type != "Control") {
var textRange = sel.createRange();
var preCaretTextRange = doc.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToEnd", textRange);
caretOffset = preCaretTextRange.text.length;
}
return caretOffset;
}
function setCaretPosition(element, offset) {
var range = document.createRange();
var sel = window.getSelection();
var currentNode = null;
var previousNode = null;
for (var i = 0; i < element.childNodes.length; i++) {
previousNode = currentNode;
currentNode = element.childNodes[i];
while (currentNode.childNodes.length > 0) {
currentNode = currentNode.childNodes[0];
}
if (previousNode != null) {
offset -= previousNode.length;
}
if (offset <= currentNode.length) {
break;
}
}
if (currentNode != null) {
range.setStart(currentNode, offset);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
var textbox = $(".textbox");
$(document).on("input", textbox, function(event) {
var position = getCaretCharacterOffsetWithin(textbox.get(0));
var text = textbox.text();
text = text.replace(/(^|\s)(#[^\s]+)/ig, "$1<span style=\"color: blue;\">$2</span>");
textbox.html($.parseHTML(text));
setCaretPosition(textbox.get(0), position);
});
});
.textbox {
padding: 6px;
border: solid 1px #aaa;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<div class="textbox" contenteditable="true"></div>

How to get range of characters between # and caret in contenteditable

I have a contenteditable div and it contains other tags and not only plain text. Only one # is allowed in. How can I get the range of the characters between # and caret if such a range exists?
Ha that was easier than I thought!. Based on this easy to overlook question: Div "contenteditable" : get and delete word preceding caret I forked its jsfiddle and here is mine working as expected:
http://jsfiddle.net/52m2thu2/1/
function getWordBetweenAtAndCaret(containerEl) {
var preceding = "",
sel,
range,
precedingRange;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount > 0) {
range = sel.getRangeAt(0).cloneRange();
range.collapse(true);
range.setStart(containerEl, 0);
preceding = range.toString();
}
} else if ((sel = document.selection) && sel.type != "Control") {
range = sel.createRange();
precedingRange = range.duplicate();
precedingRange.moveToElementText(containerEl);
precedingRange.setEndPoint("EndToStart", range);
preceding = precedingRange.text;
}
var lastWord = preceding.match(/#(.+)$/i);
if (lastWord) {
return lastWord;
} else {
return false;
}
}

Categories