I am faced with following: when I try to select text in a contenteditable
element and the end of the selection is the start of the element content, then no select event is fired and there are no Selection
and Range
objects.
Could somebody please give me any advice on why this might occur or how I can prevent this?
Code responsible for getting selection range:
$('div[contenteditable="true"]').bind("mouseup keyup touchend", function() {
lastCaretIndex = getSelectionRange();
});
function getSelectionRange() {
var sel;
if (window.getSelection) {
sel = window.getSelection();
console.log(sel); // this doesn't print anything event empty string
if (sel.rangeCount) {
return sel.getRangeAt(0);
}
} else if (document.selection) {
return document.createRange();
}
return null;
}
<div id="main-input" contenteditable="true">Hello world!</div>
<script type="text/javascript" src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
JSFiddle (open your browser console to make sure that selection doesn't get logged).
The issue is that you only log selection changes when specific events occur on the contenteditable
element. More specifically, you have
$('div[contenteditable="true"]').bind("mouseup keyup touchend", // ...
In particular the mouseup
event will normally be triggered when the selection changes. Except when it doesn't. When you release the mouse outside of the editable div
(which you do in your example!), then the div
will never receive a mouseup
event and thus never log the selection.
There are two ways around this:
body
. Downsides are that you receive more events that do not influence the selection and that it is still possible to get mouseup
events outside of the page.selectionchange
event.document.addEventListener('selectionchange', function(event) {
console.log(event.type);
});
<div contenteditable="true">Hello world!</div>
You can of course still access the selection as you currently do inside this event handler. This event is triggered every time the selection changes, so you may want to throttle it.
Full implementation of that can be found below.
function handler() {
// do whatever you want here
// this shows the selection and all ranges it consists of
var sel = window.getSelection(),
ranges = Array(sel.rangeCount).fill(0).map((_, i) => sel.getRangeAt(i));
ranges = ranges.map((r) => `${r.startOffset}-${r.endOffset}`).join(';');
console.log(`Selection [${ranges}:"${sel.toString()}"]`);
}
function throttle(func) {
var timeoutId = false,
called = false,
wrap = function() {
if (!called) {
clearInterval(timeoutId);
timeoutId = false;
} else {
func();
}
called = false;
};
return function() {
if (timeoutId === false) {
func();
timeoutId = setInterval(wrap, 500);
} else {
called = true;
}
};
}
document.addEventListener('selectionchange', throttle(handler));
<div contenteditable="true">Hello world!</div>
Your actual code works perfectly and logs a Selection object in the console, even if the end of the selection is the start of the element content.
Indeed you need to log this Selection
text instead of logging the whole object
which reflects the whole Selection object changes for each event.
I updated your snippet to log the text of the selection using Selection.toString()
, you can see it working here:
$('div[contenteditable="true"]').bind("mouseup keyup touchend", function() {
lastCaretIndex = getSelectionRange();
});
function getSelectionRange() {
var sel;
if (window.getSelection) {
sel = window.getSelection();
console.log(sel.toString()); // this doesn't print anything event empty string
if (sel.rangeCount) {
return sel.getRangeAt(0);
}
} else if (document.selection) {
return document.createRange();
}
return null;
}
<div id="main-input" contenteditable="true">Hello world!</div>
<script type="text/javascript" src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
You can check this answer, it shows and explains the perfect way to get text selection.
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