24

what is the best way to do this in jQuery? This should be a fairly common use case.

  1. User selects text in a textarea
  2. He clicks on a link
  3. The text in the link replaces the selected text in the textarea

Any code will be much appreciated - I am having some issues with part 3.

meow
  • 27,476
  • 33
  • 116
  • 177
  • 1
    The properties `selectionStart` and `selectionEnd` of your `textarea` or `input` is all you need nowadays. Combine that with the `slice` method on the input element's value. – caw Feb 09 '17 at 01:46

2 Answers2

33

Here's how you can do it, in all major browsers. I've also got a jQuery plug-in that includes this functionality. With that, the code would be

$("your_textarea_id").replaceSelectedText("NEW TEXT");

Here's a full stand-alone solution:

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 replaceSelectedText(el, text) {
    var sel = getInputSelection(el), val = el.value;
    el.value = val.slice(0, sel.start) + text + val.slice(sel.end);
}

var el = document.getElementById("your_textarea");
replaceSelectedText(el, "[NEW TEXT]");
Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • This will be an amazingly useful plugin. Thank you so much! It is kinda weird how this use case is not covered yet. I tried writing my own, but hit on so many browser quirks. ;) – meow Oct 19 '10 at 14:54
  • replaceSelectedText - the only issue i see with this is that in IE, when nothing is selected, the cursor goes to the front, as opposed to the end. anyway that can be fixed? – meow Oct 19 '10 at 22:21
  • In the code posted here or the jQuery plugin? The code here makes no attempt to reposition the selection afterwards, for the sake of brevity. The jQuery plugin should deal with that. – Tim Down Oct 19 '10 at 23:31
  • @TimDown thats a great bit of code, but why the heck is this not in the jquery libs already. I'm with Ming, I would have thought this was a fairly common use case. – Ads Nov 19 '13 at 03:07
  • 1
    @Ads: They like to keep the core lean. I can understand that. – Tim Down Nov 19 '13 at 09:32
  • Add these to the end of the replaceSelectedText() function to make sure the carat gets properly placed at the end of the inserted text: el.selectionStart = sel.start + text.length; el.selectionEnd = el.selectionStart; – Jim Carnicelli Oct 09 '14 at 06:39
  • @JimCarnicelli: There is complication in old IE for that as well. See http://stackoverflow.com/a/3288215/96100 – Tim Down Oct 09 '14 at 08:34
  • Please note that [the linked library is not yet open source](https://github.com/timdown/rangyinputs/issues/16). – Flimm Jan 16 '17 at 16:54
  • This is such a GREAT Plugin! – Robert Kehoe May 02 '18 at 20:38
0

Building on caw's comment, this is much easier to do in 2023:

link.onclick = () => {
  let first = textarea.value.slice(0, textarea.selectionStart);
  let rest = textarea.value.slice(textarea.selectionEnd, textarea.value.length);

  textarea.value = first + link.innerText + rest;

  // Bonus: place cursor behind replacement
  textarea.selectionEnd = (first + link.innerText).length;
};

It's essentially Tim Down's solution, only trimmed down to what you need when you assume that selectionStart and selectionEnd are available on text inputs.

Not sure what you mean by "text in the link". For now, I'm using link.innerText.

Why you'd need jQuery for this, I don't know, but you could easily use $(link).on('click', ...) or whatever instead.

Dennis Hackethal
  • 13,662
  • 12
  • 66
  • 115