Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IE's document.selection.createRange doesn't include leading or trailing blank lines

I'm trying to extract the exact selection and cursor location from a textarea. As usual, what's easy in most browsers is not in IE.

I'm using this:

var sel=document.selection.createRange();
var temp=sel.duplicate();
temp.moveToElementText(textarea);
temp.setEndPoint("EndToEnd", sel);
selectionEnd = temp.text.length;
selectionStart = selectionEnd - sel.text.length;

Which works 99% of the time. The problem is that TextRange.text doesn't return leading or trailing new line characters. So when the cursor is a couple of blank lines after a paragraph it yields a position at the end of the preceeding paragraph - rather than the actual cursor position.

eg:

the quick brown fox|    <- above code thinks the cursor is here

|    <- when really it's here

The only fix I can think of is to temporarily insert a character before and after the selection, grab the actual selection and then remove those temp characters again. It's a hack but in a quick experiment looks like it will work.

But first I'd like to be sure there's not an easier way.

like image 306
Brad Robinson Avatar asked Sep 01 '10 23:09

Brad Robinson


3 Answers

I'm adding another answer since my previous one is already getting somewhat epic.

This is what I consider the best version yet: it takes bobince's approach (mentioned in the comments to my first answer) and fixes the two things I didn't like about it, which were first that it relies on TextRanges that stray outside the textarea (thus harming performance), and second the dirtiness of having to pick a giant number for the number of characters to move the range boundary.

function getSelection(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_textarea");
var sel = getSelection(el);
alert(sel.start + ", " + sel.end);
like image 105
Tim Down Avatar answered Nov 14 '22 23:11

Tim Down


The move by negative bazillion seems to work perfectly.

Here's what I ended up with:

var sel=document.selection.createRange();
var temp=sel.duplicate();
temp.moveToElementText(textarea);
var basepos=-temp.moveStart('character', -10000000);

this.m_selectionStart = -sel.moveStart('character', -10000000)-basepos;
this.m_selectionEnd = -sel.moveEnd('character', -10000000)-basepos;
this.m_text=textarea.value.replace(/\r\n/gm,"\n");

Thanks bobince - how can I vote up your answer when it's just a comment :(

like image 30
Brad Robinson Avatar answered Nov 14 '22 23:11

Brad Robinson


A jquery plugin to get selection index start and end in text area. The above javascript codes didnt work for IE7 and IE8 and gave very inconsistent results, so I have written this small jquery plugin. Allows to temporarily save start and end index of the selection and hightlight the selection at a later time.

A working example and brief version is here: http://jsfiddle.net/hYuzk/3/

A more details version with comments etc. is here: http://jsfiddle.net/hYuzk/4/

        // Cross browser plugins to set or get selection/caret position in textarea, input fields etc for IE7,IE8,IE9, FF, Chrome, Safari etc 
        $.fn.extend({ 
            // Gets or sets a selection or caret position in textarea, input field etc. 
            // Usage Example: select text from index 2 to 5 --> $('#myTextArea').caretSelection({start: 2, end: 5}); 
            //                get selected text or caret position --> $('#myTextArea').caretSelection(); 
            //                if start and end positions are the same, caret position will be set instead o fmaking a selection 
            caretSelection : function(options) 
            { 
            if(options && !isNaN(options.start) && !isNaN(options.end)) 
            { 
            this.setCaretSelection(options); 
            } 
            else 
            { 
            return this.getCaretSelection(); 
            } 
            }, 
            setCaretSelection : function(options) 
            { 
            var inp = this[0]; 
            if(inp.createTextRange) 
            { 
            var selRange = inp.createTextRange(); 
            selRange.collapse(true); 
            selRange.moveStart('character', options.start); 
            selRange.moveEnd('character',options.end - options.start); 
            selRange.select(); 
            } 
            else if(inp.setSelectionRange) 
            { 
            inp.focus(); 
            inp.setSelectionRange(options.start, options.end); 
            } 
            }, 
            getCaretSelection: function() 
            { 
            var inp = this[0], start = 0, end = 0; 
            if(!isNaN(inp.selectionStart)) 
            { 
            start = inp.selectionStart; 
            end = inp.selectionEnd; 
            } 
            else if( inp.createTextRange ) 
            { 
            var inpTxtLen = inp.value.length, jqueryTxtLen = this.val().length; 
            var inpRange = inp.createTextRange(), collapsedRange = inp.createTextRange(); 

            inpRange.moveToBookmark(document.selection.createRange().getBookmark()); 
            collapsedRange.collapse(false); 

            start = inpRange.compareEndPoints('StartToEnd', collapsedRange) > -1 ? jqueryTxtLen : inpRange.moveStart('character', -inpTxtLen); 
            end = inpRange.compareEndPoints('EndToEnd', collapsedRange) > -1 ? jqueryTxtLen : inpRange.moveEnd('character', -inpTxtLen); 
            } 
            return {start: Math.abs(start), end: Math.abs(end)}; 

            }, 
            // Usage: $('#txtArea').replaceCaretSelection({start: startIndex, end: endIndex, text: 'text to replace with', insPos: 'before|after|select'}) 
            // Options     start: start index of the text to be replaced 
            //               end: end index of the text to be replaced 
            //              text: text to replace the selection with 
            //            insPos: indicates whether to place the caret 'before' or 'after' the replacement text, 'select' will select the replacement text 

            replaceCaretSelection: function(options) 
            { 
            var pos = this.caretSelection(); 
            this.val( this.val().substring(0,pos.start) + options.text + this.val().substring(pos.end) ); 
            if(options.insPos == 'before') 
            { 
            this.caretSelection({start: pos.start, end: pos.start}); 
            } 
            else if( options.insPos == 'after' ) 
            { 
            this.caretSelection({start: pos.start + options.text.length, end: pos.start + options.text.length}); 
            } 
            else if( options.insPos == 'select' ) 
            { 
            this.caretSelection({start: pos.start, end: pos.start + options.text.length}); 
            } 
            } 
        }); 
like image 23
Farrukh Aziz Avatar answered Nov 14 '22 23:11

Farrukh Aziz