Get word starting with special characters at caret in contenteditable div
contenteditable, javascript, jquery
Solution
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>
Problem
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?