I'm building an autosuggest/autocomplete feature in a contenteditable div and I'm having a problem with determining when the cursor comes after characters that don't qualify for autocompletion. What I want to do is run my autosuggest ajax request only if the user is typing an "@" handle, such as "@someUser". The problem is that contenteditable divs contain actual html, so I'm tripping up when trying to determine if the last character before the cursor is one of my approved characters. My approved characters are: A-z0-9
. I'm using this regex: /( $| $|\s$)/
, but it only checks for spaces. I can't simply negate my approved characters (something like [^A-z0-9]
) because the HTML of the contenteditable would cause false negatives (the contenteditable div can have something like <div>test</div>
as its innerHTML
). I created this demo to try to showcase the problem.
Here is the code for it:
document.querySelector('button').addEventListener('click', handleClick);
function handleClick(e) {
const inputField = document.querySelector('#edit-me');
const text = inputField.innerHTML;
if (!text) {
alert('no text');
}
const afterInvalidChar = isAfterInvalidChar(text, getCaretIndex(inputField));
if (afterInvalidChar) {
alert('cursor is not after an accepted char');
} else {
alert('cursor is after an accepted char');
}
}
function isAfterInvalidChar(text, caretPosition) {
// first get text from the beginning until the caret
let termToSearch = text.slice(0, caretPosition);
console.log(termToSearch);
alert('content before cursor: ' + termToSearch);
const rgxToCheckEnding = /( $| $|\s$)/; // <-- this is where I'm tripping up, that regex only checks for spaces, and it's still not great
if (rgxToCheckEnding.test(termToSearch)) {
// the cursor is after a space, but I also need to check for anything
// that's not one of my accepted contents
return true;
} else {
return false;
}
}
// source: https://stackoverflow.com/a/46902361/7987987
function getCaretIndex (node) {
var range = window.getSelection().getRangeAt(0),
preCaretRange = range.cloneRange(),
caretIndex,
tmp = document.createElement("div");
preCaretRange.selectNodeContents(node);
preCaretRange.setEnd(range.endContainer, range.endOffset);
tmp.appendChild(preCaretRange.cloneContents());
caretIndex = tmp.innerHTML.length;
return caretIndex;
}
#edit-me {
width: 200px;
height: 200px;
background-color: lightblue;
}
<h2>Cursor of contenteditable div</h2>
<p>Accepted chars: <code>A-z0-9</code></p>
<button type="button">is cursor after an accepted char?</button>
<div contenteditable id="edit-me"><div>
An accceptable solution should work with the following text in the contenteditable div (just basic tests but you get the point):
"test"
: correctly say the last char is approved"test "
: correctly say the last char is not approved"test-"
: correctly say the last char is not approved"test?"
: correctly say the last char is not approved
How can get this working for only my approved chars? I'm open to an entirely different strategy if it gets the job done. Thanks!