19

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?

sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
TALlama
  • 16,017
  • 8
  • 38
  • 47

4 Answers4

23

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);
Community
  • 1
  • 1
Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • 2
    Can we have this licensed under some non-viral license, or even contributed to the public domain? (this is the voice of Those Who Care about Copyright :-)) – Tomasz Zieliński Dec 10 '10 at 22:37
  • 1
    @Tomasz: Well, I moved the textarea/input selection stuff to a separate library because it works entirely differently from selections within the document: http://code.google.com/p/rangyinputs/ . Regarding licensing, what's the problem with the standard Stack Overflow Creative Commons license? – Tim Down Dec 11 '10 at 00:56
  • 2
    @Tomasz: Fair enough. I find the details of free software licensing confusing and dull, but I understand the general principles. For stuff I'm giving away, I will happily license it in whatever way is most convenient for users. – Tim Down Dec 12 '10 at 00:40
  • @Tim Down I am not able get the correct selection start/end values in IE 6 using your function. Please check http://jsfiddle.net/sandeepan_nits/qpZdJ/12/ and let me know if this function is really tested well? – Sandeepan Nath Dec 20 '10 at 20:35
  • I returns 0,0 for me. Am I missing something? – Sandeepan Nath Dec 20 '10 at 20:40
  • @Sandeepan Nath: It's very well tested, and works in IE 5, 6, 7, 8 and 9. Possibly also IE 4, although I have no means of testing that. Are you sure the problem is with this function? If it is, could you simplify the test case to isolate the problem to just this function? – Tim Down Dec 21 '10 at 00:51
  • @Tim Down - I have set up another fiddle here http://jsfiddle.net/sandeepan_nits/VGxMM/4/ with just a simple input tag and calling your function on blur, but here too it returns incorrect in case of IE 6. – Sandeepan Nath Dec 21 '10 at 08:16
  • @Sandeepan Nath: OK. The problem is not with the `getInputSelection()` function itself. The problem is that by the time the `blur` event fires, the selection has been destroyed in IE. You can use the IE-only `beforedeactivate` event instead to get the selection before it's been destroyed: http://jsfiddle.net/rtdgN/ – Tim Down Dec 21 '10 at 10:53
  • @Tim Down agreed. your function rocks! +1 – Sandeepan Nath Dec 21 '10 at 15:21
  • This doesn't work correctly for me in IE8. However, if I remove all the lines that reference 'normalizedValue', it works fine. I also needed to add 'el.focus()' at the top of the section that deals with IE8. Focus is not needed for other browsers. – PKKid Jan 22 '11 at 04:43
  • @PKKid: The `normalizedValue` stuff is essential for handling line breaks correctly. It's true that you may need to focus the textarea first, but that is assumed by the function: I'll add a note to say that. Could you provide an example of it not working in IE 8? – Tim Down Jan 22 '11 at 10:24
7

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.

Community
  • 1
  • 1
bobince
  • 528,062
  • 107
  • 651
  • 834
6

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
    }
}
TALlama
  • 16,017
  • 8
  • 38
  • 47
  • This has issues with new lines. See @bobince's answer for some background on that, and my answer for the solution. – Tim Down Nov 17 '10 at 18:25
0

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 ();
    }
  }
Atav32
  • 1,788
  • 3
  • 23
  • 33