77

I have a contenteditable div where I need to insert text at the caret position,

This can be easily done in IE by document.selection.createRange().text = "banana"

Is there a similar way of implementing this in Firefox/Chrome?

(I know a solution exists here , but it can't be used in contenteditable div, and looks clumsy)

Thank you!

Community
  • 1
  • 1
user314362
  • 1,205
  • 2
  • 11
  • 12
  • If you wish to insert html at cursor, see http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div – Kes115 Aug 17 '16 at 14:49

7 Answers7

162

The following function will insert text at the caret position and delete the existing selection. It works in all the mainstream desktop browsers:

function insertTextAtCaret(text) {
    var sel, range;
    if (window.getSelection) {
        sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) {
            range = sel.getRangeAt(0);
            range.deleteContents();
            range.insertNode( document.createTextNode(text) );
        }
    } else if (document.selection && document.selection.createRange) {
        document.selection.createRange().text = text;
    }
}

UPDATE

Based on comment, here's some code for saving and restoring the selection. Before displaying your context menu, you should store the return value of saveSelection in a variable and then pass that variable into restoreSelection to restore the selection after hiding the context menu and before inserting text.

function saveSelection() {
    if (window.getSelection) {
        sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) {
            return sel.getRangeAt(0);
        }
    } else if (document.selection && document.selection.createRange) {
        return document.selection.createRange();
    }
    return null;
}

function restoreSelection(range) {
    if (range) {
        if (window.getSelection) {
            sel = window.getSelection();
            sel.removeAllRanges();
            sel.addRange(range);
        } else if (document.selection && range.select) {
            range.select();
        }
    }
}
vsync
  • 118,978
  • 58
  • 307
  • 400
Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • 1
    Thanks for the help, I'm using this as a part of online code editor in which whenever the user types "." after a object name, a context menu pops-up with a list of its methods (intellisense/ code completion) And when the user clicks a method name, the text needs to be pasted after the dot in the code area div. But so far the "method name" gets pasted inside the contextmenu not inside the code area. – user314362 May 28 '10 at 06:54
  • 2
    OK. In which case I'd suggest storing a copy of the Range / TextRange representing the selection at the point at which you're about to display the context menu and then restore the selection from that after hiding the context menu but before inserting the text. – Tim Down May 28 '10 at 08:40
  • @Tim, is there a built-in Rangy way of doing insertTextAtCursor() instead ? Thanks a lot. – Michael Low Oct 17 '11 at 06:41
  • 1
    @mikel: Not really: you'd just use a cut-down version of the first branch. `function insertTextAtCursor(text) { var range, sel = rangy.getSelection(); if (sel.rangeCount) { range = sel.getRangeAt(0); range.insertNode( document.createTextNode(text) ); } }` – Tim Down Oct 17 '11 at 08:29
  • This works, however (in IE at least, only place I have tested it so far) there is a huge delay (~3 seconds) betweem each insertion. So if I set it up to insert `'text'` when I press a button, I can repetedly click the button but only works every ~3 seconds. If I only use the IE code, it works fine, though it dosn't work with inserting `'\t'` (only makes a space, not a tab). – zeel Feb 13 '12 at 17:04
  • @zeel: It shouldn't be that slow. Is there an example page I can see? – Tim Down Feb 13 '12 at 21:55
  • http://jsfiddle.net/zeel/ww3Rk/ tab key inserts `'\t'` and enter key inserts `'\n'`. The insert button inserts `'TEXT'`. Problem present in IE9. Absent in Chrome. Also: how do you make it place the cursor after the insertion? It does this in IE, but in chrome the tab/newline/etc is placed after the cursor, which makes it difficult to use. – zeel Feb 14 '12 at 02:14
  • 3
    @zeel: I've updated your fiddle to move the cursor to a position immediately after the inserted text: http://jsfiddle.net/ww3Rk/1/. Seems to be fine in IE 9. – Tim Down Feb 15 '12 at 14:48
  • @TimDown, I tried but it is not working with my Editor. More details http://stackoverflow.com/questions/16881333/wysiwyg-editor-place-html-content-in-current-position – Billa Jun 02 '13 at 09:41
  • if select the text out of editor, this method will insert text there. This is not expect – Fuxian Jan 13 '16 at 09:02
  • 1
    @Fuxian: I expect it but I'm used to the selection API, which is the same for both editable and non-editable text. If you're worried about that then you could check whether the element containing the selection is editable before inserting the text. I can provide code for that if you need. – Tim Down Jan 13 '16 at 09:23
23
  1. Get a Selection Object with window.getSelection().
  2. Use Selection.getRangeAt(0).insertNode() to add a textnode.
  3. If necessary, move the cursor position behind the added text with Selection.modify(). (Not standardized, but this feature is supported in Firefox, Chrome and Safari)

    function insertTextAtCursor(text)
    {
        let selection = window.getSelection();
        let range = selection.getRangeAt(0);
        range.deleteContents();
        let node = document.createTextNode(text);
        range.insertNode(node);
    
        for(let position = 0; position != text.length; position++)
        {
            selection.modify("move", "right", "character");
        };
    }
    
Martin Wantke
  • 4,287
  • 33
  • 21
  • 1
    This solution has better behaviour than the accepted answer, +1. – goblin GONE Apr 09 '20 at 00:32
  • 3
    1+ for the most simple answer to "insert/replace sth. while user is typing". If I read the doc at https://developer.mozilla.org/en-US/docs/Web/API/Selection/collapseToEnd correctly, one could use selection.collapseToEnd() without the for() loop. – Munneson Aug 28 '20 at 14:25
  • @Munneson: have you tried it? That's only if something is currently selected. The inserted node is not selected, it shows up to the right of the selection. – Han Seoul-Oh May 25 '21 at 03:19
  • worked for but only thing is its using a for loop – raj May 15 '22 at 08:03
  • Very nice. Worked in Remirror – lwdthe1 May 04 '23 at 17:39
  • after inserting, the inserted text is selected, move ONCE only to deselect: `selection.modify("move", "right", "character");` – Dee Aug 15 '23 at 08:57
11

UPD: since ~2020 solution is obsoleted (despite it can work yet)

// <div contenteditable id="myeditable">
// const editable = document.getElementById('myeditable')
// editable.focus()
// document.execCommand('insertHTML', false, '<b>B</b>anana')
document.execCommand('insertText', false, 'banana')
alex_1948511
  • 6,073
  • 2
  • 20
  • 21
  • 1
    From [MDN](https://developer.mozilla.org/en-US/docs/Web/API/document/execCommand) regarding `execCommand`: This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it. – Jens Apr 04 '20 at 11:02
  • 2
    I would really hope that when making `execCommand` obsolete, browsers would have thought of proposing an elegant API to interact with editable content. It didn't happen. – Frenchcooc Nov 25 '20 at 14:51
  • loved this solution. – raj May 15 '22 at 08:05
4

I have used next code to insert icons in chat msg

<div class="chat-msg-text" id="chat_message_text" contenteditable="true"></div>

<script>
var lastCaretPos = 0;
var parentNode;
var range;
var selection;

$(function(){
    $('#chat_message_text').focus();

    $('#chat_message_text').on('keyup mouseup',function (e){
        selection = window.getSelection();
        range = selection.getRangeAt(0);
        parentNode = range.commonAncestorContainer.parentNode;
    });
})

function insertTextAtCursor(text) { 

    if($(parentNode).parents().is('#chat_message_text') || $(parentNode).is('#chat_message_text') )
    {
        var span = document.createElement('span');              
        span.innerHTML=text;

        range.deleteContents();        
        range.insertNode(span);  
        //cursor at the last with this
        range.collapse(false);
        selection.removeAllRanges();
        selection.addRange(range);

    }
    else
    {
        msg_text = $("#chat_message_text").html()
        $("#chat_message_text").html(text+msg_text).focus()                 
    }
}

</script>

enter image description here

2

If you are working with rich editors (like DraftJs) but have no access to their APIs (e.g. modifying from an extension), these are the solutions I've found:

  • Dispatching a beforeinput event, this is the recommended way, and most editors support
target.dispatchEvent(new InputEvent("beforeinput", {
    inputType: "insertText",
    data: text,
    bubbles: true,
    cancelable: true
}))
  • Dispatching a paste event
const data = new DataTransfer();
data.setData(
    'text/plain',
    text
);
target.dispatchEvent(new ClipboardEvent("paste", {
    dataType: "text/plain",
    data: text,
    bubbles: true,
    clipboardData: data,
    cancelable: true
}));

This last one uses 2 different methods:


If you want to replace all existing text, you have to select it first

function selectTargetText(target) {
    const selection = window.getSelection();
    const range = document.createRange();
    range.selectNodeContents(target);
    selection.removeAllRanges();
    selection.addRange(range);
}

selectTargetText(target)

// wait for selection before dispatching the `beforeinput` event
document.addEventListener("selectionchange",()=>{
    target.dispatchEvent(new InputEvent("beforeinput", {
        inputType: "insertText",
        data: text,
        bubbles: true,
        cancelable: true
    }))
},{once: true})
Madacol
  • 3,611
  • 34
  • 33
  • Hello, could you share a working example of this approach? Thank you so much for your time. – Zoren Konte Jul 18 '22 at 08:43
  • My answer was specific for rich editors, since they listen to these events. In those examples I gave, you only need to replace `target` and `text`, e.g. `document.getElementById('myId')` and `"sometext"`. `target` must be any Html element inside the rich editor – Madacol Jul 20 '22 at 12:12
1

Pasting plain text can be handled with the following code.

const editorEle = document.getElementById('editor');

// Handle the `paste` event
editorEle.addEventListener('paste', function (e) {
    // Prevent the default action
    e.preventDefault();

    // Get the copied text from the clipboard
    const text = e.clipboardData
        ? (e.originalEvent || e).clipboardData.getData('text/plain')
        : // For IE
        window.clipboardData
        ? window.clipboardData.getData('Text')
        : '';

    if (document.queryCommandSupported('insertText')) {
        document.execCommand('insertText', false, text);
    } else {
        // Insert text at the current position of caret
        const range = document.getSelection().getRangeAt(0);
        range.deleteContents();

        const textNode = document.createTextNode(text);
        range.insertNode(textNode);
        range.selectNodeContents(textNode);
        range.collapse(false);

        const selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
    }
});
Peter
  • 1,124
  • 14
  • 17
-5

just an easier method with jquery:

copy the entire content of the div

var oldhtml=$('#elementID').html();

var tobejoined='<span>hii</span>';

//element with new html would be

$('#elementID').html(oldhtml+tobejoined);

simple!

  • 3
    Does not solve the initial question, it appends the text after the content of the html. – Tobi Aug 09 '17 at 17:23