197

I would like to create a simple function that adds text into a text area at the user's cursor position. It needs to be a clean function. Just the basics. I can figure out the rest.

Makyen
  • 31,849
  • 12
  • 86
  • 121
JoshMWilliams
  • 2,254
  • 3
  • 17
  • 14
  • 3
    possible duplicate of [How do I insert some text where the cursor is?](http://stackoverflow.com/questions/7404366/how-do-i-insert-some-text-where-the-cursor-is) – Derek 朕會功夫 Jun 18 '12 at 04:40
  • 2
    Take a look at this answer already posted: http://stackoverflow.com/questions/4456545/how-to-insert-text-at-the-current-caret-position-in-a-textarea – John Culviner Jun 18 '12 at 04:45
  • 1
    possible duplicate of [Inserting a text where cursor is using Javascript/jquery](http://stackoverflow.com/questions/1064089/inserting-a-text-where-cursor-is-using-javascript-jquery) – user Jul 26 '15 at 05:48
  • 1
    Interesting 2018 article: [How to Insert Text Into Textarea at Cursor Fast](https://www.everythingfrontend.com/posts/insert-text-into-textarea-at-cursor-position.html) – Delgan Jun 11 '18 at 23:53
  • If you're looking for a simple module with undo support, try [insert-text-textarea](https://github.com/bfred-it/insert-text-textarea). If you need IE8+ support, try the [insert-text-at-cursor](https://www.npmjs.com/package/insert-text-at-cursor) package. – fregante Mar 29 '19 at 01:45
  • Duplicate of https://stackoverflow.com/q/1064089/12860895 – Aryan Beezadhur Oct 17 '20 at 16:36

14 Answers14

164

Use selectionStart/selectionEnd properties of the input element (works for <textarea> as well)

function insertAtCursor(myField, myValue) {
    //IE support
    if (document.selection) {
        myField.focus();
        sel = document.selection.createRange();
        sel.text = myValue;
    }
    //MOZILLA and others
    else if (myField.selectionStart || myField.selectionStart == '0') {
        var startPos = myField.selectionStart;
        var endPos = myField.selectionEnd;
        myField.value = myField.value.substring(0, startPos)
            + myValue
            + myField.value.substring(endPos, myField.value.length);
    } else {
        myField.value += myValue;
    }
}
Klesun
  • 12,280
  • 5
  • 59
  • 52
Raab
  • 34,778
  • 4
  • 50
  • 65
  • 30
    to fix "loses caret position": add inser these lines before `} else {` `myField.selectionStart = startPos + myValue.length;` `myField.selectionEnd = startPos + myValue.length;` – user340140 Nov 28 '12 at 01:23
  • 13
    Thanks Rab for the answer and @user340140 for the fix. Here's a [working example](http://jsfiddle.net/Znarkus/Z99mK/). – Markus Hedlund May 14 '13 at 20:02
  • 2
    @user340140, your "lose caret potition" fix, only works if I give focus to the input right before the lines you suggest. It seems to be impossible to change selection on a non-focused field, at least in Chrome (current version 62.0) – Jette Dec 15 '17 at 13:58
  • 2
    There is a minor issue with this code: `selectionStart` is a numeric value, and thus should be compared to `0` and not `'0'`, and probably should use [`===`](https://stackoverflow.com/questions/359494/which-equals-operator-vs-should-be-used-in-javascript-comparisons?rq=1) – Herohtar Dec 18 '18 at 21:53
108

This snippet could help you with it in a few lines of jQuery 1.9+: http://jsfiddle.net/4MBUG/2/

$('input[type=button]').on('click', function() {
    var cursorPos = $('#text').prop('selectionStart');
    var v = $('#text').val();
    var textBefore = v.substring(0,  cursorPos);
    var textAfter  = v.substring(cursorPos, v.length);

    $('#text').val(textBefore + $(this).val() + textAfter);
});
m43x
  • 225
  • 1
  • 3
  • 11
Adriano Alves
  • 1,205
  • 1
  • 8
  • 2
62

New answer:

https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setRangeText

I'm not sure about the browser support for this though.

Tested in Chrome 81.

function typeInTextarea(newText, el = document.activeElement) {
  const [start, end] = [el.selectionStart, el.selectionEnd];
  el.setRangeText(newText, start, end, 'select');
}

document.getElementById("input").onkeydown = e => {
  if (e.key === "Enter") typeInTextarea("lol");
}
<input id="input" />
<br/><br/>
<div>Press Enter to insert "lol" at caret.</div>
<div>It'll replace a selection with the given text.</div>

Old answer:

A pure JS modification of Erik Pukinskis' answer:

function typeInTextarea(newText, el = document.activeElement) {
  const start = el.selectionStart
  const end = el.selectionEnd
  const text = el.value
  const before = text.substring(0, start)
  const after  = text.substring(end, text.length)
  el.value = (before + newText + after)
  el.selectionStart = el.selectionEnd = start + newText.length
  el.focus()
}

document.getElementById("input").onkeydown = e => {
  if (e.key === "Enter") typeInTextarea("lol");
}
<input id="input" />
<br/><br/>
<div>Press Enter to insert "lol" at caret.</div>

Tested in Chrome 47, 81, and Firefox 76.

If you want to change the value of the currently selected text while you're typing in the same field (for an autocomplete or similar effect), pass document.activeElement as the first parameter.

It's not the most elegant way to do this, but it's pretty simple.

Example usages:

typeInTextarea('hello');
typeInTextarea('haha', document.getElementById('some-id'));
Jayant Bhawal
  • 2,044
  • 2
  • 31
  • 32
  • you didnt close the line with >> ; < – Phoenix Mar 18 '16 at 19:10
  • 5
    @Phoenix semicolons are optional in Javascript. Works without them too. Although, you may edit in semicolons if you want. No biggie. – Jayant Bhawal Mar 18 '16 at 20:29
  • 3
    [I made a demo on JSFiddle.](https://jsfiddle.net/j77uezpL/) It also works using `Version 54.0.2813.0 canary (64-bit)`, which is basically Chrome Canary 54.0.2813.0. Finally, if you want it to insert into the text box by ID, use `document.getElementById('insertyourIDhere')` in place of `el` in the function. – bb216b3acfd8f72cbc8f899d4d6963 Jul 31 '16 at 03:36
  • What part of my answer is not "pure" JS? Did I forget some C++ in there? –  Jun 05 '18 at 09:36
  • 2
    Hey @ErikAigner! My bad, didn't realise this question had answers by two Erik's. I meant `Erik Pukinskis`. I'll update the answer to better reflect that. – Jayant Bhawal Jun 06 '18 at 06:15
  • Worked for the WebView in Android. – The Berga May 04 '20 at 09:20
  • You're better off attempting execCommand first and falling back to this if it fails. setRangeText will nuke the undo/redo stack. execCommand is considered obsolete by MDN, but if you care about UX, the only alternatively is to re-implement the undo/redo stack yourself. – Jools Sep 25 '20 at 08:48
  • If you get the error `setRangeText is not a function`, pass the e.g. textarea object with `$('.mytextarea')[0]` resp. `el[0]`. – Avatar Mar 12 '21 at 15:04
  • Thank you... However, I think it's always better to put the date of any new update. – Khalid Almannai Aug 07 '23 at 06:44
49

For the sake of proper Javascript

HTMLTextAreaElement.prototype.insertAtCaret = function (text) {
  text = text || '';
  if (document.selection) {
    // IE
    this.focus();
    var sel = document.selection.createRange();
    sel.text = text;
  } else if (this.selectionStart || this.selectionStart === 0) {
    // Others
    var startPos = this.selectionStart;
    var endPos = this.selectionEnd;
    this.value = this.value.substring(0, startPos) +
      text +
      this.value.substring(endPos, this.value.length);
    this.selectionStart = startPos + text.length;
    this.selectionEnd = startPos + text.length;
  } else {
    this.value += text;
  }
};
Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
  • very nice extension! works just as expected. Thanks! – Martin Johansson Nov 26 '15 at 11:49
  • Best solution! Thank you – Dima Melnik Jan 09 '18 at 16:23
  • 11
    It's not a good idea to extend the prototype of objects you don't own. Just make it a regular function and it works just as well. – fregante Mar 29 '19 at 02:47
  • 1
    This clears the undo buffer for the edit element after setting `this.value = ...`. Is there a way to preserve it? – c00000fd Dec 28 '19 at 05:13
  • @fregante Then why have a prototype system at all. If you only work with your own types to begin with, there is no need to extend a prototype ever. Extension is what the prototype system is for. –  Mar 05 '21 at 08:42
  • 2
    @ErikAigner That's not right. Before ES6 `A.prototype.fn = X` was the only way to have "classes"/inheritance. Just because you can extend your objects, it doesn't mean you should extend _native_ objects. Imagine 10 years ago you implemented `Array#map`, then `Array#map` became a native API, but incompatible with yours. Now someone opens your codebase and sees `[].map()` and assumes it's the native API. Hello headaches and bugs. – fregante Mar 05 '21 at 21:58
  • What JavaScript project lasts 10 years? :D But you have a point, I guess. –  Jun 10 '21 at 08:38
28

A simple solution that works on firefox, chrome, opera, safari and edge but probably won't work on old IE browsers.

var target = document.getElementById("mytextarea_id")

if (target.setRangeText) {
    //if setRangeText function is supported by current browser
    target.setRangeText(data)
} else {
    target.focus()
    document.execCommand('insertText', false /*no UI*/, data);
}

setRangeText function allow you to replace current selection with the provided text or if no selection then insert the text at cursor position. It's only supported by firefox as far as I know.

For other browsers there is "insertText" command which only affect the html element currently focused and has same behavior as setRangeText

Inspired partially by this article

Max Base
  • 639
  • 1
  • 7
  • 15
Ramast
  • 7,157
  • 3
  • 32
  • 32
  • 1
    This is almost the right way. The article you linked, offers a full solution as a package: [insert-text-at-cursor](https://www.npmjs.com/package/insert-text-at-cursor). However I prefer `execCommand` because it supports `undo` and made [insert-text-textarea](https://github.com/bfred-it/insert-text-textarea). No IE support but smaller – fregante Mar 29 '19 at 01:48
  • 1
    Unfortunately, `execCommand` is considered obsolete by MDN: https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand I don't know why, it seems to be really useful! – Richard Mar 14 '20 at 23:07
  • 1
    Yes, execCommand is used for other browsers, for firefox the function setRangeText is used instead. – Ramast Mar 17 '20 at 14:48
  • Ramast, that isn't what your code does. It will use setRangeText rather than execCommand for any browser that defines it (most). For the behaviour you describe, you need to call document.execCommand first, then check the return value. If it's false, use target.setRangeText. – Jools Jul 08 '20 at 12:30
  • @Jools if setRangeText is supported then why not use it instead of execCommand? Why do I need to try execCommand first? – Ramast Jul 09 '20 at 14:15
  • I meant to match the behaviour you described: "execCommand is used for other browsers, for firefox the function setRangeText is used instead". The reason you might want to use `execCommand` instead is as fregante said: it supports `undo`. If you call `setRangeText`, similar to setting `.value`, it nukes the undo/redo stack e.g. Ctrl+Z and right-click, Undo will become unavailable. – Jools Jul 10 '20 at 15:21
12

I like simple javascript, and I usually have jQuery around. Here's what I came up with, based off mparkuk's:

function typeInTextarea(el, newText) {
    var start = el.prop("selectionStart")
    var end = el.prop("selectionEnd")
    var text = el.val()
    var before = text.substring(0, start)
    var after  = text.substring(end, text.length)
    el.val(before + newText + after)
    el[0].selectionStart = el[0].selectionEnd = start + newText.length
    el.focus()
}

$("button").on("click", function() {
    typeInTextarea($("textarea"), "some text")
    return false
})

Here's a demo: http://codepen.io/erikpukinskis/pen/EjaaMY?editors=101

Max Base
  • 639
  • 1
  • 7
  • 15
Erik Pukinskis
  • 480
  • 5
  • 11
11

Rab's answer works great, but not for Microsoft Edge, so I added a small adaptation for Edge as well:

https://jsfiddle.net/et9borp4/

function insertAtCursor(myField, myValue) {
    //IE support
    if (document.selection) {
        myField.focus();
        sel = document.selection.createRange();
        sel.text = myValue;
    }
    // Microsoft Edge
    else if(window.navigator.userAgent.indexOf("Edge") > -1) {
      var startPos = myField.selectionStart; 
      var endPos = myField.selectionEnd; 

      myField.value = myField.value.substring(0, startPos)+ myValue 
             + myField.value.substring(endPos, myField.value.length); 

      var pos = startPos + myValue.length;
      myField.focus();
      myField.setSelectionRange(pos, pos);
    }
    //MOZILLA and others
    else if (myField.selectionStart || myField.selectionStart == '0') {
        var startPos = myField.selectionStart;
        var endPos = myField.selectionEnd;
        myField.value = myField.value.substring(0, startPos)
            + myValue
            + myField.value.substring(endPos, myField.value.length);
    } else {
        myField.value += myValue;
    }
}
Tornseglare
  • 963
  • 1
  • 11
  • 24
9

function insertAtCaret(text) {
  const textarea = document.querySelector('textarea')
  textarea.setRangeText(
    text,
    textarea.selectionStart,
    textarea.selectionEnd,
    'end'
  )
}

setInterval(() => insertAtCaret('Hello'), 3000)
<textarea cols="60">Stack Overflow Stack Exchange Starbucks Coffee</textarea>
General Grievance
  • 4,555
  • 31
  • 31
  • 45
井上智文
  • 1,905
  • 17
  • 14
7

If the user does not touch the input after text is inserted, the 'input' event is never triggered, and the value attribute will not reflect the change. Therefore it is important to trigger the input event after programmatically inserting text. Focusing the field is not enough.

The following is a copy of Snorvarg's answer with an input trigger at the end:

function insertAtCursor(myField, myValue) {
    //IE support
    if (document.selection) {
        myField.focus();
        sel = document.selection.createRange();
        sel.text = myValue;
    }
    // Microsoft Edge
    else if(window.navigator.userAgent.indexOf("Edge") > -1) {
      var startPos = myField.selectionStart; 
      var endPos = myField.selectionEnd; 

      myField.value = myField.value.substring(0, startPos)+ myValue 
             + myField.value.substring(endPos, myField.value.length); 

      var pos = startPos + myValue.length;
      myField.focus();
      myField.setSelectionRange(pos, pos);
    }
    //MOZILLA and others
    else if (myField.selectionStart || myField.selectionStart == '0') {
        var startPos = myField.selectionStart;
        var endPos = myField.selectionEnd;
        myField.value = myField.value.substring(0, startPos)
            + myValue
            + myField.value.substring(endPos, myField.value.length);
    } else {
        myField.value += myValue;
    }
    triggerEvent(myField,'input');
}

function triggerEvent(el, type){
  if ('createEvent' in document) {
    // modern browsers, IE9+
    var e = document.createEvent('HTMLEvents');
    e.initEvent(type, false, true);
    el.dispatchEvent(e);
  } else {
    // IE 8
    var e = document.createEventObject();
    e.eventType = type;
    el.fireEvent('on'+e.eventType, e);
  }
}

Credit to plainjs.com for the triggerEvent function

More about the oninput event at w3schools.com

I discovered this while creating an emoji-picker for a chat. If the user just select a few emojis and hit the "send" button, the input field is never touched by the user. When checking the value attribute it was always empty, even though the inserted emoji unicodes was visible in the input field. Turns out that if the user does not touch the field the 'input' event never fired and the solution was to trigger it like this. It took quite a while to figure this one out... hope it will save someone some time.

Jette
  • 2,459
  • 28
  • 37
2

The code below is a TypeScript adaptation of the package https://github.com/grassator/insert-text-at-cursor by Dmitriy Kubyshkin.


/**
 * Inserts the given text at the cursor. If the element contains a selection, the selection
 * will be replaced by the text.
 */
export function insertText(input: HTMLTextAreaElement | HTMLInputElement, text: string) {
  // Most of the used APIs only work with the field selected
  input.focus();

  // IE 8-10
  if ((document as any).selection) {
    const ieRange = (document as any).selection.createRange();
    ieRange.text = text;

    // Move cursor after the inserted text
    ieRange.collapse(false /* to the end */);
    ieRange.select();

    return;
  }

  // Webkit + Edge
  const isSuccess = document.execCommand("insertText", false, text);
  if (!isSuccess) {
    const start = input.selectionStart;
    const end = input.selectionEnd;
    // Firefox (non-standard method)
    if (typeof (input as any).setRangeText === "function") {
      (input as any).setRangeText(text);
    } else {
      if (canManipulateViaTextNodes(input)) {
        const textNode = document.createTextNode(text);
        let node = input.firstChild;

        // If textarea is empty, just insert the text
        if (!node) {
          input.appendChild(textNode);
        } else {
          // Otherwise we need to find a nodes for start and end
          let offset = 0;
          let startNode = null;
          let endNode = null;

          // To make a change we just need a Range, not a Selection
          const range = document.createRange();

          while (node && (startNode === null || endNode === null)) {
            const nodeLength = node.nodeValue.length;

            // if start of the selection falls into current node
            if (start >= offset && start <= offset + nodeLength) {
              range.setStart((startNode = node), start - offset);
            }

            // if end of the selection falls into current node
            if (end >= offset && end <= offset + nodeLength) {
              range.setEnd((endNode = node), end - offset);
            }

            offset += nodeLength;
            node = node.nextSibling;
          }

          // If there is some text selected, remove it as we should replace it
          if (start !== end) {
            range.deleteContents();
          }

          // Finally insert a new node. The browser will automatically
          // split start and end nodes into two if necessary
          range.insertNode(textNode);
        }
      } else {
        // For the text input the only way is to replace the whole value :(
        const value = input.value;
        input.value = value.slice(0, start) + text + value.slice(end);
      }
    }

    // Correct the cursor position to be at the end of the insertion
    input.setSelectionRange(start + text.length, start + text.length);

    // Notify any possible listeners of the change
    const e = document.createEvent("UIEvent");
    e.initEvent("input", true, false);
    input.dispatchEvent(e);
  }
}

function canManipulateViaTextNodes(input: HTMLTextAreaElement | HTMLInputElement) {
  if (input.nodeName !== "TEXTAREA") {
    return false;
  }
  let browserSupportsTextareaTextNodes;
  if (typeof browserSupportsTextareaTextNodes === "undefined") {
    const textarea = document.createElement("textarea");
    textarea.value = "1";
    browserSupportsTextareaTextNodes = !!textarea.firstChild;
  }
  return browserSupportsTextareaTextNodes;
}

Andre Pena
  • 56,650
  • 48
  • 196
  • 243
1

Posting modified function for own reference. This example inserts a selected item from <select> object and puts the caret between the tags:

//Inserts a choicebox selected element into target by id
function insertTag(choicebox,id) {
    var ta=document.getElementById(id)
    ta.focus()
    var ss=ta.selectionStart
    var se=ta.selectionEnd
    ta.value=ta.value.substring(0,ss)+'<'+choicebox.value+'>'+'</'+choicebox.value+'>'+ta.value.substring(se,ta.value.length)
    ta.setSelectionRange(ss+choicebox.value.length+2,ss+choicebox.value.length+2)
}
Alexiy
  • 1,966
  • 16
  • 18
0
/**
 * Usage "foo baz".insertInside(4, 0, "bar ") ==> "foo bar baz"
 */
String.prototype.insertInside = function(start, delCount, newSubStr) {
    return this.slice(0, start) + newSubStr + this.slice(start + Math.abs(delCount));
};

$('textarea').bind("keydown keypress", function (event) {
    var val = $(this).val();
    var indexOf = $(this).prop('selectionStart');
    if(event.which === 13) {
        val = val.insertInside(indexOf, 0,  "<br>\n");
        $(this).val(val);
        $(this).focus();
    }
});
Max Base
  • 639
  • 1
  • 7
  • 15
zaarour
  • 107
  • 1
  • 4
  • While this may answer the question, it is better to explain the essential parts of the answer and possibly what was the problem with OPs code. – pirho Dec 05 '17 at 07:47
0

Extending on Adriano's answer, we may also take cursor end into consideration which will make the "replace text" work

$('input[type=button]').on('click', function() {
    var cursorStart = $('#text').prop('selectionStart');
    var cursorEnd = $('#text').prop('selectionEnd');
    var v = $('#text').val();
    var textBefore = v.substring(0,cursorStart);
    var textAfter  = v.substring(cursorEnd);
    $('#text').val(textBefore + $(this).val() + textAfter);
});
Rohan
  • 19
  • 1
  • 5
-1

Changed it to getElementById(myField):

function insertAtCursor(myField, myValue) {
    // IE support
    if (document.selection) {
        document.getElementById(myField).focus();
        sel = document.selection.createRange();
        sel.text = myValue;
    }
    // MOZILLA and others
    else if (document.getElementById(myField).selectionStart || document.getElementById(myField).selectionStart == '0') {
        var startPos = document.getElementById(myField).selectionStart;
        var endPos = document.getElementById(myField).selectionEnd;
        document.getElementById(myField).value =
                document.getElementById(myField).value.substring(0, startPos)
                + myValue
                + document.getElementById(myField).value.substring(endPos, document.getElementById(myField).value.length);
    } else {
        document.getElementById(myField).value += myValue;
    }
}
Max Base
  • 639
  • 1
  • 7
  • 15
tekagami
  • 103
  • 1
  • 4
  • 3
    That's going to hit the DOM way more than you need to.. storing `myfield` as a local is much better for performance – TMan May 06 '14 at 03:49
  • 2
    Wow, really way too much repetition of `document.getElementById(myField)`! Do it once at the top and use a variable name. How many times in a row do you intend to redundantly lookup the same element? – doug65536 Jun 15 '16 at 22:23
  • Thanks for helping ,i got the solution – Muhammad Abdullah Nov 15 '21 at 05:47