80

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!

1

7 Answers 7

165

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(); } } } 
Sign up to request clarification or add additional context in comments.

11 Comments

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.
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.
@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) ); } }
@zeel: I've updated your fiddle to move the cursor to a position immediately after the inserted text: jsfiddle.net/ww3Rk/1. Seems to be fine in IE 9.
@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.
|
25
  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"); }; } 

4 Comments

1+ for the most simple answer to "insert/replace sth. while user is typing". If I read the doc at developer.mozilla.org/en-US/docs/Web/API/Selection/… correctly, one could use selection.collapseToEnd() without the for() loop.
@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.
worked for but only thing is its using a for loop
after inserting, the inserted text is selected, move ONCE only to deselect: selection.modify("move", "right", "character");
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') 

3 Comments

From MDN 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.
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.
loved this solution.
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

Comments

3

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

Comments

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

2 Comments

Hello, could you share a working example of this approach? Thank you so much for your time.
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
-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!

1 Comment

Does not solve the initial question, it appends the text after the content of the html.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.