I have a contenteditable div
, and I need to know the word at the current caret position. I tried this solution, but the problem is, it doesn't recognize special characters like @
and ~
. So if a word starts with ~
, like ~fool
, I am getting fool
, whereas I expected ~fool
. So I tried to modify the solution by taking into account that if after moving the selection back, the character encountered is not a space, I would continue moving backwards until a space is encountered. That would make the start of the selection. Similarly then I would keep moving forward until I find a space, and that would mark the end of selection. Then the selection would give me the word. To get the caret position, I used this solution. Combined, my code now looks like this:
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;
}
function getCurrentWord() {
var sel, word = "";
if (window.getSelection && (sel = window.getSelection()).modify) {
var selectedRange = sel.getRangeAt(0);
sel.collapseToStart();
sel.modify("move", "backward", "word");
while (sel.toString() != " " && getCaretPosition($("#editor").get(0)) != 0) {
sel.modify("move", "backward", "character");
(sel = window.getSelection()).modify;
}
sel.modify("move", "forward", "character");
sel.modify("extend", "forward", "word");
word = sel.toString();
// Restore selection
sel.removeAllRanges();
sel.addRange(selectedRange);
} else if ((sel = document.selection) && sel.type != "Control") {
var range = sel.createRange();
range.collapse(true);
range.expand("word");
word = range.text;
}
return word;
}
$(function () {
$(document).on('keyup keydown paste cut mouseup',"#editor", function () {
var word = getCurrentWord();
console.log(word);
});
});
However this is not working at all. That is problem no. 1. Problem 2 is, even if there is an image in the pic and user clicks on the pic, the handler keeps returning the last word before the image, whereas I am expecting a blank string. Can anyone help me fix these two issues?
I modified the getCurrentWord()
function to use basic String methods to get the word from the caret position. The function accepts the element and position and returns the word at that position.
Below is the updated function.
function getCurrentWord(el, position) {
// Get content of div
var content = el.textContent;
// Check if clicked at the end of word
position = content[position] === ' ' ? position - 1 : position;
// Get the start and end index
var startPosition = content.lastIndexOf(' ', position);
var endPosition = content.indexOf(' ', position);
// Special cases
startPosition = startPosition === content.length ? 0 : startPosition;
endPosition = endPosition === -1 ? content.length : endPosition;
return content.substring(startPosition + 1, endPosition);
}
The function first gets the content of the element. Next, it checks if the user has clicked at the end of a word, if yes, then subtract one from the position so that the indexOf
and lastIndexOf
will work correctly on space.
For start and end positions, there are two special cases which need to be handled. First, click on the last element. For this, the startPosition
will be -1
since there could be no space after the last word.
Second, when clicking on the first word, the endPosition
will be -1
because there could be no space before the first word.
These two conditions will work even if there is space before first character and space after the last character.
indexOf
and lastIndexOf
are used to find the space before and after word and using those indexes substring
will give the word at that position.
Here's live demo to test.
$(function() {
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;
}
function getCurrentWord(el, position) {
var word = '';
// Get content of div
var content = el.textContent;
// Check if clicked at the end of word
position = content[position] === ' ' ? position - 1 : position;
// Get the start and end index
var startPosition = content.lastIndexOf(' ', position);
startPosition = startPosition === content.length ? 0 : startPosition;
var endPosition = content.indexOf(' ', position);
endPosition = endPosition === -1 ? content.length : endPosition;
return content.substring(startPosition + 1, endPosition);
}
$('#editor').on('keyup keydown paste cut mouseup', function() {
var caretPosition = getCaretPosition(this);
var word = getCurrentWord(this, caretPosition);
console.log(word);
});
});
div {
font-size: 18px;
line-height: 1.5em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="editor" contenteditable>
Lorem !p$um dolor $!t @met, con$ectetur @d!p!$!c!ng el!t, $ed do e!u$mod tempor !nc!d!dunt ut l@bore et dolore m@gn@ @l!qu@. Ut en!m @d m!n!m ven!@m, qu!$ no$trud exerc!t@t!on ull@mco l@bor!$ n!$! ut @l!qu!p ex e@ commodo con$equ@t. Du!$ @ute !rure dolor
!n reprehender!t !n volupt@te vel!t e$$e c!llum dolore eu fug!@t null@ p@r!@tur.
</div>
Here's a pretty barebones example. There's a div with example text:
<div contenteditable onclick="getCaretCharacterOffsetWithin(this)">some ~test content</div>
Here's the script. When we click on the div, wherever the cursor lands we get the current position and spit out the word we're in. This word can include special characters and is only space delineated.
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;
}
console.log('caretOffset', caretOffset);
word = getWordAtPosition(caretOffset, element);
console.log('word', word);
return caretOffset;
}
function getWordAtPosition(position, element) {
var total_text = element.innerHTML;
var current_word = "";
var i = 0;
var word_found = false;
while(i < total_text.length) {
if(total_text[i] != ' ')
current_word += total_text[i];
else if(word_found)
return current_word;
else
current_word = "";
if(i == position)
word_found = true;
i++;
}
return current_word;
}
Heres an example of it working:
https://codepen.io/anon/pen/dWdyLV
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With