Reputation: 16437
Finding out what's selected in real browsers is as simple as:
var range = {
start: textbox.selectionStart,
end: textbox.selectionEnd
}
But IE, as usual, doesn't understand. What's the best cross-browser way to do this?
Upvotes: 20
Views: 20011
Reputation: 1812
From BootstrapFormHelpers
function getCursorPosition($element) {
var position = 0,
selection;
if (document.selection) {
// IE Support
$element.focus();
selection = document.selection.createRange();
selection.moveStart ('character', -$element.value.length);
position = selection.text.length;
} else if ($element.selectionStart || $element.selectionStart === 0) {
position = $element.selectionStart;
}
return position;
}
function setCursorPosition($element, position) {
var selection;
if (document.selection) {
// IE Support
$element.focus ();
selection = document.selection.createRange();
selection.moveStart ('character', -$element.value.length);
selection.moveStart ('character', position);
selection.moveEnd ('character', 0);
selection.select ();
} else if ($element.selectionStart || $element.selectionStart === 0) {
$element.selectionStart = position;
$element.selectionEnd = position;
$element.focus ();
}
}
Upvotes: 0
Reputation: 324687
I'll post this function for another time, seeing as this question got linked to from another one.
The following will do the job in all browsers and deals with all new line problems without seriously compromising performance. I've arrived at this after some toing and froing and now I'm pretty convinced it's the best such function around.
UPDATE
This function does assume the textarea/input has focus, so you may need to call the textarea's focus()
method before calling it.
function getInputSelection(el) {
var start = 0, end = 0, normalizedValue, range,
textInputRange, len, endRange;
if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
start = el.selectionStart;
end = el.selectionEnd;
} else {
range = document.selection.createRange();
if (range && range.parentElement() == el) {
len = el.value.length;
normalizedValue = el.value.replace(/\r\n/g, "\n");
// Create a working TextRange that lives only in the input
textInputRange = el.createTextRange();
textInputRange.moveToBookmark(range.getBookmark());
// Check if the start and end of the selection are at the very end
// of the input, since moveStart/moveEnd doesn't return what we want
// in those cases
endRange = el.createTextRange();
endRange.collapse(false);
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
start = end = len;
} else {
start = -textInputRange.moveStart("character", -len);
start += normalizedValue.slice(0, start).split("\n").length - 1;
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
end = len;
} else {
end = -textInputRange.moveEnd("character", -len);
end += normalizedValue.slice(0, end).split("\n").length - 1;
}
}
}
}
return {
start: start,
end: end
};
}
var el = document.getElementById("your_input");
el.focus();
var sel = getInputSelection(el);
alert(sel.start + ", " + sel.end);
Upvotes: 24
Reputation: 536705
IE's Range implementation is a slithy horror. It really wants you to use the execrable execCommand interface instead of anything involving indexing into the text.
There are two approaches I know of for getting the indices and they both have problems. The first uses range.text as in your example code. Unfortunately range.text has a habit of stripping off leading and trailing newlines, which means if the caret/selection is at the start of a line other than the first one, beforeLength will be off by (number of newlines*2) characters and you'll get the wrong selected text.
The second approach is to use range.moveStart/End (on a duplicated range), as outlined in the answer to this question: Character offset in an Internet Explorer TextRange (however as you are using a known textarea parent you can ignore the stuff about node-finding). This doesn't have the same problem, but it does report all indices as if newlines were simple LF characters, even though textarea.value and range.text will return them as CRLF sequences! So you can't use them directly to index into the textarea, but you can either fix them up with a bunch of newline counting or just string-replace away all the CRs from the value before you use it.
Upvotes: 7
Reputation: 16437
My current solution is verbose and based on this thread, but I'm open to better solutions.
function getSelection(inputBox) {
if ("selectionStart" in inputBox) {
return {
start: inputBox.selectionStart,
end: inputBox.selectionEnd
}
}
//and now, the blinkered IE way
var bookmark = document.selection.createRange().getBookmark()
var selection = inputBox.createTextRange()
selection.moveToBookmark(bookmark)
var before = inputBox.createTextRange()
before.collapse(true)
before.setEndPoint("EndToStart", selection)
var beforeLength = before.text.length
var selLength = selection.text.length
return {
start: beforeLength,
end: beforeLength + selLength
}
}
Upvotes: 6