6

I want to replace selected text(or insert new text after cursor position if nothing is selected). The new text is entered from another textbox.
I want to be able to insert new text without clicking first (focusing) in the textarea.
meaning: first select text to replace inside textarea, then enter new text into the textbox and click the button.

<textarea id='text' cols="40" rows="20">
</textarea>

<div id="opt">
    <input id="input" type="text" size="35">
    <input type="button" onclick='pasteIntoInput(document.getElementById("input").value)' value="button"/>    
</div>


function pasteIntoInput(text) { 
  el=document.getElementById("text");
  el.focus();    
  if (typeof el.selectionStart == "number"&& typeof el.selectionEnd == "number") { 
    var val = el.value; 
    var selStart = el.selectionStart;
    el.value = val.slice(0, selStart) + text + val.slice(el.selectionEnd);        
    el.selectionEnd = el.selectionStart = selStart + text.length;
  } 
  else if (typeof document.selection != "undefined") {
    var textRange = document.selection.createRange();        
    textRange.text = text;       
    textRange.collapse(false);        
    textRange.select();
  } 
}  

Online example: link text

Sandeepan Nath
  • 9,966
  • 17
  • 86
  • 144
asker
  • 881
  • 2
  • 10
  • 15

4 Answers4

5

So, you want to persist the selection when you focus out of the textarea and focus on the input tag.

You need to remember the selection (start and end points when the text area loses focus) and force the selection so that it persists.

To remember the selection, you can store the el.selectionStart and el.selectionEnd in two global variables inside a function which is called at onblur() event of textarea tag.

Then inside your pasteIntoInput() you can consider those two points for replacement.

To force selection - Check this solution for persisting the selection. This uses jquery however, not plain javascript.

However, I am not sure whether the solution actually works. I tried it here http://jsfiddle.net/sandeepan_nits/qpZdJ/1/ but it does not work as expected.

Updates

I doubt if it is possible to persist selection after focus is gone. Probably selection needs focus to be there and the answer link that I gave tries to focus and then select. In that case this will not solve your problem. So, the alternatives could be -

  • faking a text area with an html div. You can define some styles to create a selection like effect and apply it onblur() or use a simple readymade editor if available.

  • Displaying the selection dynamically in a separate area. Check this demo of jquery fieldSelection plugin . Remember that you are already storing the selection in global variables for the actual replacement. You only need to display to the user the selection which will be replaced. I think displaying the selection separately like this demo saves your time and it looks cool too.

But depends on your requirement of course.

Further Update

Check http://jsfiddle.net/sandeepan_nits/qpZdJ/2/ for a working "Replacing text inside textarea without focus" (like you want) but without the selection on blur. I still don't know whether it is possible to keep the selection on blur.

Another Update (21st December)

Working solution for IEs as well as other browsers http://jsfiddle.net/sandeepan_nits/qpZdJ/24/

Here is the same code:-

The html -

<textarea id='text' cols="40" rows="20" onbeforedeactivate="storeSelectionIeCase();" onblur="storeSelectionOthersCase();">
</textarea>

<div id="opt">
    <input id="input" type="text" size="35">
    <input type="button" onclick='pasteIntoInput(document.getElementById("input").value)' value="button"/>

</div>

and all the js

var storedSelectionStart = null;
var storedSelectionEnd = null;

function pasteIntoInput(text)
{

    el=document.getElementById("text");

    el.focus();    
    if((storedSelectionStart != null) && (storedSelectionEnd != null))
    {
        start = storedSelectionStart;
        end = storedSelectionEnd;
    }
    else
    {
        start = el.selectionStart;
        end = el.selectionEnd;
    }
    if (typeof start  == "number"&& typeof end == "number")
    {
        var val = el.value;
        var selStart = start;
        var end  = selStart + text.length;
        el.value = val.slice(0, selStart) + text + val.slice(end );        
    }
    else if (typeof document.selection != "undefined")
    {
       var textRange = document.selection.createRange();        
       textRange.text = text;       
       textRange.collapse(false);        
       textRange.select();
    }
}  

function storeSelectionOthersCase()
{
    if(!(isBrowserIE6() || isBrowserIE7()))
    {
        storeSelection();
    }
    else
    {
        return false;
    }
}


function storeSelectionIeCase()
{
    if((isBrowserIE6() || isBrowserIE7()))
    {
        storeSelection();
    }
    else
    {
        return false;
    }
}


function storeSelection()
{
    //get selection
    el=document.getElementById("text");

    var el = document.getElementById("text");
    var sel = getInputSelection(el);
    //alert("check"+sel.start + ", " + sel.end);


    storedSelectionStart  = sel.start;
    storedSelectionEnd = sel.end;

   //alert("see"+storedSelectionStart  +" - "+storedSelectionEnd );
}

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
    };
}

function isBrowserIE6()
{
    var ret = false;
    if(($.browser.msie) && (parseInt($.browser.version) == 6) && (!this.XMLHttpRequest))
    {
        ret = true;
    }
    return ret;
}

function isBrowserIE7()
{
    var ret = false;
    if(($.browser.msie) && ((parseInt($.browser.version) == 7) && (this.XMLHttpRequest)))
    { //Modification because of IE tester IE7 being detected as IE6
        ret = true;
    }
    return ret;
}

The previous fiddle was not working in IEs because by the time the onblur() event fires, the selection is destroyed in IE. I have applied some browser based conditions for IE 6 and 7, but not tested yet in IE 8.

Thanks to Tim Down who helped me in identifying the problem with the previous fiddle.

Community
  • 1
  • 1
Sandeepan Nath
  • 9,966
  • 17
  • 86
  • 144
  • @asker I doubt if it is possible to persist selection after focus is gone. check my update. – Sandeepan Nath Dec 19 '10 at 21:38
  • I just want to select some text, popup frame with textbox where i enter new text and replace the selected text. – asker Dec 19 '10 at 21:47
  • @asker I have understood your requirement. You want to persist the selection when you focus out of the textarea and focus on the input tag (to enter new text). My answer remains same. – Sandeepan Nath Dec 19 '10 at 21:52
  • 1
    I reckon it is possible - not necessarily to keep the text selected onblur, but to remember the selectionStart and selectionEnd positions, then when you have entered the new text and clicked a button, it closes the popup and replaces the text between those positions in the specified textbox. Unfortunately I am only taking a 5 minute break from work so don't have time to work out a solution, but that is where I would start. You know, from another question, how to get the selectionStart and selectionEnd - it shouldn't be hard to get to a solution from there. – ClarkeyBoy Dec 19 '10 at 23:16
  • @ClarkyBoy - I think the persistence of text selection on blur is the main requirement now from a user experience point of view. Remembering the text selection and replacement is easy. Check my Further Update and see that it does the replacement properly like the OP wants but without the persistence of selection, it does not feel good for the user. Is it possible to somehow keep the selection on blur? – Sandeepan Nath Dec 20 '10 at 08:10
  • there is also problem without any selection, let's say I just want to insert new text at then end of existing text inside textarea. I place cursor at the end, focus the textarea and input new text then click the button. The result: text added at the begining of the textarea. – asker Dec 20 '10 at 11:52
  • The code in fiddle under the Further Update section (http://jsfiddle.net/sandeepan_nits/qpZdJ/2/) does correct addition of text without selection. I have verified again. What do you mean by "I place cursor at the end, *focus the textarea* ...". When you focus on the textarea again where do you place the cursor. Wherever you place the cursor last, the text is getting put there. So, replacement is happening when selected, adding is happening when not selected. – Sandeepan Nath Dec 20 '10 at 16:34
  • @Sandeepan Nath, try this: 1)Open your example in IE 2)enter any text inside textarea (the cursor should be at the end) 3) click textbox and enter text to be inserted 4)click button the result: the text is added at the begining of textarea and not at the end. the text must be inserted where was the cursor before leaving the text area – asker Dec 20 '10 at 18:10
  • @asker - I see the problem. I just copied and used `.selectionstart` as it was in the original code you posted. But it looks like getting selection start and end positions in IEs is very difficult using plain javascript. Here http://stackoverflow.com/questions/235411/is-there-an-internet-explorer-approved-substitute-for-selectionstart-and-selectio/4207763#4207763 is an answer which confirms a function which does the same correctly. I have used that function in this fiddle http://jsfiddle.net/sandeepan_nits/qpZdJ/12/ but still getting 0,0 as start and end selection points in IE 6. – Sandeepan Nath Dec 20 '10 at 20:38
  • @Sandeepan Nath, I've just found similar functionallity in stackoverflow.com: the button that adds link to the question :) it opens frame with textbox and adds link to caret position inside textarea – asker Dec 20 '10 at 20:46
  • @asker - yes that should work for you. I checked it works in IE 6. Do post the correction here if you find the correct code – Sandeepan Nath Dec 20 '10 at 20:58
  • getting the selection start and end positions :), if you can catch the appropriate code from the html source of stackoverflow. – Sandeepan Nath Dec 20 '10 at 21:04
  • after a quick look it seems to work!!! I will test all possible scenarios with different browsers tomorrow and accept your answer if everything is ok! Thank you for now. – asker Dec 21 '10 at 17:10
  • i think you should paste you code here, other wise it will be lost in some time (but let me test it first tomorrow) – asker Dec 21 '10 at 17:11
  • @asker - you are welcome. let me know after testing. I will put the code here too. – Sandeepan Nath Dec 21 '10 at 18:30
  • works! I treat IE8 as IE7 (you don't have a check for IE8). thanks – asker Dec 22 '10 at 16:02
  • wow thanks for my second best answer today. sorry I don't have a well tested check for IE 8. those two functions are from my existing code. But feature detection is recommended by jquery instead of browser detection http://api.jquery.com/jQuery.support/. – Sandeepan Nath Dec 22 '10 at 16:09
0

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});
            }
            }
        });
0

Alright, correct me where I am wrong.

1) When text in the textarea is selected, then the button is clicked, the selected text is replaced by the text in the input.
2) When no text is selected, no matter the cursor position, text is automatically added at the very end of the textarea.

If those are the only stipulations, then this javascript would suffice, otherwise I need more information on what you want it to do.

function pasteIntoInput(text) { 
  el=document.getElementById("text");
  el.focus();    
  if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number" && el.selectionStart != el.selectionEnd) { 
    var val = el.value; 
    var selStart = el.selectionStart;
    el.value = val.slice(0, selStart) + text + val.slice(el.selectionEnd);        
    el.selectionEnd = el.selectionStart = selStart + text.length;
  }
  else
    el.value += text;
}

Sorry that I cannot be of more assistance, it would be beneficial to understand the use of the function, so I could give the desired action.

Yoshiyahu
  • 2,089
  • 7
  • 22
  • 34
  • it's not what i want: enter 3 lines of text into textarea. move cursor tp the end of second line. go to the textbox enter new text and click button. the result: text inserted at the end of textarea and not where the cursor was. – asker Dec 21 '10 at 14:20
  • also: selecting text is not remembered. – asker Dec 21 '10 at 14:21
0

This is alternative solution based on answers from above and more ideas.
For IE only (other browsers support this functionality by default).

More testing is needed (I tested it just in IE8)

<script type="text/javascript">
    var pos = 0;
    var len = 0;

    function pasteIntoInput(text) {
       var el = document.getElementById("text");

        el.focus();
        if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
            var val = el.value;
            var selStart = el.selectionStart;
            el.value = val.slice(0, selStart) + text + val.slice(el.selectionEnd);
            el.selectionEnd = el.selectionStart = selStart + text.length;
        }
        else if (typeof document.selection != "undefined") {
        //var textRange = document.selection.createRange();
        var textRange  = el.createTextRange();
            //  el.focus();
            if (len > 0) { //something selected, so replace
                textRange.collapse(true);
                textRange.moveEnd('character', pos+len);
                textRange.moveStart('character', pos);
                textRange.select();
                textRange.text = text;

            }
            else {
                textRange.collapse(true);                   
                textRange.moveEnd('character', pos);
                textRange.moveStart('character', pos);                    
                textRange.text = text;
                textRange.select();

            }
            el.focus();
        }
    }


    function GetCaretPosition(txtarea) {

        pos = 0;
        if (document.selection) {

            // get current selection
            var range = document.selection.createRange();
            if (range.text.length>0)
             len=range.text.length;

            // get full range
            var rangeFull = document.body.createTextRange();
            rangeFull.moveToElementText(txtarea);

            var sel_start;
            for (sel_start = 0; rangeFull.compareEndPoints('StartToStart', range) < 0; sel_start++) {
                rangeFull.moveStart('character', 1);
            }                


            pos = sel_start;                


        }  

    }

</script>
asker
  • 881
  • 2
  • 10
  • 15