Javascript trick for 'paste as plain text` in execCommand

JavascriptHtmlDom EventsExeccommand

Javascript Problem Overview


I have a basic editor based on execCommand following the sample introduced here. There are three ways to paste text within the execCommand area:

  • Ctrl+V
  • Right Click -> Paste
  • Right Click -> Paste As Plain Text

I want to allow pasting only plain text without any HTML markup. How can I force the first two actions to paste Plain Text?

Possible Solution: The way I can think of is to set listener for keyup events for (Ctrl+V) and strip HTML tags before paste.

  1. Is it the best solution?
  2. Is it bulletproof to avoid any HTML markup in paste?
  3. How to add listener to Right Click -> Paste?

Javascript Solutions


Solution 1 - Javascript

It will intercept the paste event, cancel the paste, and manually insert the text representation of the clipboard:
http://jsfiddle.net/HBEzc/. This should be the most reliable:

  • It catches all kinds of pasting (Ctrl+V, context menu, etc.)
  • It allows you to get the clipboard data directly as text, so you don't have to do ugly hacks to replace HTML.

I'm not sure of cross-browser support, though.

editor.addEventListener("paste", function(e) {
    // cancel paste
    e.preventDefault();

    // get text representation of clipboard
    var text = (e.originalEvent || e).clipboardData.getData('text/plain');

    // insert text manually
    document.execCommand("insertHTML", false, text);
});

Solution 2 - Javascript

I couldn't get the accepted answer here to work in IE so I did some scouting around and came to this answer which works in IE11 and the latest versions of Chrome and Firefox.

$('[contenteditable]').on('paste', function(e) {
    e.preventDefault();
    var text = '';
    if (e.clipboardData || e.originalEvent.clipboardData) {
      text = (e.originalEvent || e).clipboardData.getData('text/plain');
    } else if (window.clipboardData) {
      text = window.clipboardData.getData('Text');
    }
    if (document.queryCommandSupported('insertText')) {
      document.execCommand('insertText', false, text);
    } else {
      document.execCommand('paste', false, text);
    }
});

Solution 3 - Javascript

A close solution as pimvdb. But it's working of FF, Chrome and IE 9:

editor.addEventListener("paste", function(e) {
    e.preventDefault();
    
    if (e.clipboardData) {
        content = (e.originalEvent || e).clipboardData.getData('text/plain');

        document.execCommand('insertText', false, content);
    }
    else if (window.clipboardData) {
        content = window.clipboardData.getData('Text');

        document.selection.createRange().pasteHTML(content);
    }   
});

Solution 4 - Javascript

Of course the question is already answered and the topic very old but I want to provide my solution as it is simple an clean:

This is inside my paste-event on my contenteditable-div.

var text = '';
var that = $(this);

if (e.clipboardData)
    text = e.clipboardData.getData('text/plain');
else if (window.clipboardData)
    text = window.clipboardData.getData('Text');
else if (e.originalEvent.clipboardData)
    text = $('<div></div>').text(e.originalEvent.clipboardData.getData('text'));

if (document.queryCommandSupported('insertText')) {
    document.execCommand('insertHTML', false, $(text).html());
    return false;
}
else { // IE > 7
    that.find('*').each(function () {
         $(this).addClass('within');
    });

    setTimeout(function () {
          // nochmal alle durchlaufen
          that.find('*').each(function () {
               // wenn das element keine klasse 'within' hat, dann unwrap
               // http://api.jquery.com/unwrap/
               $(this).not('.within').contents().unwrap();
          });
    }, 1);
}

The else-part is from another SO-post I couldn't find anymore...


UPDATE 19.11.2014: The other SO-post

Solution 5 - Javascript

None of the posted answers really seems to work cross browser or the solution is over complicated:

  • The command insertText is not supported by IE
  • Using the paste command results in stack overflow error in IE11

What worked for me (IE11, Edge, Chrome and FF) is the following:

$("div[contenteditable=true]").off('paste').on('paste', function(e) {
    e.preventDefault();
    var text = e.originalEvent.clipboardData ? e.originalEvent.clipboardData.getData('text/plain') : window.clipboardData.getData('Text');
    _insertText(text);
});

function _insertText(text) { 
    // use insertText command if supported
    if (document.queryCommandSupported('insertText')) {
        document.execCommand('insertText', false, text);
    }
    // or insert the text content at the caret's current position
    // replacing eventually selected content
    else {
        var range = document.getSelection().getRangeAt(0);
        range.deleteContents();
        var textNode = document.createTextNode(text);
        range.insertNode(textNode);
        range.selectNodeContents(textNode);
        range.collapse(false);

        var selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
    }
};

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<textarea name="t1"></textarea>
<div style="border: 1px solid;" contenteditable="true">Edit me!</div>
<input />
</body>

Note that the custom paste handler is only needed/working for contenteditable nodes. As both textarea and plain input fields don't support pasting HTML content at all, so nothing needs to be done here.

Solution 6 - Javascript

Firefox does not allow you to access the clipboard data so you'll need to make a 'hack' to get it to work. I've not been able to find a complete solution, however you can fix it for ctrl+v pastes by creating a textarea & pasting to that instead:

//Test if browser has the clipboard object
if (!window.Clipboard)
{
    /*Create a text area element to hold your pasted text
    Textarea is a good choice as it will make anything added to it in to plain text*/			
    var paster = document.createElement("textarea");
    //Hide the textarea
    paster.style.display = "none";				
    document.body.appendChild(paster);
    //Add a new keydown event tou your editor
    editor.addEventListener("keydown", function(e){

        function handlePaste()
        {
	        //Get the text from the textarea
            var pastedText = paster.value;
            //Move the cursor back to the editor
	        editor.focus();
            //Check that there is a value. FF throws an error for insertHTML with an empty string
            if (pastedText !== "") document.execCommand("insertHTML", false, pastedText);
            //Reset the textarea
	        paster.value = "";
        }

        if (e.which === 86 && e.ctrlKey)
        {
	        //ctrl+v => paste
	        //Set the focus on your textarea
            paster.focus();
            //We need to wait a bit, otherwise FF will still try to paste in the editor => settimeout
	        window.setTimeout(handlePaste, 1);
        }

    }, false);
}
else //Pretty much the answer given by pimvdb above
{
    //Add listener for paster to force paste-as-plain-text
    editor.addEventListener("paste", function(e){

	    //Get the plain text from the clipboard
	    var plain = (!!e.clipboardData)? e.clipboardData.getData("text/plain") : window.clipboardData.getData("Text");
            //Stop default paste action
	    e.preventDefault();
	    //Paste plain text
	    document.execCommand("insertHTML", false, plain);

    }, false);
}

Solution 7 - Javascript

I was also working on a plain text paste and I started to hate all the execCommand and getData errors, so I decided to do it the classic way and it works like a charm:

$('#editor').bind('paste', function(){
    var before = document.getElementById('editor').innerHTML;
    setTimeout(function(){
        var after = document.getElementById('editor').innerHTML;
        var pos1 = -1;
        var pos2 = -1;
        for (var i=0; i<after.length; i++) {
            if (pos1 == -1 && before.substr(i, 1) != after.substr(i, 1)) pos1 = i;
            if (pos2 == -1 && before.substr(before.length-i-1, 1) != after.substr(after.length-i-1, 1)) pos2 = i;
        }
        var pasted = after.substr(pos1, after.length-pos2-pos1);
        var replace = pasted.replace(/<[^>]+>/g, '');
        var replaced = after.substr(0, pos1)+replace+after.substr(pos1+pasted.length);
        document.getElementById('editor').innerHTML = replaced;
    }, 100);
});

The code with my notations can be found here: http://www.albertmartin.de/blog/code.php/20/plain-text-paste-with-javascript

Solution 8 - Javascript

function PasteString() {
    var editor = document.getElementById("TemplateSubPage");
    editor.focus();
  //  editor.select();
    document.execCommand('Paste');
}

function CopyString() {
    var input = document.getElementById("TemplateSubPage");
    input.focus();
   // input.select();
    document.execCommand('Copy');
    if (document.selection || document.textSelection) {
        document.selection.empty();
    } else if (window.getSelection) {
        window.getSelection().removeAllRanges();
    }
}

Above code works for me in IE10 and IE11 and now also works in Chrome and Safari. Not tested in Firefox.

Solution 9 - Javascript

In IE11, execCommand doesn't work well. I use below code for IE11 <div class="wmd-input" id="wmd-input-md" contenteditable=true> is my div box.

I read clipboard data from window.clipboardData and modify div's textContent and give caret.

I give timeout for setting caret, because if I don't set timeout, a caret goes to end of div.

and you should read clipboardData in IE11 in below way. If you don't do it, newline caracter is not handled properly, so caret goes wrong.

var tempDiv = document.createElement("div");
tempDiv.textContent = window.clipboardData.getData("text");
var text = tempDiv.textContent;

Tested on IE11 and chrome. It may not work on IE9

document.getElementById("wmd-input-md").addEventListener("paste", function (e) {
    if (!e.clipboardData) {
        //For IE11
        e.preventDefault();
        e.stopPropagation();
        var tempDiv = document.createElement("div");
        tempDiv.textContent = window.clipboardData.getData("text");
        var text = tempDiv.textContent;
        var selection = document.getSelection();
        var start = selection.anchorOffset > selection.focusOffset ? selection.focusOffset : selection.anchorOffset;
        var end = selection.anchorOffset > selection.focusOffset ? selection.anchorOffset : selection.focusOffset;                    
        selection.removeAllRanges();
                                    
        setTimeout(function () {    
            $(".wmd-input").text($(".wmd-input").text().substring(0, start)
              + text
              + $(".wmd-input").text().substring(end));
            var range = document.createRange();
            range.setStart(document.getElementsByClassName("wmd-input")[0].firstChild, start + text.length);
            range.setEnd(document.getElementsByClassName("wmd-input")[0].firstChild, start + text.length);
                    
            selection.addRange(range);
        }, 1);
    } else {                
        //For Chrome
        e.preventDefault();
        var text = e.clipboardData.getData("text");

        var selection = document.getSelection();
        var start = selection.anchorOffset > selection.focusOffset ? selection.focusOffset : selection.anchorOffset;
        var end = selection.anchorOffset > selection.focusOffset ? selection.anchorOffset : selection.focusOffset;
        
        $(this).text($(this).text().substring(0, start)
          + text
          + $(this).text().substring(end));

        var range = document.createRange();
        range.setStart($(this)[0].firstChild, start + text.length);
        range.setEnd($(this)[0].firstChild, start + text.length);
        selection.removeAllRanges();
        selection.addRange(range);
    }
}, false);

Solution 10 - Javascript

After along search and trying I have found somehow kind of optimal solution

> what is important to keep in mind

// /\x0D/g return key ASCII
window.document.execCommand('insertHTML', false, text.replace('/\x0D/g', "\\n"))


and give the css style white-space: pre-line //for displaying

var contenteditable = document.querySelector('[contenteditable]')
            contenteditable.addEventListener('paste', function(e){
                let text = ''
                contenteditable.classList.remove('empty')                
                e.preventDefault()
                text = (e.originalEvent || e).clipboardData.getData('text/plain')
                e.clipboardData.setData('text/plain', '')                 
                window.document.execCommand('insertHTML', false, text.replace('/\x0D/g', "\\n"))// /\x0D/g return ASCII
        })

#input{
  width: 100%;
  height: 100px;
  border: 1px solid black;
  white-space: pre-line; 
}

<div id="input"contenteditable="true">
        <p>
        </p>
</div>   

Solution 11 - Javascript

OK as everybody is trying to work around clipboard data, checking keypress event, and using execCommand.

I thought of this

CODE

handlePastEvent=()=>{
    document.querySelector("#new-task-content-1").addEventListener("paste",function(e)
    {
        
        setTimeout(function(){
            document.querySelector("#new-task-content-1").innerHTML=document.querySelector("#new-task-content-1").innerText.trim();
        },1);
    });

}
handlePastEvent();

<div contenteditable="true" id="new-task-content-1">You cann't paste HTML here</div>

Solution 12 - Javascript

In 2022, you can use CSS user-modify: read-write-plaintext-only to archive this

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-css -->

    .plain-text-only {
      user-modify: read-write-plaintext-only;
      -moz-user-modify: read-write-plaintext-only;
      -webkit-user-modify: read-write-plaintext-only;
    }

    div[contenteditable] {
      padding: 1rem 0.5rem;
      border: 2px solid #eee;
      border-radius: 4px;
      margin-bottom: 2rem;
    }

<!-- language: lang-html -->

    <div contenteditable class="plain-text-only">Can't paste HTML here</div>
    <div contenteditable>HTML styled text free</div>

<!-- end snippet -->

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGooglebotView Question on Stackoverflow
Solution 1 - JavascriptpimvdbView Answer on Stackoverflow
Solution 2 - JavascriptJamie BarkerView Answer on Stackoverflow
Solution 3 - JavascriptAdriano Galesso AlvesView Answer on Stackoverflow
Solution 4 - JavascriptwebprogrammerView Answer on Stackoverflow
Solution 5 - JavascriptdprView Answer on Stackoverflow
Solution 6 - JavascriptMidnightTortoiseView Answer on Stackoverflow
Solution 7 - JavascriptAlbertView Answer on Stackoverflow
Solution 8 - JavascriptNikhil GhuseView Answer on Stackoverflow
Solution 9 - JavascriptLee HojinView Answer on Stackoverflow
Solution 10 - JavascriptmoogaView Answer on Stackoverflow
Solution 11 - JavascriptArun SharmaView Answer on Stackoverflow
Solution 12 - JavascriptSon Tr.View Answer on Stackoverflow