Prevent editable div's behavior when typing certain characters - javascript

I have the following code taken from Pranav C Balan's answer to my previous question:
var div = document.getElementById('div');
div.addEventListener('input', function() {
var pos = getCaretCharacterOffsetWithin(this);
// get all red subtring and wrap it with span
this.innerHTML = this.innerText.replace(/red/g, '<span style="color:red">$&</span>')
setCaretPosition(this, pos);
})
// following code is copied from following question
// https://stackoverflow.com/questions/26139475/restore-cursor-position-after-changing-contenteditable
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();
//select appropriate node
var currentNode = null;
var previousNode = null;
for (var i = 0; i < element.childNodes.length; i++) {
//save previous node
previousNode = currentNode;
//get current node
currentNode = element.childNodes[i];
//if we get span or something else then we should get child node
while (currentNode.childNodes.length > 0) {
currentNode = currentNode.childNodes[0];
}
//calc offset in current node
if (previousNode != null) {
offset -= previousNode.length;
}
//check whether current node has enough length
if (offset <= currentNode.length) {
break;
}
}
//move caret to specified offset
if (currentNode != null) {
range.setStart(currentNode, offset);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
<span contenteditable="true" id="div" style="width:100%;display:block">sss</span>
It has a editable <div> where the user can input and it automatically colors the word red as red just like some code editors color key words like HTML tags, strings, functions, etc.Type "red" and you will understand what I mean.
The issue I'm having is, when I type "<", it deletes all the characters in front of it unless it finds a ">" where it will stop. Another error happens if you type "&#1" (or any other number instead of 1 really).
Any ideia on how to prevent this behavior?

You're running into this problem because you're expecting the user to be able to input HTML-like entities such as <xyz... or { but don't want to parse that input as HTML, but at the same time, you're yourself putting html elements in the same div and you want that to be parsed as HTML. So there are two ways you can go about this:
Keep the input and presentation separate. So user can input anything, which you'll sanitize and display in an output box.
Or... change the addEventListener function:
div.addEventListener('input', function() {
var pos = getCaretCharacterOffsetWithin(this);
var userString = sanitizeHTML(this.innerText);
// get all red subtring and wrap it with span
this.innerHTML = userString.replace(/red/g, '<span style="color:red">$&</span>')
setCaretPosition(this, pos);
})
This would work in most scenarios, but it'd break (badly) if you're expecting user to input HTML too, for example <span class="red" style="color: red">red</span> would become horribly mutilated. Other than that, you're good to go. Get sanitizeHTML from here: https://github.com/punkave/sanitize-html

Related

Get the cursor position in a text that has emojis and insert tags

What's up guys, how's it going? I'm having trouble saving the cursor position and inserting dynamic tags.I'm using the Emojiarea plugin to create a div where I can write texts, insert emojis, templates and tags. https://github.com/mervick/emojionearea
I use the following function below to create a div on my textarea:
$("#email_campaign_description").emojioneArea({
search: false,
recentEmojis: false,
pickerPosition: "right",
events: {
blur: function (editor, event) {
$scope.lastPosition = getCaretCharacterOffsetWithin(editor[0])
},
}
});
The next function returns the last position of my cursor when I click
somewhere in the text:
function getCaretCharacterOffsetWithin(element) {
var caretOffset = 0;
var doc = element.ownerDocument;
var win = doc.defaultView;
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;
}
debugger
return caretOffset;
}
And the last function adds dynamic tags to the text:
$scope.chooseTag = function (label) {
var domElement = $('#email_campaign_description');
var emojiElement = domElement[0].emojioneArea;
if (document.selection) {
domElement.focus();
var sel = document.selection.createRange();
sel.text = $scope.tags.model;
domElement.focus();
} else if ($scope.lastPosition) {
var startPos = $scope.lastPosition;
var endPos = startPos + $scope.tags.model[0].length;
emojiElement.setText(emojiElement.getText().substring(0, startPos) + ' ' + $scope.tags.model + ' ' + emojiElement.getText().substring(endPos, emojiElement.getText().length));
domElement.focus();
} else {
emojiElement.setText($scope.tags.model);
domElement.focus();
}
if ($scope.tags.model === '[vendor_name]') {
emojiElement.setText(domElement.val().replace('[vendor_name]', $scope.vendor.name));
}
$scope.campaign.description = $('#email_campaign_description').val();
};
The problem happens that the function that stores the click position in the text does not read emojis. So, if I write a text like: "Hello [emoji], welcome!" and at the end of that text I try to add a tag, my function will not read the emoji, and will insert the tag over the last character of my sentence, in this case "!". Likewise if I add two emojis, my function will not read and insert the tag over the last two characters in this case "o!". The correct thing would be my function to read these two emojis, and add my tag exactly in the desired location, that is: "Hello [emoji], welcome! [Tag]"
What can I do for my function getCaretCharacterOffsetWithin(element) to read emojis as a character, or a space occupied?
The problem is that javascript isn't great at handling Unicode strings.
For example:
"hello".length === 5
"👩🏻‍🦰".length === 7
There are several libraries that can help accurately measure the length of unicode strings. Graphemer is one of them (full disclosure: I published this library).
To fix your getCaretCharacterOffsetWithin(element) function do the following:
Import and instantiate the Graphemer library.
import Graphemer from 'graphemer';
const splitter = new Graphemer();
function getCaretCharacterOffsetWithin(element) {...}
Update the first instance where you count the string length.
caretOffset = preCaretRange.toString().length; // original
caretOffset = splitter.countGraphemes(preCaretRange.toString()); // updated
Update the second instance where you count the string length.
caretOffset = preCaretTextRange.text.length; // original
caretOffset = splitter.countGraphemes(preCaretTextRange.text); // updated
A library-free (Typescript) solution:
correctUnicodeOffset(offset: number, str: string): number {
if (offset < 1) return offset;
return Array.from(str.substr(0, offset)).length;
}
Use it like this:
myOffset = this.correctUnicodeOffset(myOffset, myStr);

How to replace a custom text before caret in a contenteditable div that contains many html tags?

var input = document.getElementById("div_id");
var username = "TEST";
input.focus();
var selection = window.getSelection();
var range = selection.getRangeAt(0);
var span = document.createElement('span');
span.setAttribute('class', 'tagged-user');
span.setAttribute('id', 55);
span.innerHTML = username;
//var initialText = input.textContent;
//var before_caret = initialText.slice(0,getCaretPosition(input));
// var after_caret = initialText.slice(getCaretPosition(input), initialText.length);
// // var before_caret = input.textContent;
// console.log("before_caret " + before_caret);
// *******************************
//this is the regex that match last #something before caret and it work good.
//var droped_at = before_caret.replace(/#\w+(?!.*#\w+)/g, '');
// *******************************
// input.textContent = droped_at + after_caret;
// console.log("before_caret " + before_caret);
range.deleteContents();
range.insertNode(span);
range.collapse(false);
range.insertNode(document.createTextNode("\u00a0"));
// cursor at the last
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
.tagged-user {
color: #1e81d6;
font-weight: bold;
}
<div contenteditable="true" id="div_id">
First person <span class="tagged-user" id="1">#Joe </span> and #te and <span class="tagged-user" id="2">#Emilie</span> want to go to the beach.
</div>
An example of a text:
hello man, #te #emilie how are you?( #emilie is a span tag)
In this example if the cursor is after #te, I want to replace #te by TEST.
Hello,
I am trying to make a facebook like mention system. I'm able to trigger a message that shows a list of all my contacts as soon as I type "#".
Everything is working fine, the only problem is when I type "#te" for finding "TEST" in the list, it should be able to replace "#te" with "TEST". It inputs #teTEST, any clue how to do it ? I'm using a div with contenteditable.
Thanks a lot.
It seems to be sooo complicated to get/set caret position in a content-editable div element.
Anyway, I created a working snippet with the functions I found in these topics…
Get caret position in contentEditable div
Set Caret Position in 'contenteditable' div that has children
… and added a function oninput to make the replacement you wanted:
(See comments in my code for more details)
var input = document.getElementById("div_id");
var replaced = "#te"; // TAKIT: Added this variable
var replacer = "#TEST"; // TAKIT: Renamed this variable
var replacer_tags = ['<span class="tagged-user" id="0">', '</span>']; // TAKIT: Added after comment
input.focus();
// TAKIT: Added functions from these solutions:
// https://stackoverflow.com/questions/3972014/get-caret-position-in-contenteditable-div
function GetCaretPosition(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;
}
// https://stackoverflow.com/questions/36869503/set-caret-position-in-contenteditable-div-that-has-children
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
}
// TAKIT: Added this whole function
input.oninput = function() {
var caretPos = GetCaretPosition(input); // Gets caret position
if (this.innerHTML.includes(replaced)) { // Filters the calling of the function
this.innerHTML = this.innerHTML.replace(replaced, replacer_tags.join(replacer) + ' '); // Replace
caretPos += replacer.length - replaced.length + 1; // Offset due to strings length difference
SetCaretPosition(input, caretPos); // Restores caret position
}
// console.clear(); console.log(this.innerHTML); // For tests
}
.tagged-user {
color: #1e81d6;
font-weight: bold;
}
<div contenteditable="true" id="div_id">
First person <span class="tagged-user" id="1">#Joe</span> and (add "te" to test) # and <span class="tagged-user" id="2">#Emilie</span> want to go to the beach.
</div>
Hope it helps.

How to make range offset to work with HTML elements in a multiple line contenteditable div?

I am having a few issues with my code regarding caret positioning, content editable div and HTML tags in it.
What I am trying to achieve
I'd like to have a content editable div, which allows for line breaks and multiple HTML tags inserted by typing some sort of shortcut - double left bracket '{{' in my case.
What I have achieved so far
The div allows for a single HTML tag and only works in a single line of text.
The issues
1) When I break the line with the return key, the {{ no longer triggers the tag to show up. I assume that you have to somehow make the script to take line breaks (nodes?) into account when creating the range.
2) If you already have one HTML tag visible, you can't insert another one. Instead, you get the following error in browser's console.
Uncaught DOMException: Failed to execute 'setStart' on 'Range': The offset 56 is larger than the node's length (33).
I noticed that range offset goes to 0 (or starts with the end of HTML tag) which is probably at the culprit of the issue here.
Below is the code I have so far...
Everything is triggered on either keyup or mouseclick.
var tw_template_trigger = '{{';
var tw_template_tag = '<span class="tw-template-tag" contenteditable="false"><i class="tw-icon tw-icon-close"></i>Pick a tag</span>';
$('.tw-post-template-content').on( 'keyup mouseup', function() {
// Basically check if someone typed {{
// if yes, attempt to delete those two characters
// then paste tag HTML in that position
if( checkIfTagIsTriggered( this ) && deleteTagTrigger( this ) ) {
pasteTagAtCaret();
}
});
function pasteTagAtCaret(selectPastedContent) {
// Then add the tag
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// only relatively recently standardized and is not supported in
// some browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = tw_template_tag;
var frag = document.createDocumentFragment(), node, lastNode;
while ( (node = el.firstChild) ) {
lastNode = frag.appendChild(node);
}
var firstNode = frag.firstChild;
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if ( (sel = document.selection) && sel.type != "Control") {
// IE < 9
var originalRange = sel.createRange();
originalRange.collapse(true);
sel.createRange().pasteHTML( tw_template_tag );
}
}
function checkIfTagIsTriggered(containerEl) {
var precedingChar = "", 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);
precedingChar = range.toString().slice(-2);
}
} else if ( (sel = document.selection) && sel.type != "Control") {
range = sel.createRange();
precedingRange = range.duplicate();
precedingRange.moveToElementText(containerEl);
precedingRange.setEndPoint("EndToStart", range);
precedingChar = precedingRange.text.slice(-2);
}
if( tw_template_trigger == precedingChar )
return true;
return false;
}
function deleteTagTrigger(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;
}
// First Remove {{
var words = range.toString().trim().split(' '),
lastWord = words[words.length - 1];
if (lastWord && lastWord == tw_template_trigger ) {
/* Find word start and end */
var wordStart = range.toString().lastIndexOf(lastWord);
var wordEnd = wordStart + lastWord.length;
range.setStart(containerEl.firstChild, wordStart);
range.setEnd(containerEl.firstChild, wordEnd);
range.deleteContents();
range.insertNode(document.createTextNode(' '));
// delete That specific word and replace if with resultValue
return true;
}
return false;
}
I noticed that those two lines are causing the browser error in the second issue
range.setStart(containerEl.firstChild, wordStart);
range.setEnd(containerEl.firstChild, wordEnd);
Theoretically, I know what the issue is. I believe both issues could be solved by making the range-creating script to use parent node rather than children nodes and also to loop through text nodes which line breaks are. However, I don't have a clue how to implement it at this point.
Could you please point me into the right direction?
Edit
I've actually managed to upload a demo with the progress so far to make it more clear.
Demo
I solved the problem myself and merged all functions into one. Neat! Below is the final code. I removed the ability to press enter after further considering it.
Hope it helps someone
var tw_template_trigger = '{{';
var tw_template_tag = '<span class="tw-template-tag" contenteditable="false">Pick a tag</span>';
$(".tw-post-template-content").keypress(function(e){ return e.which != 13; });
$('.tw-post-template-content').on( 'keyup mouseup', function() {
triggerTag( this );
});
function triggerTag(containerEl) {
var sel,
range,
text;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount > 0) {
range = sel.getRangeAt(0).cloneRange(); // clone current range into another variable for manipulation#
range.collapse(true);
range.setStart(containerEl, 0);
text = range.toString();
}
}
if( text && text.slice(-2) == tw_template_trigger ) {
range.setStart( range.endContainer, range.endOffset - tw_template_trigger.length);
range.setEnd( range.endContainer, range.endOffset );
range.deleteContents();
range.insertNode(document.createTextNode(' '));
//
var el = document.createElement("div");
el.innerHTML = tw_template_tag;
var frag = document.createDocumentFragment(), node, lastNode;
while ( (node = el.firstChild) ) {
lastNode = frag.appendChild(node);
}
var firstNode = frag.firstChild;
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
return true;
}
return false;
}

how to get the caret position of a contenteditable div which contains images

I have this contentedittable div
<div contenteditable="true" id="text">minubyv<img src="images/smiley/Emoji Smiley-01.png" class="emojiText" />iubyvt</div>
Here is an image description of the code output
so I want to get the caret position of the div and lets assume that the cursor is after the last character. And this is my code for getting the caret position
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;
}
var update = function() {
console.log(getCaretPosition(this));
};
$('#text').on("mousedown mouseup keydown keyup", update);
But the problem is that it returns 6 instead of 14. The caret position goes back to 0 after the image. Please is there a way I can get the caret position to be 14 in this case.
EDIT
I want to also insert some element starting from the caret position. so this is my function to do that
selectStart = 0;
var update = function() {
selectStart = getCaretPosition(this);
};
function insertEmoji(svg){
input = $('div#text').html();
beforeCursor = input.substring(0, selectStart);
afterCursor = input.substring(selectStart, input.length);
emoji = '<img src="images/smiley/'+svg+'.png" class="emojiText" />';
$('div#text').html(beforeCursor+emoji+afterCursor);
}
See Tim Down's answer on Get a range's start and end offset's relative to its parent container.
Try to use the function he has to get the selection index with nested elements like this:
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() {
console.log(getCaretCharacterOffsetWithin(this));
};
$('#text').on("mousedown mouseup keydown keyup", update);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div contenteditable="true" id="text">minubyv<img src="https://themeforest.net/images/smileys/happy.png" class="emojiText" />iubyvt</div>
I wrote my own function, based on Tim Down's, that works like you want it. I changed the treeWalker to filter NodeFilter.ELEMENT_NODE insted of NodeFilter.SHOW_TEXT, and now <img/> elements also get processed inside our loop. I start by storing the range.startOffset and then recurse through all the selection tree nodes. If it finds an img node, then it adds just 1 to the position; if the current node element is different than our range.startContainer, then it adds that node's length. The position is altered by a different variable lastNodeLength that is adds to the charCount at each loop. Finally, it adds whatever is left in the lastNodeLength to the charCount when it exists the loop and we have the correct final caret position, including image elements.
Final working code (it returns 14 at the end, exactly as it should and you want)
function getCharacterOffsetWithin_final(range, node) {
var treeWalker = document.createTreeWalker(
node,
NodeFilter.ELEMENT_NODE,
function(node) {
var nodeRange = document.createRange();
nodeRange.selectNodeContents(node);
return nodeRange.compareBoundaryPoints(Range.END_TO_END, range) < 1 ?
NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
},
false
);
var charCount = 0, lastNodeLength = 0;
if (range.startContainer.nodeType == 3) {
charCount += range.startOffset;
}
while (treeWalker.nextNode()) {
charCount += lastNodeLength;
lastNodeLength = 0;
if(range.startContainer != treeWalker.currentNode) {
if(treeWalker.currentNode instanceof Text) {
lastNodeLength += treeWalker.currentNode.length;
} else if(treeWalker.currentNode instanceof HTMLBRElement ||
treeWalker.currentNode instanceof HTMLImageElement /* ||
treeWalker.currentNode instanceof HTMLDivElement*/)
{
lastNodeLength++;
}
}
}
return charCount + lastNodeLength;
}
var update = function() {
var el = document.getElementById("text");
var range = window.getSelection().getRangeAt(0);
console.log("Caret pos: " + getCharacterOffsetWithin_final(range, el))
};
$('#text').on("mouseup keyup", update);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div contenteditable="true" id="text">minubyv<img contenteditable="true" src="https://themeforest.net/images/smileys/happy.png" class="emojiText" />iubyvt</div>

How to select the html tags with their contents when using selectNodeContents?

I have this bit of code, which I use to get the cursor's position in an editable div :
function getMeCurPos(element){
if (typeof window.getSelection != "undefined") {
var range = window.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
caretOffset = preCaretRange.toString().length;
return caretOffset;
}
}
The problem is that, the caretOffset returned only counts the textual contents and not the html tags. For eg :
Consider this string in my editable div :
Hey <b>jony</b>, whats goin on in the | party
*Cursor is denoted by | character.
Doing getMeCurPos(ele) returns : 30 but it should return 37. It doesn't count b tags
You may create a temp div in which you can put your preCaretRange and where you can use textContent or innerText on it. This won't take the HTML length, but the text around it.
function getMeCurPos(element){
if (typeof window.getSelection != "undefined") {
var range = window.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
var temp = document.createElement("div");
temp.innerHTML = preCaretRange.toString();
var sanitized = temp.textContent || temp.innerText;
caretOffset = sanitized.length;
return caretOffset;
}
}
See this fiddle
For getting the selection both as text and as DOM nodes you can use the cloneContents() in the Javascript selection-range.
HTML as below
function getMeCurPos(element) {
if (typeof window.getSelection != "undefined") {
var range = window.getSelection();
cloned.innerHTML = '';
for (let i = 0; i < range.rangeCount; i++) {
cloned.append(range.getRangeAt(i).cloneContents());
}
var sanitized = cloned.innerHTML;
caretOffset = sanitized.length;
return caretOffset;
}
}
<p contenteditable="true" id="test" onclick="alert(getMeCurPos(this))">Test1<br>Test2<br>Test3<br>Test4</p>
<br>
Cloned: <span id="cloned"></span>
I've taken the code from this article:
Select all DIV text with single mouse click
<script type="text/javascript">
function selectText(containerid) {
if (document.selection) {
var range = document.body.createTextRange();
range.moveToElementText(document.getElementById(containerid));
range.select();
} else if (window.getSelection) {
var range = document.createRange();
range.selectNode(document.getElementById(containerid));
window.getSelection().addRange(range);
}
}
</script>
<div id="selectable" onclick="selectText('selectable')">http://example.com/page.htm</div>

Categories