How can a web application detect a paste event and retrieve the data to be pasted?
I would like to remove HTML content before the text is pasted into a rich text editor.
Cleaning the text after being pasted afterwards works, but the problem is that all previous formatting is lost. For example, I can write a sentence in the editor and make it bold, but when I paste new text, all formatting is lost. I want to clean just the text that is pasted, and leave any previous formatting untouched.
Ideally, the solution should work across all modern browsers (e.g., MSIE, Gecko, Chrome, and Safari).
Note that MSIE has clipboardData.getData(), but I could not find similar functionality for other browsers.
Solution #1 (Plain Text only and requires Firefox 22+)
Works for IE6+, FF 22+, Chrome, Safari, Edge
(Only tested in IE9+, but should work for lower versions)
If you need support for pasting HTML or Firefox <= 22, see Solution #2.
function handlePaste(e) {
var clipboardData, pastedData;
// Stop data actually being pasted into div
e.stopPropagation();
e.preventDefault();
// Get pasted data via clipboard API
clipboardData = e.clipboardData || window.clipboardData;
pastedData = clipboardData.getData('Text');
// Do whatever with pasteddata
alert(pastedData);
}
document.getElementById('editableDiv').addEventListener('paste', handlePaste);
<div id='editableDiv' contenteditable='true'>Paste</div>
JSFiddle
Note that this solution uses the parameter 'Text' for the getData function, which is non-standard. However, it works in all browsers at the time of writing.
Solution #2 (HTML and works for Firefox <= 22)
Tested in IE6+, FF 3.5+, Chrome, Safari, Edge
var editableDiv = document.getElementById('editableDiv');
function handlepaste(e) {
var types, pastedData, savedContent;
// Browsers that support the 'text/html' type in the Clipboard API (Chrome, Firefox 22+)
if (e && e.clipboardData && e.clipboardData.types && e.clipboardData.getData) {
// Check for 'text/html' in types list. See abligh's answer below for deatils on
// why the DOMStringList bit is needed. We cannot fall back to 'text/plain' as
// Safari/Edge don't advertise HTML data even if it is available
types = e.clipboardData.types;
if (((types instanceof DOMStringList) && types.contains("text/html")) || (types.indexOf && types.indexOf('text/html') !== -1)) {
// Extract data and pass it to callback
pastedData = e.clipboardData.getData('text/html');
processPaste(editableDiv, pastedData);
// Stop the data from actually being pasted
e.stopPropagation();
e.preventDefault();
return false;
}
}
// Everything else: Move existing element contents to a DocumentFragment for safekeeping
savedContent = document.createDocumentFragment();
while (editableDiv.childNodes.length > 0) {
savedContent.appendChild(editableDiv.childNodes[0]);
}
// Then wait for browser to paste content into it and cleanup
waitForPastedData(editableDiv, savedContent);
return true;
}
function waitForPastedData(elem, savedContent) {
// If data has been processes by browser, process it
if (elem.childNodes && elem.childNodes.length > 0) {
// Retrieve pasted content via innerHTML
// (Alternatively loop through elem.childNodes or elem.getElementsByTagName here)
var pastedData = elem.innerHTML;
// Restore saved content
elem.innerHTML = "";
elem.appendChild(savedContent);
// Call callback
processPaste(elem, pastedData);
}
// Else wait 20ms and try again
else {
setTimeout(function() {
waitForPastedData(elem, savedContent)
}, 20);
}
}
function processPaste(elem, pastedData) {
// Do whatever with gathered data;
alert(pastedData);
elem.focus();
}
// Modern browsers. Note: 3rd argument is required for Firefox <= 6
if (editableDiv.addEventListener) {
editableDiv.addEventListener('paste', handlepaste, false);
}
// IE <= 8
else {
editableDiv.attachEvent('onpaste', handlepaste);
}
<div id='div' contenteditable='true'>Paste</div>
JSFiddle
Explanation
The onpaste event of the div has the handlePaste function attached to it and passed a single argument: the event object for the paste event. Of particular interest to us is the clipboardData property of this event which enables clipboard access in non-ie browsers. In IE the equivalent is window.clipboardData, although this has a slightly different API.
See resources section below.
The handlepaste function:
This function has two branches.
The first checks for the existence of event.clipboardData and checks whether it's types property contains 'text/html' (types may be either a DOMStringList which is checked using the contains method, or a string which is checked using the indexOf method). If all of these conditions are fulfilled, then we proceed as in solution #1, except with 'text/html' instead of 'text/plain'. This currently works in Chrome and Firefox 22+.
If this method is not supported (all other browsers), then we
Save the element's contents to a DocumentFragment
Empty the element
Call the waitForPastedData function
The waitforpastedata function:
This function first polls for the pasted data (once per 20ms), which is necessary because it doesn't appear straight away. When the data has appeared it:
Saves the innerHTML of the editable div (which is now the pasted data) to a variable
Restores the content saved in the DocumentFragment
Calls the 'processPaste' function with the retrieved data
The processpaste function:
Does arbitrary things with the pasted data. In this case we just alert the data, you can do whatever you like. You will probably want to run the pasted data through some kind of data sanitizing process.
Saving and restoring the cursor position
In a real situation you would probably want to save the selection before, and restore it afterwards (Set cursor position on contentEditable <div>). You could then insert the pasted data at the position the cursor was in when the user initiated the paste action.
Resources on MDN
paste event
DocumentFragment
DomStringList
Thanks to Tim Down to suggesting the use of a DocumentFragment, and abligh for catching an error in Firefox due to the use of DOMStringList instead of a string for clipboardData.types
The situation has changed since writing this answer: now that Firefox has added support in version 22, all major browsers now support accessing the clipboard data in a paste event. See Nico Burns's answer for an example.
In the past this was not generally possible in a cross-browser way. The ideal would be to be able to get the pasted content via the paste event, which is possible in recent browsers but not in some older browsers (in particular, Firefox < 22).
When you need to support older browsers, what you can do is quite involved and a bit of a hack that will work in Firefox 2+, IE 5.5+ and WebKit browsers such as Safari or Chrome. Recent versions of both TinyMCE and CKEditor use this technique:
Detect a ctrl-v / shift-ins event using a keypress event handler
In that handler, save the current user selection, add a textarea element off-screen (say at left -1000px) to the document, turn designMode off and call focus() on the textarea, thus moving the caret and effectively redirecting the paste
Set a very brief timer (say 1 millisecond) in the event handler to call another function that stores the textarea value, removes the textarea from the document, turns designMode back on, restores the user selection and pastes the text in.
Note that this will only work for keyboard paste events and not pastes from the context or edit menus. By the time the paste event fires, it's too late to redirect the caret into the textarea (in some browsers, at least).
In the unlikely event that you need to support Firefox 2, note that you'll need to place the textarea in the parent document rather than the WYSIWYG editor iframe's document in that browser.
Simple version:
document.querySelector('[contenteditable]').addEventListener('paste', (e) => {
e.preventDefault();
const text = (e.originalEvent || e).clipboardData.getData('text/plain');
window.document.execCommand('insertText', false, text);
});
Using clipboardData
Demo : http://jsbin.com/nozifexasu/edit?js,output
Edge, Firefox, Chrome, Safari, Opera tested.
⚠ Document.execCommand() is obsolete now.
Note: Remember to check input/output at server-side also (like PHP strip-tags)
Live Demo
Tested on Chrome / FF / IE11
There is a Chrome/IE annoyance which is that these browsers add <div> element for each new line. There is a post about this here and it can be fixed by setting the contenteditable element to be display:inline-block
Select some highlighted HTML and paste it here:
function onPaste(e){
var content;
e.preventDefault();
if( e.clipboardData ){
content = e.clipboardData.getData('text/plain');
document.execCommand('insertText', false, content);
return false;
}
else if( window.clipboardData ){
content = window.clipboardData.getData('Text');
if (window.getSelection)
window.getSelection().getRangeAt(0).insertNode( document.createTextNode(content) );
}
}
/////// EVENT BINDING /////////
document.querySelector('[contenteditable]').addEventListener('paste', onPaste);
[contenteditable]{
/* chroem bug: https://stackoverflow.com/a/24689420/104380 */
display:inline-block;
width: calc(100% - 40px);
min-height:120px;
margin:10px;
padding:10px;
border:1px dashed green;
}
/*
mark HTML inside the "contenteditable"
(Shouldn't be any OFC!)'
*/
[contenteditable] *{
background-color:red;
}
<div contenteditable></div>
I've written a little proof of concept for Tim Downs proposal here with off-screen textarea. And here goes the code:
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script language="JavaScript">
$(document).ready(function()
{
var ctrlDown = false;
var ctrlKey = 17, vKey = 86, cKey = 67;
$(document).keydown(function(e)
{
if (e.keyCode == ctrlKey) ctrlDown = true;
}).keyup(function(e)
{
if (e.keyCode == ctrlKey) ctrlDown = false;
});
$(".capture-paste").keydown(function(e)
{
if (ctrlDown && (e.keyCode == vKey || e.keyCode == cKey)){
$("#area").css("display","block");
$("#area").focus();
}
});
$(".capture-paste").keyup(function(e)
{
if (ctrlDown && (e.keyCode == vKey || e.keyCode == cKey)){
$("#area").blur();
//do your sanitation check or whatever stuff here
$("#paste-output").text($("#area").val());
$("#area").val("");
$("#area").css("display","none");
}
});
});
</script>
</head>
<body class="capture-paste">
<div id="paste-output"></div>
<div>
<textarea id="area" style="display: none; position: absolute; left: -99em;"></textarea>
</div>
</body>
</html>
Just copy and paste the whole code into one html file and try to paste (using ctrl-v) text from clipboard anywhere on the document.
I've tested it in IE9 and new versions of Firefox, Chrome and Opera. Works quite well. Also it's good that one can use whatever key combination he prefers to triger this functionality. Of course don't forget to include jQuery sources.
Feel free to use this code and if you come with some improvements or problems please post them back. Also note that I'm no Javascript developer so I may have missed something (=>do your own testign).
Based on l2aelba anwser. This was tested on FF, Safari, Chrome, IE (8,9,10 and 11)
$("#editText").on("paste", function (e) {
e.preventDefault();
var text;
var clp = (e.originalEvent || e).clipboardData;
if (clp === undefined || clp === null) {
text = window.clipboardData.getData("text") || "";
if (text !== "") {
if (window.getSelection) {
var newNode = document.createElement("span");
newNode.innerHTML = text;
window.getSelection().getRangeAt(0).insertNode(newNode);
} else {
document.selection.createRange().pasteHTML(text);
}
}
} else {
text = clp.getData('text/plain') || "";
if (text !== "") {
document.execCommand('insertText', false, text);
}
}
});
This one does not use any setTimeout().
I have used this great article to achieve cross browser support.
$(document).on("focus", "input[type=text],textarea", function (e) {
var t = e.target;
if (!$(t).data("EventListenerSet")) {
//get length of field before paste
var keyup = function () {
$(this).data("lastLength", $(this).val().length);
};
$(t).data("lastLength", $(t).val().length);
//catch paste event
var paste = function () {
$(this).data("paste", 1);//Opera 11.11+
};
//process modified data, if paste occured
var func = function () {
if ($(this).data("paste")) {
alert(this.value.substr($(this).data("lastLength")));
$(this).data("paste", 0);
this.value = this.value.substr(0, $(this).data("lastLength"));
$(t).data("lastLength", $(t).val().length);
}
};
if (window.addEventListener) {
t.addEventListener('keyup', keyup, false);
t.addEventListener('paste', paste, false);
t.addEventListener('input', func, false);
}
else {//IE
t.attachEvent('onkeyup', function () {
keyup.call(t);
});
t.attachEvent('onpaste', function () {
paste.call(t);
});
t.attachEvent('onpropertychange', function () {
func.call(t);
});
}
$(t).data("EventListenerSet", 1);
}
});
This code is extended with selection handle before paste:
demo
For cleaning the pasted text and replacing the currently selected text with the pasted text the matter is pretty trivial:
<div id='div' contenteditable='true' onpaste='handlepaste(this, event)'>Paste</div>
JS:
function handlepaste(el, e) {
document.execCommand('insertText', false, e.clipboardData.getData('text/plain'));
e.preventDefault();
}
This was too long for a comment on Nico's answer, which I don't think works on Firefox any more (per the comments), and didn't work for me on Safari as is.
Firstly, you now appear to be able to read directly from the clipboard. Rather than code like:
if (/text\/plain/.test(e.clipboardData.types)) {
// shouldn't this be writing to elem.value for text/plain anyway?
elem.innerHTML = e.clipboardData.getData('text/plain');
}
use:
types = e.clipboardData.types;
if (((types instanceof DOMStringList) && types.contains("text/plain")) ||
(/text\/plain/.test(types))) {
// shouldn't this be writing to elem.value for text/plain anyway?
elem.innerHTML = e.clipboardData.getData('text/plain');
}
because Firefox has a types field which is a DOMStringList which does not implement test.
Next Firefox will not allow paste unless the focus is in a contenteditable=true field.
Finally, Firefox will not allow paste reliably unless the focus is in a textarea (or perhaps input) which is not only contenteditable=true but also:
not display:none
not visibility:hidden
not zero sized
I was trying to hide the text field so I could make paste work over a JS VNC emulator (i.e. it was going to a remote client and there was no actually textarea etc to paste into). I found trying to hide the text field in the above gave symptoms where it worked sometimes, but typically failed on the second paste (or when the field was cleared to prevent pasting the same data twice) as the field lost focus and would not properly regain it despite focus(). The solution I came up with was to put it at z-order: -1000, make it display:none, make it as 1px by 1px, and set all the colours to transparent. Yuck.
On Safari, you the second part of the above applies, i.e. you need to have a textarea which is not display:none.
This should work on all browsers that support the onpaste event and the mutation observer.
This solution goes a step beyond getting the text only, it actually allows you to edit the pasted content before it get pasted into an element.
It works by using contenteditable, onpaste event (supported by all major browsers) en mutation observers (supported by Chrome, Firefox and IE11+)
step 1
Create a HTML-element with contenteditable
<div contenteditable="true" id="target_paste_element"></div>
step 2
In your Javascript code add the following event
document.getElementById("target_paste_element").addEventListener("paste", pasteEventVerifierEditor.bind(window, pasteCallBack), false);
We need to bind pasteCallBack, since the mutation observer will be called asynchronously.
step 3
Add the following function to your code
function pasteEventVerifierEditor(callback, e)
{
//is fired on a paste event.
//pastes content into another contenteditable div, mutation observer observes this, content get pasted, dom tree is copied and can be referenced through call back.
//create temp div
//save the caret position.
savedCaret = saveSelection(document.getElementById("target_paste_element"));
var tempDiv = document.createElement("div");
tempDiv.id = "id_tempDiv_paste_editor";
//tempDiv.style.display = "none";
document.body.appendChild(tempDiv);
tempDiv.contentEditable = "true";
tempDiv.focus();
//we have to wait for the change to occur.
//attach a mutation observer
if (window['MutationObserver'])
{
//this is new functionality
//observer is present in firefox/chrome and IE11
// select the target node
// create an observer instance
tempDiv.observer = new MutationObserver(pasteMutationObserver.bind(window, callback));
// configuration of the observer:
var config = { attributes: false, childList: true, characterData: true, subtree: true };
// pass in the target node, as well as the observer options
tempDiv.observer.observe(tempDiv, config);
}
}
function pasteMutationObserver(callback)
{
document.getElementById("id_tempDiv_paste_editor").observer.disconnect();
delete document.getElementById("id_tempDiv_paste_editor").observer;
if (callback)
{
//return the copied dom tree to the supplied callback.
//copy to avoid closures.
callback.apply(document.getElementById("id_tempDiv_paste_editor").cloneNode(true));
}
document.body.removeChild(document.getElementById("id_tempDiv_paste_editor"));
}
function pasteCallBack()
{
//paste the content into the element.
restoreSelection(document.getElementById("target_paste_element"), savedCaret);
delete savedCaret;
pasteHtmlAtCaret(this.innerHTML, false, true);
}
saveSelection = function(containerEl) {
if (containerEl == document.activeElement)
{
var range = window.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
};
}
};
restoreSelection = function(containerEl, savedSel) {
containerEl.focus();
var charIndex = 0, range = document.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl], node, foundStart = false, stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
function pasteHtmlAtCaret(html, returnInNode, selectPastedContent) {
//function written by Tim Down
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// only relatively recently standardized and is not supported in
// some browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(), node, lastNode;
while ( (node = el.firstChild) ) {
lastNode = frag.appendChild(node);
}
var firstNode = frag.firstChild;
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
if (returnInNode)
{
range.setStart(lastNode, 0); //this part is edited, set caret inside pasted node.
}
else
{
range.setStartAfter(lastNode);
}
if (selectPastedContent) {
range.setStartBefore(firstNode);
} else {
range.collapse(true);
}
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if ( (sel = document.selection) && sel.type != "Control") {
// IE < 9
var originalRange = sel.createRange();
originalRange.collapse(true);
sel.createRange().pasteHTML(html);
if (selectPastedContent) {
range = sel.createRange();
range.setEndPoint("StartToStart", originalRange);
range.select();
}
}
}
What the code does:
Somebody fires the paste event by using ctrl-v, contextmenu or other means
In the paste event a new element with contenteditable is created (an element with contenteditable has elevated privileges)
The caret position of the target element is saved.
The focus is set to the new element
The content gets pasted into the new element and is rendered in the DOM.
The mutation observer catches this (it registers all changes to the dom tree and content). Then fires the mutation event.
The dom of the pasted content gets cloned into a variable and returned to the callback. The temporary element is destroyed.
The callback receives the cloned DOM. The caret is restored. You can edit this before you append it to your target. element. In this example I'm using Tim Downs functions for saving/restoring the caret and pasting HTML into the element.
Example
document.getElementById("target_paste_element").addEventListener("paste", pasteEventVerifierEditor.bind(window, pasteCallBack), false);
function pasteEventVerifierEditor(callback, e) {
//is fired on a paste event.
//pastes content into another contenteditable div, mutation observer observes this, content get pasted, dom tree is copied and can be referenced through call back.
//create temp div
//save the caret position.
savedCaret = saveSelection(document.getElementById("target_paste_element"));
var tempDiv = document.createElement("div");
tempDiv.id = "id_tempDiv_paste_editor";
//tempDiv.style.display = "none";
document.body.appendChild(tempDiv);
tempDiv.contentEditable = "true";
tempDiv.focus();
//we have to wait for the change to occur.
//attach a mutation observer
if (window['MutationObserver']) {
//this is new functionality
//observer is present in firefox/chrome and IE11
// select the target node
// create an observer instance
tempDiv.observer = new MutationObserver(pasteMutationObserver.bind(window, callback));
// configuration of the observer:
var config = {
attributes: false,
childList: true,
characterData: true,
subtree: true
};
// pass in the target node, as well as the observer options
tempDiv.observer.observe(tempDiv, config);
}
}
function pasteMutationObserver(callback) {
document.getElementById("id_tempDiv_paste_editor").observer.disconnect();
delete document.getElementById("id_tempDiv_paste_editor").observer;
if (callback) {
//return the copied dom tree to the supplied callback.
//copy to avoid closures.
callback.apply(document.getElementById("id_tempDiv_paste_editor").cloneNode(true));
}
document.body.removeChild(document.getElementById("id_tempDiv_paste_editor"));
}
function pasteCallBack() {
//paste the content into the element.
restoreSelection(document.getElementById("target_paste_element"), savedCaret);
delete savedCaret;
//edit the copied content by slicing
pasteHtmlAtCaret(this.innerHTML.slice(3), false, true);
}
saveSelection = function(containerEl) {
if (containerEl == document.activeElement) {
var range = window.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
};
}
};
restoreSelection = function(containerEl, savedSel) {
containerEl.focus();
var charIndex = 0,
range = document.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl],
node, foundStart = false,
stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
function pasteHtmlAtCaret(html, returnInNode, selectPastedContent) {
//function written by Tim Down
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// only relatively recently standardized and is not supported in
// some browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(),
node, lastNode;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
var firstNode = frag.firstChild;
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
if (returnInNode) {
range.setStart(lastNode, 0); //this part is edited, set caret inside pasted node.
} else {
range.setStartAfter(lastNode);
}
if (selectPastedContent) {
range.setStartBefore(firstNode);
} else {
range.collapse(true);
}
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if ((sel = document.selection) && sel.type != "Control") {
// IE < 9
var originalRange = sel.createRange();
originalRange.collapse(true);
sel.createRange().pasteHTML(html);
if (selectPastedContent) {
range = sel.createRange();
range.setEndPoint("StartToStart", originalRange);
range.select();
}
}
}
div {
border: 1px solid black;
height: 50px;
padding: 5px;
}
<div contenteditable="true" id="target_paste_element"></div>
Many thanks to Tim Down
See this post for the answer:
Get the pasted content on document on paste event
First that comes to mind is the pastehandler of google's closure lib
http://closure-library.googlecode.com/svn/trunk/closure/goog/demos/pastehandler.html
Solution that works for me is adding event listener to paste event if you are pasting to a text input.
Since paste event happens before text in input changes, inside my on paste handler I create a deferred function inside which I check for changes in my input box that happened on paste:
onPaste: function() {
var oThis = this;
setTimeout(function() { // Defer until onPaste() is done
console.log('paste', oThis.input.value);
// Manipulate pasted input
}, 1);
}
Simple solution:
document.onpaste = function(e) {
var pasted = e.clipboardData.getData('Text');
console.log(pasted)
}
This worked for me :
function onPasteMe(currentData, maxLen) {
// validate max length of pasted text
var totalCharacterCount = window.clipboardData.getData('Text').length;
}
<input type="text" onPaste="return onPasteMe(this, 50);" />
function myFunct( e ){
e.preventDefault();
var pastedText = undefined;
if( window.clipboardData && window.clipboardData.getData ){
pastedText = window.clipboardData.getData('Text');
}
else if( e.clipboardData && e.clipboardData.getData ){
pastedText = e.clipboardData.getData('text/plain');
}
//work with text
}
document.onpaste = myFunct;
You can do this in this way:
use this jQuery plugin for pre & post paste events:
$.fn.pasteEvents = function( delay ) {
if (delay == undefined) delay = 20;
return $(this).each(function() {
var $el = $(this);
$el.on("paste", function() {
$el.trigger("prepaste");
setTimeout(function() { $el.trigger("postpaste"); }, delay);
});
});
};
Now you can use this plugin;:
$('#txt').on("prepaste", function() {
$(this).find("*").each(function(){
var tmp=new Date.getTime();
$(this).data("uid",tmp);
});
}).pasteEvents();
$('#txt').on("postpaste", function() {
$(this).find("*").each(function(){
if(!$(this).data("uid")){
$(this).removeClass();
$(this).removeAttr("style id");
}
});
}).pasteEvents();
Explaination
First set a uid for all existing elements as data attribute.
Then compare all nodes POST PASTE event. So by comparing you can identify the newly inserted one because they will have a uid, then just remove style/class/id attribute from newly created elements, so that you can keep your older formatting.
$('#dom').on('paste',function (e){
setTimeout(function(){
console.log(e.currentTarget.value);
},0);
});
Just let the browser paste as usual in its content editable div and then after the paste swap any span elements used for custom text styles with the text itself. This seems to work okay in internet explorer and the other browsers I tried...
$('[contenteditable]').on('paste', function (e) {
setTimeout(function () {
$(e.target).children('span').each(function () {
$(this).replaceWith($(this).text());
});
}, 0);
});
This solution assumes that you are running jQuery and that you don't want text formatting in any of your content editable divs.
The plus side is that it's super simple.
This solution is replace the html tag, it's simple and cross-browser; check this jsfiddle: http://jsfiddle.net/tomwan/cbp1u2cx/1/, core code:
var $plainText = $("#plainText");
var $linkOnly = $("#linkOnly");
var $html = $("#html");
$plainText.on('paste', function (e) {
window.setTimeout(function () {
$plainText.html(removeAllTags(replaceStyleAttr($plainText.html())));
}, 0);
});
$linkOnly.on('paste', function (e) {
window.setTimeout(function () {
$linkOnly.html(removeTagsExcludeA(replaceStyleAttr($linkOnly.html())));
}, 0);
});
function replaceStyleAttr (str) {
return str.replace(/(<[\w\W]*?)(style)([\w\W]*?>)/g, function (a, b, c, d) {
return b + 'style_replace' + d;
});
}
function removeTagsExcludeA (str) {
return str.replace(/<\/?((?!a)(\w+))\s*[\w\W]*?>/g, '');
}
function removeAllTags (str) {
return str.replace(/<\/?(\w+)\s*[\w\W]*?>/g, '');
}
notice: you should do some work about xss filter on the back side because this solution cannot filter strings like '<<>>'
This is an existing code posted above but I have updated it for IE's, the bug was when the existing text is selected and pasted will not delete the selected content. This has been fixed by the below code
selRange.deleteContents();
See complete code below
$('[contenteditable]').on('paste', function (e) {
e.preventDefault();
if (window.clipboardData) {
content = window.clipboardData.getData('Text');
if (window.getSelection) {
var selObj = window.getSelection();
var selRange = selObj.getRangeAt(0);
selRange.deleteContents();
selRange.insertNode(document.createTextNode(content));
}
} else if (e.originalEvent.clipboardData) {
content = (e.originalEvent || e).clipboardData.getData('text/plain');
document.execCommand('insertText', false, content);
}
});
The paste event is fired when the user has initiated a "paste" action through the browser's user interface.
HTML
<div class="source" contenteditable="true">Try copying text from this box...</div>
<div class="target" contenteditable="true">...and pasting it into this one</div>
JavaScript
const target = document.querySelector('div.target');
target.addEventListener('paste', (event) => {
let paste = (event.clipboardData || window.clipboardData).getData('text');
paste = paste.toUpperCase();
const selection = window.getSelection();
if (!selection.rangeCount) return false;
selection.deleteFromDocument();
selection.getRangeAt(0).insertNode(document.createTextNode(paste));
event.preventDefault();
});
Know more
In order to support the copy and paste of plain text both on IE11 and Chrome I used the following function.
It has two if statements distinguishing IE from chome and executing the approriate code. In the first part the code reads the text from the clipboard, in the second part it pastes the text right in the cursor position replacing the selected text if present.
In particular, for the paste on IE the code gets the selection range, deletes the selected text, inserts the text from the clipboard in a new html text node, reconfigure the range to insert the text node at the cursor position plus the text length.
The code is the following:
editable.addEventListener("paste", function(e) {
e.preventDefault();
// Get text from the clipboard.
var text = '';
if (e.clipboardData || (e.originalEvent && e.originalEvent.clipboardData)) {
text = (e.originalEvent || e).clipboardData.getData('text/plain');
} else if (window.clipboardData) {
text = window.clipboardData.getData('Text');
}
// bool to indicate if the user agent is internet explorer
let isIE = /Trident/.test(navigator.userAgent);
if (document.queryCommandSupported('insertText') && !isIE) {
// Chrome etc.
document.execCommand('insertText', false, text);
} else {
// IE.
// delete content from selection.
var sel = window.getSelection();
var range = sel.getRangeAt(0);
document.execCommand("delete");
// paste plain text in a new node.
var newNode = document.createTextNode(text);
range.insertNode(newNode);
range.setStart(newNode, 0)
range.setEnd(newNode, newNode.childNodes.length);
sel.removeAllRanges;
sel.addRange(range);
}
}, false);
In particular, in order to paste text on IE many answers I found this instruction document.execCommand('paste', false, text); that doesn't work on IE11 because the browser calls the paste event again and again many times. So I replaced it with the functions on the range object.
Another issue is that on IE11, depending on the version, the function document.execCommand('insertText', false, text); sometimes is available other times not, so I checked explicitly whether the browser is IE and for it executed the part of the code based on the range selection (see else).
Related
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.
Is it the best solution?
Is it bulletproof to avoid any HTML markup in paste?
How to add listener to Right Click -> Paste?
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);
});
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);
}
});
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);
}
});
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
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.
Note that execCommand() is deprecated, so avoid using it in your production environments. Do something like this instead:
const editor = document.querySelector('[contentEditable]')
editor.addEventListener('paste', handlePaste)
function handlePaste(e) {
e.preventDefault()
const text = (e.clipboardData || window.clipboardData).getData('text')
const selection = window.getSelection()
if (selection.rangeCount) {
selection.deleteFromDocument()
selection.getRangeAt(0).insertNode(document.createTextNode(text))
}
}
[contentEditable] {
padding: 1em;
border: 1px solid black;
}
<div contentEditable>Paste HTML here</div>
References:
https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event
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);
}
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
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.
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);
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>
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>
In 2022, you can use CSS user-modify: read-write-plaintext-only to archive this
.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;
}
<div contenteditable class="plain-text-only">Can't paste HTML here</div>
<div contenteditable>HTML styled text free</div>
How can a web application detect a paste event and retrieve the data to be pasted?
I would like to remove HTML content before the text is pasted into a rich text editor.
Cleaning the text after being pasted afterwards works, but the problem is that all previous formatting is lost. For example, I can write a sentence in the editor and make it bold, but when I paste new text, all formatting is lost. I want to clean just the text that is pasted, and leave any previous formatting untouched.
Ideally, the solution should work across all modern browsers (e.g., MSIE, Gecko, Chrome, and Safari).
Note that MSIE has clipboardData.getData(), but I could not find similar functionality for other browsers.
Solution #1 (Plain Text only and requires Firefox 22+)
Works for IE6+, FF 22+, Chrome, Safari, Edge
(Only tested in IE9+, but should work for lower versions)
If you need support for pasting HTML or Firefox <= 22, see Solution #2.
function handlePaste(e) {
var clipboardData, pastedData;
// Stop data actually being pasted into div
e.stopPropagation();
e.preventDefault();
// Get pasted data via clipboard API
clipboardData = e.clipboardData || window.clipboardData;
pastedData = clipboardData.getData('Text');
// Do whatever with pasteddata
alert(pastedData);
}
document.getElementById('editableDiv').addEventListener('paste', handlePaste);
<div id='editableDiv' contenteditable='true'>Paste</div>
JSFiddle
Note that this solution uses the parameter 'Text' for the getData function, which is non-standard. However, it works in all browsers at the time of writing.
Solution #2 (HTML and works for Firefox <= 22)
Tested in IE6+, FF 3.5+, Chrome, Safari, Edge
var editableDiv = document.getElementById('editableDiv');
function handlepaste(e) {
var types, pastedData, savedContent;
// Browsers that support the 'text/html' type in the Clipboard API (Chrome, Firefox 22+)
if (e && e.clipboardData && e.clipboardData.types && e.clipboardData.getData) {
// Check for 'text/html' in types list. See abligh's answer below for deatils on
// why the DOMStringList bit is needed. We cannot fall back to 'text/plain' as
// Safari/Edge don't advertise HTML data even if it is available
types = e.clipboardData.types;
if (((types instanceof DOMStringList) && types.contains("text/html")) || (types.indexOf && types.indexOf('text/html') !== -1)) {
// Extract data and pass it to callback
pastedData = e.clipboardData.getData('text/html');
processPaste(editableDiv, pastedData);
// Stop the data from actually being pasted
e.stopPropagation();
e.preventDefault();
return false;
}
}
// Everything else: Move existing element contents to a DocumentFragment for safekeeping
savedContent = document.createDocumentFragment();
while (editableDiv.childNodes.length > 0) {
savedContent.appendChild(editableDiv.childNodes[0]);
}
// Then wait for browser to paste content into it and cleanup
waitForPastedData(editableDiv, savedContent);
return true;
}
function waitForPastedData(elem, savedContent) {
// If data has been processes by browser, process it
if (elem.childNodes && elem.childNodes.length > 0) {
// Retrieve pasted content via innerHTML
// (Alternatively loop through elem.childNodes or elem.getElementsByTagName here)
var pastedData = elem.innerHTML;
// Restore saved content
elem.innerHTML = "";
elem.appendChild(savedContent);
// Call callback
processPaste(elem, pastedData);
}
// Else wait 20ms and try again
else {
setTimeout(function() {
waitForPastedData(elem, savedContent)
}, 20);
}
}
function processPaste(elem, pastedData) {
// Do whatever with gathered data;
alert(pastedData);
elem.focus();
}
// Modern browsers. Note: 3rd argument is required for Firefox <= 6
if (editableDiv.addEventListener) {
editableDiv.addEventListener('paste', handlepaste, false);
}
// IE <= 8
else {
editableDiv.attachEvent('onpaste', handlepaste);
}
<div id='div' contenteditable='true'>Paste</div>
JSFiddle
Explanation
The onpaste event of the div has the handlePaste function attached to it and passed a single argument: the event object for the paste event. Of particular interest to us is the clipboardData property of this event which enables clipboard access in non-ie browsers. In IE the equivalent is window.clipboardData, although this has a slightly different API.
See resources section below.
The handlepaste function:
This function has two branches.
The first checks for the existence of event.clipboardData and checks whether it's types property contains 'text/html' (types may be either a DOMStringList which is checked using the contains method, or a string which is checked using the indexOf method). If all of these conditions are fulfilled, then we proceed as in solution #1, except with 'text/html' instead of 'text/plain'. This currently works in Chrome and Firefox 22+.
If this method is not supported (all other browsers), then we
Save the element's contents to a DocumentFragment
Empty the element
Call the waitForPastedData function
The waitforpastedata function:
This function first polls for the pasted data (once per 20ms), which is necessary because it doesn't appear straight away. When the data has appeared it:
Saves the innerHTML of the editable div (which is now the pasted data) to a variable
Restores the content saved in the DocumentFragment
Calls the 'processPaste' function with the retrieved data
The processpaste function:
Does arbitrary things with the pasted data. In this case we just alert the data, you can do whatever you like. You will probably want to run the pasted data through some kind of data sanitizing process.
Saving and restoring the cursor position
In a real situation you would probably want to save the selection before, and restore it afterwards (Set cursor position on contentEditable <div>). You could then insert the pasted data at the position the cursor was in when the user initiated the paste action.
Resources on MDN
paste event
DocumentFragment
DomStringList
Thanks to Tim Down to suggesting the use of a DocumentFragment, and abligh for catching an error in Firefox due to the use of DOMStringList instead of a string for clipboardData.types
The situation has changed since writing this answer: now that Firefox has added support in version 22, all major browsers now support accessing the clipboard data in a paste event. See Nico Burns's answer for an example.
In the past this was not generally possible in a cross-browser way. The ideal would be to be able to get the pasted content via the paste event, which is possible in recent browsers but not in some older browsers (in particular, Firefox < 22).
When you need to support older browsers, what you can do is quite involved and a bit of a hack that will work in Firefox 2+, IE 5.5+ and WebKit browsers such as Safari or Chrome. Recent versions of both TinyMCE and CKEditor use this technique:
Detect a ctrl-v / shift-ins event using a keypress event handler
In that handler, save the current user selection, add a textarea element off-screen (say at left -1000px) to the document, turn designMode off and call focus() on the textarea, thus moving the caret and effectively redirecting the paste
Set a very brief timer (say 1 millisecond) in the event handler to call another function that stores the textarea value, removes the textarea from the document, turns designMode back on, restores the user selection and pastes the text in.
Note that this will only work for keyboard paste events and not pastes from the context or edit menus. By the time the paste event fires, it's too late to redirect the caret into the textarea (in some browsers, at least).
In the unlikely event that you need to support Firefox 2, note that you'll need to place the textarea in the parent document rather than the WYSIWYG editor iframe's document in that browser.
Simple version:
document.querySelector('[contenteditable]').addEventListener('paste', (e) => {
e.preventDefault();
const text = (e.originalEvent || e).clipboardData.getData('text/plain');
window.document.execCommand('insertText', false, text);
});
Using clipboardData
Demo : http://jsbin.com/nozifexasu/edit?js,output
Edge, Firefox, Chrome, Safari, Opera tested.
⚠ Document.execCommand() is obsolete now.
Note: Remember to check input/output at server-side also (like PHP strip-tags)
Live Demo
Tested on Chrome / FF / IE11
There is a Chrome/IE annoyance which is that these browsers add <div> element for each new line. There is a post about this here and it can be fixed by setting the contenteditable element to be display:inline-block
Select some highlighted HTML and paste it here:
function onPaste(e){
var content;
e.preventDefault();
if( e.clipboardData ){
content = e.clipboardData.getData('text/plain');
document.execCommand('insertText', false, content);
return false;
}
else if( window.clipboardData ){
content = window.clipboardData.getData('Text');
if (window.getSelection)
window.getSelection().getRangeAt(0).insertNode( document.createTextNode(content) );
}
}
/////// EVENT BINDING /////////
document.querySelector('[contenteditable]').addEventListener('paste', onPaste);
[contenteditable]{
/* chroem bug: https://stackoverflow.com/a/24689420/104380 */
display:inline-block;
width: calc(100% - 40px);
min-height:120px;
margin:10px;
padding:10px;
border:1px dashed green;
}
/*
mark HTML inside the "contenteditable"
(Shouldn't be any OFC!)'
*/
[contenteditable] *{
background-color:red;
}
<div contenteditable></div>
I've written a little proof of concept for Tim Downs proposal here with off-screen textarea. And here goes the code:
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script language="JavaScript">
$(document).ready(function()
{
var ctrlDown = false;
var ctrlKey = 17, vKey = 86, cKey = 67;
$(document).keydown(function(e)
{
if (e.keyCode == ctrlKey) ctrlDown = true;
}).keyup(function(e)
{
if (e.keyCode == ctrlKey) ctrlDown = false;
});
$(".capture-paste").keydown(function(e)
{
if (ctrlDown && (e.keyCode == vKey || e.keyCode == cKey)){
$("#area").css("display","block");
$("#area").focus();
}
});
$(".capture-paste").keyup(function(e)
{
if (ctrlDown && (e.keyCode == vKey || e.keyCode == cKey)){
$("#area").blur();
//do your sanitation check or whatever stuff here
$("#paste-output").text($("#area").val());
$("#area").val("");
$("#area").css("display","none");
}
});
});
</script>
</head>
<body class="capture-paste">
<div id="paste-output"></div>
<div>
<textarea id="area" style="display: none; position: absolute; left: -99em;"></textarea>
</div>
</body>
</html>
Just copy and paste the whole code into one html file and try to paste (using ctrl-v) text from clipboard anywhere on the document.
I've tested it in IE9 and new versions of Firefox, Chrome and Opera. Works quite well. Also it's good that one can use whatever key combination he prefers to triger this functionality. Of course don't forget to include jQuery sources.
Feel free to use this code and if you come with some improvements or problems please post them back. Also note that I'm no Javascript developer so I may have missed something (=>do your own testign).
Based on l2aelba anwser. This was tested on FF, Safari, Chrome, IE (8,9,10 and 11)
$("#editText").on("paste", function (e) {
e.preventDefault();
var text;
var clp = (e.originalEvent || e).clipboardData;
if (clp === undefined || clp === null) {
text = window.clipboardData.getData("text") || "";
if (text !== "") {
if (window.getSelection) {
var newNode = document.createElement("span");
newNode.innerHTML = text;
window.getSelection().getRangeAt(0).insertNode(newNode);
} else {
document.selection.createRange().pasteHTML(text);
}
}
} else {
text = clp.getData('text/plain') || "";
if (text !== "") {
document.execCommand('insertText', false, text);
}
}
});
This one does not use any setTimeout().
I have used this great article to achieve cross browser support.
$(document).on("focus", "input[type=text],textarea", function (e) {
var t = e.target;
if (!$(t).data("EventListenerSet")) {
//get length of field before paste
var keyup = function () {
$(this).data("lastLength", $(this).val().length);
};
$(t).data("lastLength", $(t).val().length);
//catch paste event
var paste = function () {
$(this).data("paste", 1);//Opera 11.11+
};
//process modified data, if paste occured
var func = function () {
if ($(this).data("paste")) {
alert(this.value.substr($(this).data("lastLength")));
$(this).data("paste", 0);
this.value = this.value.substr(0, $(this).data("lastLength"));
$(t).data("lastLength", $(t).val().length);
}
};
if (window.addEventListener) {
t.addEventListener('keyup', keyup, false);
t.addEventListener('paste', paste, false);
t.addEventListener('input', func, false);
}
else {//IE
t.attachEvent('onkeyup', function () {
keyup.call(t);
});
t.attachEvent('onpaste', function () {
paste.call(t);
});
t.attachEvent('onpropertychange', function () {
func.call(t);
});
}
$(t).data("EventListenerSet", 1);
}
});
This code is extended with selection handle before paste:
demo
For cleaning the pasted text and replacing the currently selected text with the pasted text the matter is pretty trivial:
<div id='div' contenteditable='true' onpaste='handlepaste(this, event)'>Paste</div>
JS:
function handlepaste(el, e) {
document.execCommand('insertText', false, e.clipboardData.getData('text/plain'));
e.preventDefault();
}
This was too long for a comment on Nico's answer, which I don't think works on Firefox any more (per the comments), and didn't work for me on Safari as is.
Firstly, you now appear to be able to read directly from the clipboard. Rather than code like:
if (/text\/plain/.test(e.clipboardData.types)) {
// shouldn't this be writing to elem.value for text/plain anyway?
elem.innerHTML = e.clipboardData.getData('text/plain');
}
use:
types = e.clipboardData.types;
if (((types instanceof DOMStringList) && types.contains("text/plain")) ||
(/text\/plain/.test(types))) {
// shouldn't this be writing to elem.value for text/plain anyway?
elem.innerHTML = e.clipboardData.getData('text/plain');
}
because Firefox has a types field which is a DOMStringList which does not implement test.
Next Firefox will not allow paste unless the focus is in a contenteditable=true field.
Finally, Firefox will not allow paste reliably unless the focus is in a textarea (or perhaps input) which is not only contenteditable=true but also:
not display:none
not visibility:hidden
not zero sized
I was trying to hide the text field so I could make paste work over a JS VNC emulator (i.e. it was going to a remote client and there was no actually textarea etc to paste into). I found trying to hide the text field in the above gave symptoms where it worked sometimes, but typically failed on the second paste (or when the field was cleared to prevent pasting the same data twice) as the field lost focus and would not properly regain it despite focus(). The solution I came up with was to put it at z-order: -1000, make it display:none, make it as 1px by 1px, and set all the colours to transparent. Yuck.
On Safari, you the second part of the above applies, i.e. you need to have a textarea which is not display:none.
This should work on all browsers that support the onpaste event and the mutation observer.
This solution goes a step beyond getting the text only, it actually allows you to edit the pasted content before it get pasted into an element.
It works by using contenteditable, onpaste event (supported by all major browsers) en mutation observers (supported by Chrome, Firefox and IE11+)
step 1
Create a HTML-element with contenteditable
<div contenteditable="true" id="target_paste_element"></div>
step 2
In your Javascript code add the following event
document.getElementById("target_paste_element").addEventListener("paste", pasteEventVerifierEditor.bind(window, pasteCallBack), false);
We need to bind pasteCallBack, since the mutation observer will be called asynchronously.
step 3
Add the following function to your code
function pasteEventVerifierEditor(callback, e)
{
//is fired on a paste event.
//pastes content into another contenteditable div, mutation observer observes this, content get pasted, dom tree is copied and can be referenced through call back.
//create temp div
//save the caret position.
savedCaret = saveSelection(document.getElementById("target_paste_element"));
var tempDiv = document.createElement("div");
tempDiv.id = "id_tempDiv_paste_editor";
//tempDiv.style.display = "none";
document.body.appendChild(tempDiv);
tempDiv.contentEditable = "true";
tempDiv.focus();
//we have to wait for the change to occur.
//attach a mutation observer
if (window['MutationObserver'])
{
//this is new functionality
//observer is present in firefox/chrome and IE11
// select the target node
// create an observer instance
tempDiv.observer = new MutationObserver(pasteMutationObserver.bind(window, callback));
// configuration of the observer:
var config = { attributes: false, childList: true, characterData: true, subtree: true };
// pass in the target node, as well as the observer options
tempDiv.observer.observe(tempDiv, config);
}
}
function pasteMutationObserver(callback)
{
document.getElementById("id_tempDiv_paste_editor").observer.disconnect();
delete document.getElementById("id_tempDiv_paste_editor").observer;
if (callback)
{
//return the copied dom tree to the supplied callback.
//copy to avoid closures.
callback.apply(document.getElementById("id_tempDiv_paste_editor").cloneNode(true));
}
document.body.removeChild(document.getElementById("id_tempDiv_paste_editor"));
}
function pasteCallBack()
{
//paste the content into the element.
restoreSelection(document.getElementById("target_paste_element"), savedCaret);
delete savedCaret;
pasteHtmlAtCaret(this.innerHTML, false, true);
}
saveSelection = function(containerEl) {
if (containerEl == document.activeElement)
{
var range = window.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
};
}
};
restoreSelection = function(containerEl, savedSel) {
containerEl.focus();
var charIndex = 0, range = document.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl], node, foundStart = false, stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
function pasteHtmlAtCaret(html, returnInNode, selectPastedContent) {
//function written by Tim Down
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// only relatively recently standardized and is not supported in
// some browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(), node, lastNode;
while ( (node = el.firstChild) ) {
lastNode = frag.appendChild(node);
}
var firstNode = frag.firstChild;
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
if (returnInNode)
{
range.setStart(lastNode, 0); //this part is edited, set caret inside pasted node.
}
else
{
range.setStartAfter(lastNode);
}
if (selectPastedContent) {
range.setStartBefore(firstNode);
} else {
range.collapse(true);
}
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if ( (sel = document.selection) && sel.type != "Control") {
// IE < 9
var originalRange = sel.createRange();
originalRange.collapse(true);
sel.createRange().pasteHTML(html);
if (selectPastedContent) {
range = sel.createRange();
range.setEndPoint("StartToStart", originalRange);
range.select();
}
}
}
What the code does:
Somebody fires the paste event by using ctrl-v, contextmenu or other means
In the paste event a new element with contenteditable is created (an element with contenteditable has elevated privileges)
The caret position of the target element is saved.
The focus is set to the new element
The content gets pasted into the new element and is rendered in the DOM.
The mutation observer catches this (it registers all changes to the dom tree and content). Then fires the mutation event.
The dom of the pasted content gets cloned into a variable and returned to the callback. The temporary element is destroyed.
The callback receives the cloned DOM. The caret is restored. You can edit this before you append it to your target. element. In this example I'm using Tim Downs functions for saving/restoring the caret and pasting HTML into the element.
Example
document.getElementById("target_paste_element").addEventListener("paste", pasteEventVerifierEditor.bind(window, pasteCallBack), false);
function pasteEventVerifierEditor(callback, e) {
//is fired on a paste event.
//pastes content into another contenteditable div, mutation observer observes this, content get pasted, dom tree is copied and can be referenced through call back.
//create temp div
//save the caret position.
savedCaret = saveSelection(document.getElementById("target_paste_element"));
var tempDiv = document.createElement("div");
tempDiv.id = "id_tempDiv_paste_editor";
//tempDiv.style.display = "none";
document.body.appendChild(tempDiv);
tempDiv.contentEditable = "true";
tempDiv.focus();
//we have to wait for the change to occur.
//attach a mutation observer
if (window['MutationObserver']) {
//this is new functionality
//observer is present in firefox/chrome and IE11
// select the target node
// create an observer instance
tempDiv.observer = new MutationObserver(pasteMutationObserver.bind(window, callback));
// configuration of the observer:
var config = {
attributes: false,
childList: true,
characterData: true,
subtree: true
};
// pass in the target node, as well as the observer options
tempDiv.observer.observe(tempDiv, config);
}
}
function pasteMutationObserver(callback) {
document.getElementById("id_tempDiv_paste_editor").observer.disconnect();
delete document.getElementById("id_tempDiv_paste_editor").observer;
if (callback) {
//return the copied dom tree to the supplied callback.
//copy to avoid closures.
callback.apply(document.getElementById("id_tempDiv_paste_editor").cloneNode(true));
}
document.body.removeChild(document.getElementById("id_tempDiv_paste_editor"));
}
function pasteCallBack() {
//paste the content into the element.
restoreSelection(document.getElementById("target_paste_element"), savedCaret);
delete savedCaret;
//edit the copied content by slicing
pasteHtmlAtCaret(this.innerHTML.slice(3), false, true);
}
saveSelection = function(containerEl) {
if (containerEl == document.activeElement) {
var range = window.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
};
}
};
restoreSelection = function(containerEl, savedSel) {
containerEl.focus();
var charIndex = 0,
range = document.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl],
node, foundStart = false,
stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
function pasteHtmlAtCaret(html, returnInNode, selectPastedContent) {
//function written by Tim Down
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// only relatively recently standardized and is not supported in
// some browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(),
node, lastNode;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
var firstNode = frag.firstChild;
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
if (returnInNode) {
range.setStart(lastNode, 0); //this part is edited, set caret inside pasted node.
} else {
range.setStartAfter(lastNode);
}
if (selectPastedContent) {
range.setStartBefore(firstNode);
} else {
range.collapse(true);
}
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if ((sel = document.selection) && sel.type != "Control") {
// IE < 9
var originalRange = sel.createRange();
originalRange.collapse(true);
sel.createRange().pasteHTML(html);
if (selectPastedContent) {
range = sel.createRange();
range.setEndPoint("StartToStart", originalRange);
range.select();
}
}
}
div {
border: 1px solid black;
height: 50px;
padding: 5px;
}
<div contenteditable="true" id="target_paste_element"></div>
Many thanks to Tim Down
See this post for the answer:
Get the pasted content on document on paste event
First that comes to mind is the pastehandler of google's closure lib
http://closure-library.googlecode.com/svn/trunk/closure/goog/demos/pastehandler.html
Solution that works for me is adding event listener to paste event if you are pasting to a text input.
Since paste event happens before text in input changes, inside my on paste handler I create a deferred function inside which I check for changes in my input box that happened on paste:
onPaste: function() {
var oThis = this;
setTimeout(function() { // Defer until onPaste() is done
console.log('paste', oThis.input.value);
// Manipulate pasted input
}, 1);
}
Simple solution:
document.onpaste = function(e) {
var pasted = e.clipboardData.getData('Text');
console.log(pasted)
}
This worked for me :
function onPasteMe(currentData, maxLen) {
// validate max length of pasted text
var totalCharacterCount = window.clipboardData.getData('Text').length;
}
<input type="text" onPaste="return onPasteMe(this, 50);" />
function myFunct( e ){
e.preventDefault();
var pastedText = undefined;
if( window.clipboardData && window.clipboardData.getData ){
pastedText = window.clipboardData.getData('Text');
}
else if( e.clipboardData && e.clipboardData.getData ){
pastedText = e.clipboardData.getData('text/plain');
}
//work with text
}
document.onpaste = myFunct;
You can do this in this way:
use this jQuery plugin for pre & post paste events:
$.fn.pasteEvents = function( delay ) {
if (delay == undefined) delay = 20;
return $(this).each(function() {
var $el = $(this);
$el.on("paste", function() {
$el.trigger("prepaste");
setTimeout(function() { $el.trigger("postpaste"); }, delay);
});
});
};
Now you can use this plugin;:
$('#txt').on("prepaste", function() {
$(this).find("*").each(function(){
var tmp=new Date.getTime();
$(this).data("uid",tmp);
});
}).pasteEvents();
$('#txt').on("postpaste", function() {
$(this).find("*").each(function(){
if(!$(this).data("uid")){
$(this).removeClass();
$(this).removeAttr("style id");
}
});
}).pasteEvents();
Explaination
First set a uid for all existing elements as data attribute.
Then compare all nodes POST PASTE event. So by comparing you can identify the newly inserted one because they will have a uid, then just remove style/class/id attribute from newly created elements, so that you can keep your older formatting.
$('#dom').on('paste',function (e){
setTimeout(function(){
console.log(e.currentTarget.value);
},0);
});
Just let the browser paste as usual in its content editable div and then after the paste swap any span elements used for custom text styles with the text itself. This seems to work okay in internet explorer and the other browsers I tried...
$('[contenteditable]').on('paste', function (e) {
setTimeout(function () {
$(e.target).children('span').each(function () {
$(this).replaceWith($(this).text());
});
}, 0);
});
This solution assumes that you are running jQuery and that you don't want text formatting in any of your content editable divs.
The plus side is that it's super simple.
This solution is replace the html tag, it's simple and cross-browser; check this jsfiddle: http://jsfiddle.net/tomwan/cbp1u2cx/1/, core code:
var $plainText = $("#plainText");
var $linkOnly = $("#linkOnly");
var $html = $("#html");
$plainText.on('paste', function (e) {
window.setTimeout(function () {
$plainText.html(removeAllTags(replaceStyleAttr($plainText.html())));
}, 0);
});
$linkOnly.on('paste', function (e) {
window.setTimeout(function () {
$linkOnly.html(removeTagsExcludeA(replaceStyleAttr($linkOnly.html())));
}, 0);
});
function replaceStyleAttr (str) {
return str.replace(/(<[\w\W]*?)(style)([\w\W]*?>)/g, function (a, b, c, d) {
return b + 'style_replace' + d;
});
}
function removeTagsExcludeA (str) {
return str.replace(/<\/?((?!a)(\w+))\s*[\w\W]*?>/g, '');
}
function removeAllTags (str) {
return str.replace(/<\/?(\w+)\s*[\w\W]*?>/g, '');
}
notice: you should do some work about xss filter on the back side because this solution cannot filter strings like '<<>>'
This is an existing code posted above but I have updated it for IE's, the bug was when the existing text is selected and pasted will not delete the selected content. This has been fixed by the below code
selRange.deleteContents();
See complete code below
$('[contenteditable]').on('paste', function (e) {
e.preventDefault();
if (window.clipboardData) {
content = window.clipboardData.getData('Text');
if (window.getSelection) {
var selObj = window.getSelection();
var selRange = selObj.getRangeAt(0);
selRange.deleteContents();
selRange.insertNode(document.createTextNode(content));
}
} else if (e.originalEvent.clipboardData) {
content = (e.originalEvent || e).clipboardData.getData('text/plain');
document.execCommand('insertText', false, content);
}
});
The paste event is fired when the user has initiated a "paste" action through the browser's user interface.
HTML
<div class="source" contenteditable="true">Try copying text from this box...</div>
<div class="target" contenteditable="true">...and pasting it into this one</div>
JavaScript
const target = document.querySelector('div.target');
target.addEventListener('paste', (event) => {
let paste = (event.clipboardData || window.clipboardData).getData('text');
paste = paste.toUpperCase();
const selection = window.getSelection();
if (!selection.rangeCount) return false;
selection.deleteFromDocument();
selection.getRangeAt(0).insertNode(document.createTextNode(paste));
event.preventDefault();
});
Know more
In order to support the copy and paste of plain text both on IE11 and Chrome I used the following function.
It has two if statements distinguishing IE from chome and executing the approriate code. In the first part the code reads the text from the clipboard, in the second part it pastes the text right in the cursor position replacing the selected text if present.
In particular, for the paste on IE the code gets the selection range, deletes the selected text, inserts the text from the clipboard in a new html text node, reconfigure the range to insert the text node at the cursor position plus the text length.
The code is the following:
editable.addEventListener("paste", function(e) {
e.preventDefault();
// Get text from the clipboard.
var text = '';
if (e.clipboardData || (e.originalEvent && e.originalEvent.clipboardData)) {
text = (e.originalEvent || e).clipboardData.getData('text/plain');
} else if (window.clipboardData) {
text = window.clipboardData.getData('Text');
}
// bool to indicate if the user agent is internet explorer
let isIE = /Trident/.test(navigator.userAgent);
if (document.queryCommandSupported('insertText') && !isIE) {
// Chrome etc.
document.execCommand('insertText', false, text);
} else {
// IE.
// delete content from selection.
var sel = window.getSelection();
var range = sel.getRangeAt(0);
document.execCommand("delete");
// paste plain text in a new node.
var newNode = document.createTextNode(text);
range.insertNode(newNode);
range.setStart(newNode, 0)
range.setEnd(newNode, newNode.childNodes.length);
sel.removeAllRanges;
sel.addRange(range);
}
}, false);
In particular, in order to paste text on IE many answers I found this instruction document.execCommand('paste', false, text); that doesn't work on IE11 because the browser calls the paste event again and again many times. So I replaced it with the functions on the range object.
Another issue is that on IE11, depending on the version, the function document.execCommand('insertText', false, text); sometimes is available other times not, so I checked explicitly whether the browser is IE and for it executed the part of the code based on the range selection (see else).
How can a web application detect a paste event and retrieve the data to be pasted?
I would like to remove HTML content before the text is pasted into a rich text editor.
Cleaning the text after being pasted afterwards works, but the problem is that all previous formatting is lost. For example, I can write a sentence in the editor and make it bold, but when I paste new text, all formatting is lost. I want to clean just the text that is pasted, and leave any previous formatting untouched.
Ideally, the solution should work across all modern browsers (e.g., MSIE, Gecko, Chrome, and Safari).
Note that MSIE has clipboardData.getData(), but I could not find similar functionality for other browsers.
Solution #1 (Plain Text only and requires Firefox 22+)
Works for IE6+, FF 22+, Chrome, Safari, Edge
(Only tested in IE9+, but should work for lower versions)
If you need support for pasting HTML or Firefox <= 22, see Solution #2.
function handlePaste(e) {
var clipboardData, pastedData;
// Stop data actually being pasted into div
e.stopPropagation();
e.preventDefault();
// Get pasted data via clipboard API
clipboardData = e.clipboardData || window.clipboardData;
pastedData = clipboardData.getData('Text');
// Do whatever with pasteddata
alert(pastedData);
}
document.getElementById('editableDiv').addEventListener('paste', handlePaste);
<div id='editableDiv' contenteditable='true'>Paste</div>
JSFiddle
Note that this solution uses the parameter 'Text' for the getData function, which is non-standard. However, it works in all browsers at the time of writing.
Solution #2 (HTML and works for Firefox <= 22)
Tested in IE6+, FF 3.5+, Chrome, Safari, Edge
var editableDiv = document.getElementById('editableDiv');
function handlepaste(e) {
var types, pastedData, savedContent;
// Browsers that support the 'text/html' type in the Clipboard API (Chrome, Firefox 22+)
if (e && e.clipboardData && e.clipboardData.types && e.clipboardData.getData) {
// Check for 'text/html' in types list. See abligh's answer below for deatils on
// why the DOMStringList bit is needed. We cannot fall back to 'text/plain' as
// Safari/Edge don't advertise HTML data even if it is available
types = e.clipboardData.types;
if (((types instanceof DOMStringList) && types.contains("text/html")) || (types.indexOf && types.indexOf('text/html') !== -1)) {
// Extract data and pass it to callback
pastedData = e.clipboardData.getData('text/html');
processPaste(editableDiv, pastedData);
// Stop the data from actually being pasted
e.stopPropagation();
e.preventDefault();
return false;
}
}
// Everything else: Move existing element contents to a DocumentFragment for safekeeping
savedContent = document.createDocumentFragment();
while (editableDiv.childNodes.length > 0) {
savedContent.appendChild(editableDiv.childNodes[0]);
}
// Then wait for browser to paste content into it and cleanup
waitForPastedData(editableDiv, savedContent);
return true;
}
function waitForPastedData(elem, savedContent) {
// If data has been processes by browser, process it
if (elem.childNodes && elem.childNodes.length > 0) {
// Retrieve pasted content via innerHTML
// (Alternatively loop through elem.childNodes or elem.getElementsByTagName here)
var pastedData = elem.innerHTML;
// Restore saved content
elem.innerHTML = "";
elem.appendChild(savedContent);
// Call callback
processPaste(elem, pastedData);
}
// Else wait 20ms and try again
else {
setTimeout(function() {
waitForPastedData(elem, savedContent)
}, 20);
}
}
function processPaste(elem, pastedData) {
// Do whatever with gathered data;
alert(pastedData);
elem.focus();
}
// Modern browsers. Note: 3rd argument is required for Firefox <= 6
if (editableDiv.addEventListener) {
editableDiv.addEventListener('paste', handlepaste, false);
}
// IE <= 8
else {
editableDiv.attachEvent('onpaste', handlepaste);
}
<div id='div' contenteditable='true'>Paste</div>
JSFiddle
Explanation
The onpaste event of the div has the handlePaste function attached to it and passed a single argument: the event object for the paste event. Of particular interest to us is the clipboardData property of this event which enables clipboard access in non-ie browsers. In IE the equivalent is window.clipboardData, although this has a slightly different API.
See resources section below.
The handlepaste function:
This function has two branches.
The first checks for the existence of event.clipboardData and checks whether it's types property contains 'text/html' (types may be either a DOMStringList which is checked using the contains method, or a string which is checked using the indexOf method). If all of these conditions are fulfilled, then we proceed as in solution #1, except with 'text/html' instead of 'text/plain'. This currently works in Chrome and Firefox 22+.
If this method is not supported (all other browsers), then we
Save the element's contents to a DocumentFragment
Empty the element
Call the waitForPastedData function
The waitforpastedata function:
This function first polls for the pasted data (once per 20ms), which is necessary because it doesn't appear straight away. When the data has appeared it:
Saves the innerHTML of the editable div (which is now the pasted data) to a variable
Restores the content saved in the DocumentFragment
Calls the 'processPaste' function with the retrieved data
The processpaste function:
Does arbitrary things with the pasted data. In this case we just alert the data, you can do whatever you like. You will probably want to run the pasted data through some kind of data sanitizing process.
Saving and restoring the cursor position
In a real situation you would probably want to save the selection before, and restore it afterwards (Set cursor position on contentEditable <div>). You could then insert the pasted data at the position the cursor was in when the user initiated the paste action.
Resources on MDN
paste event
DocumentFragment
DomStringList
Thanks to Tim Down to suggesting the use of a DocumentFragment, and abligh for catching an error in Firefox due to the use of DOMStringList instead of a string for clipboardData.types
The situation has changed since writing this answer: now that Firefox has added support in version 22, all major browsers now support accessing the clipboard data in a paste event. See Nico Burns's answer for an example.
In the past this was not generally possible in a cross-browser way. The ideal would be to be able to get the pasted content via the paste event, which is possible in recent browsers but not in some older browsers (in particular, Firefox < 22).
When you need to support older browsers, what you can do is quite involved and a bit of a hack that will work in Firefox 2+, IE 5.5+ and WebKit browsers such as Safari or Chrome. Recent versions of both TinyMCE and CKEditor use this technique:
Detect a ctrl-v / shift-ins event using a keypress event handler
In that handler, save the current user selection, add a textarea element off-screen (say at left -1000px) to the document, turn designMode off and call focus() on the textarea, thus moving the caret and effectively redirecting the paste
Set a very brief timer (say 1 millisecond) in the event handler to call another function that stores the textarea value, removes the textarea from the document, turns designMode back on, restores the user selection and pastes the text in.
Note that this will only work for keyboard paste events and not pastes from the context or edit menus. By the time the paste event fires, it's too late to redirect the caret into the textarea (in some browsers, at least).
In the unlikely event that you need to support Firefox 2, note that you'll need to place the textarea in the parent document rather than the WYSIWYG editor iframe's document in that browser.
Simple version:
document.querySelector('[contenteditable]').addEventListener('paste', (e) => {
e.preventDefault();
const text = (e.originalEvent || e).clipboardData.getData('text/plain');
window.document.execCommand('insertText', false, text);
});
Using clipboardData
Demo : http://jsbin.com/nozifexasu/edit?js,output
Edge, Firefox, Chrome, Safari, Opera tested.
⚠ Document.execCommand() is obsolete now.
Note: Remember to check input/output at server-side also (like PHP strip-tags)
Live Demo
Tested on Chrome / FF / IE11
There is a Chrome/IE annoyance which is that these browsers add <div> element for each new line. There is a post about this here and it can be fixed by setting the contenteditable element to be display:inline-block
Select some highlighted HTML and paste it here:
function onPaste(e){
var content;
e.preventDefault();
if( e.clipboardData ){
content = e.clipboardData.getData('text/plain');
document.execCommand('insertText', false, content);
return false;
}
else if( window.clipboardData ){
content = window.clipboardData.getData('Text');
if (window.getSelection)
window.getSelection().getRangeAt(0).insertNode( document.createTextNode(content) );
}
}
/////// EVENT BINDING /////////
document.querySelector('[contenteditable]').addEventListener('paste', onPaste);
[contenteditable]{
/* chroem bug: https://stackoverflow.com/a/24689420/104380 */
display:inline-block;
width: calc(100% - 40px);
min-height:120px;
margin:10px;
padding:10px;
border:1px dashed green;
}
/*
mark HTML inside the "contenteditable"
(Shouldn't be any OFC!)'
*/
[contenteditable] *{
background-color:red;
}
<div contenteditable></div>
I've written a little proof of concept for Tim Downs proposal here with off-screen textarea. And here goes the code:
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script language="JavaScript">
$(document).ready(function()
{
var ctrlDown = false;
var ctrlKey = 17, vKey = 86, cKey = 67;
$(document).keydown(function(e)
{
if (e.keyCode == ctrlKey) ctrlDown = true;
}).keyup(function(e)
{
if (e.keyCode == ctrlKey) ctrlDown = false;
});
$(".capture-paste").keydown(function(e)
{
if (ctrlDown && (e.keyCode == vKey || e.keyCode == cKey)){
$("#area").css("display","block");
$("#area").focus();
}
});
$(".capture-paste").keyup(function(e)
{
if (ctrlDown && (e.keyCode == vKey || e.keyCode == cKey)){
$("#area").blur();
//do your sanitation check or whatever stuff here
$("#paste-output").text($("#area").val());
$("#area").val("");
$("#area").css("display","none");
}
});
});
</script>
</head>
<body class="capture-paste">
<div id="paste-output"></div>
<div>
<textarea id="area" style="display: none; position: absolute; left: -99em;"></textarea>
</div>
</body>
</html>
Just copy and paste the whole code into one html file and try to paste (using ctrl-v) text from clipboard anywhere on the document.
I've tested it in IE9 and new versions of Firefox, Chrome and Opera. Works quite well. Also it's good that one can use whatever key combination he prefers to triger this functionality. Of course don't forget to include jQuery sources.
Feel free to use this code and if you come with some improvements or problems please post them back. Also note that I'm no Javascript developer so I may have missed something (=>do your own testign).
Based on l2aelba anwser. This was tested on FF, Safari, Chrome, IE (8,9,10 and 11)
$("#editText").on("paste", function (e) {
e.preventDefault();
var text;
var clp = (e.originalEvent || e).clipboardData;
if (clp === undefined || clp === null) {
text = window.clipboardData.getData("text") || "";
if (text !== "") {
if (window.getSelection) {
var newNode = document.createElement("span");
newNode.innerHTML = text;
window.getSelection().getRangeAt(0).insertNode(newNode);
} else {
document.selection.createRange().pasteHTML(text);
}
}
} else {
text = clp.getData('text/plain') || "";
if (text !== "") {
document.execCommand('insertText', false, text);
}
}
});
This one does not use any setTimeout().
I have used this great article to achieve cross browser support.
$(document).on("focus", "input[type=text],textarea", function (e) {
var t = e.target;
if (!$(t).data("EventListenerSet")) {
//get length of field before paste
var keyup = function () {
$(this).data("lastLength", $(this).val().length);
};
$(t).data("lastLength", $(t).val().length);
//catch paste event
var paste = function () {
$(this).data("paste", 1);//Opera 11.11+
};
//process modified data, if paste occured
var func = function () {
if ($(this).data("paste")) {
alert(this.value.substr($(this).data("lastLength")));
$(this).data("paste", 0);
this.value = this.value.substr(0, $(this).data("lastLength"));
$(t).data("lastLength", $(t).val().length);
}
};
if (window.addEventListener) {
t.addEventListener('keyup', keyup, false);
t.addEventListener('paste', paste, false);
t.addEventListener('input', func, false);
}
else {//IE
t.attachEvent('onkeyup', function () {
keyup.call(t);
});
t.attachEvent('onpaste', function () {
paste.call(t);
});
t.attachEvent('onpropertychange', function () {
func.call(t);
});
}
$(t).data("EventListenerSet", 1);
}
});
This code is extended with selection handle before paste:
demo
For cleaning the pasted text and replacing the currently selected text with the pasted text the matter is pretty trivial:
<div id='div' contenteditable='true' onpaste='handlepaste(this, event)'>Paste</div>
JS:
function handlepaste(el, e) {
document.execCommand('insertText', false, e.clipboardData.getData('text/plain'));
e.preventDefault();
}
This was too long for a comment on Nico's answer, which I don't think works on Firefox any more (per the comments), and didn't work for me on Safari as is.
Firstly, you now appear to be able to read directly from the clipboard. Rather than code like:
if (/text\/plain/.test(e.clipboardData.types)) {
// shouldn't this be writing to elem.value for text/plain anyway?
elem.innerHTML = e.clipboardData.getData('text/plain');
}
use:
types = e.clipboardData.types;
if (((types instanceof DOMStringList) && types.contains("text/plain")) ||
(/text\/plain/.test(types))) {
// shouldn't this be writing to elem.value for text/plain anyway?
elem.innerHTML = e.clipboardData.getData('text/plain');
}
because Firefox has a types field which is a DOMStringList which does not implement test.
Next Firefox will not allow paste unless the focus is in a contenteditable=true field.
Finally, Firefox will not allow paste reliably unless the focus is in a textarea (or perhaps input) which is not only contenteditable=true but also:
not display:none
not visibility:hidden
not zero sized
I was trying to hide the text field so I could make paste work over a JS VNC emulator (i.e. it was going to a remote client and there was no actually textarea etc to paste into). I found trying to hide the text field in the above gave symptoms where it worked sometimes, but typically failed on the second paste (or when the field was cleared to prevent pasting the same data twice) as the field lost focus and would not properly regain it despite focus(). The solution I came up with was to put it at z-order: -1000, make it display:none, make it as 1px by 1px, and set all the colours to transparent. Yuck.
On Safari, you the second part of the above applies, i.e. you need to have a textarea which is not display:none.
This should work on all browsers that support the onpaste event and the mutation observer.
This solution goes a step beyond getting the text only, it actually allows you to edit the pasted content before it get pasted into an element.
It works by using contenteditable, onpaste event (supported by all major browsers) en mutation observers (supported by Chrome, Firefox and IE11+)
step 1
Create a HTML-element with contenteditable
<div contenteditable="true" id="target_paste_element"></div>
step 2
In your Javascript code add the following event
document.getElementById("target_paste_element").addEventListener("paste", pasteEventVerifierEditor.bind(window, pasteCallBack), false);
We need to bind pasteCallBack, since the mutation observer will be called asynchronously.
step 3
Add the following function to your code
function pasteEventVerifierEditor(callback, e)
{
//is fired on a paste event.
//pastes content into another contenteditable div, mutation observer observes this, content get pasted, dom tree is copied and can be referenced through call back.
//create temp div
//save the caret position.
savedCaret = saveSelection(document.getElementById("target_paste_element"));
var tempDiv = document.createElement("div");
tempDiv.id = "id_tempDiv_paste_editor";
//tempDiv.style.display = "none";
document.body.appendChild(tempDiv);
tempDiv.contentEditable = "true";
tempDiv.focus();
//we have to wait for the change to occur.
//attach a mutation observer
if (window['MutationObserver'])
{
//this is new functionality
//observer is present in firefox/chrome and IE11
// select the target node
// create an observer instance
tempDiv.observer = new MutationObserver(pasteMutationObserver.bind(window, callback));
// configuration of the observer:
var config = { attributes: false, childList: true, characterData: true, subtree: true };
// pass in the target node, as well as the observer options
tempDiv.observer.observe(tempDiv, config);
}
}
function pasteMutationObserver(callback)
{
document.getElementById("id_tempDiv_paste_editor").observer.disconnect();
delete document.getElementById("id_tempDiv_paste_editor").observer;
if (callback)
{
//return the copied dom tree to the supplied callback.
//copy to avoid closures.
callback.apply(document.getElementById("id_tempDiv_paste_editor").cloneNode(true));
}
document.body.removeChild(document.getElementById("id_tempDiv_paste_editor"));
}
function pasteCallBack()
{
//paste the content into the element.
restoreSelection(document.getElementById("target_paste_element"), savedCaret);
delete savedCaret;
pasteHtmlAtCaret(this.innerHTML, false, true);
}
saveSelection = function(containerEl) {
if (containerEl == document.activeElement)
{
var range = window.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
};
}
};
restoreSelection = function(containerEl, savedSel) {
containerEl.focus();
var charIndex = 0, range = document.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl], node, foundStart = false, stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
function pasteHtmlAtCaret(html, returnInNode, selectPastedContent) {
//function written by Tim Down
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// only relatively recently standardized and is not supported in
// some browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(), node, lastNode;
while ( (node = el.firstChild) ) {
lastNode = frag.appendChild(node);
}
var firstNode = frag.firstChild;
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
if (returnInNode)
{
range.setStart(lastNode, 0); //this part is edited, set caret inside pasted node.
}
else
{
range.setStartAfter(lastNode);
}
if (selectPastedContent) {
range.setStartBefore(firstNode);
} else {
range.collapse(true);
}
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if ( (sel = document.selection) && sel.type != "Control") {
// IE < 9
var originalRange = sel.createRange();
originalRange.collapse(true);
sel.createRange().pasteHTML(html);
if (selectPastedContent) {
range = sel.createRange();
range.setEndPoint("StartToStart", originalRange);
range.select();
}
}
}
What the code does:
Somebody fires the paste event by using ctrl-v, contextmenu or other means
In the paste event a new element with contenteditable is created (an element with contenteditable has elevated privileges)
The caret position of the target element is saved.
The focus is set to the new element
The content gets pasted into the new element and is rendered in the DOM.
The mutation observer catches this (it registers all changes to the dom tree and content). Then fires the mutation event.
The dom of the pasted content gets cloned into a variable and returned to the callback. The temporary element is destroyed.
The callback receives the cloned DOM. The caret is restored. You can edit this before you append it to your target. element. In this example I'm using Tim Downs functions for saving/restoring the caret and pasting HTML into the element.
Example
document.getElementById("target_paste_element").addEventListener("paste", pasteEventVerifierEditor.bind(window, pasteCallBack), false);
function pasteEventVerifierEditor(callback, e) {
//is fired on a paste event.
//pastes content into another contenteditable div, mutation observer observes this, content get pasted, dom tree is copied and can be referenced through call back.
//create temp div
//save the caret position.
savedCaret = saveSelection(document.getElementById("target_paste_element"));
var tempDiv = document.createElement("div");
tempDiv.id = "id_tempDiv_paste_editor";
//tempDiv.style.display = "none";
document.body.appendChild(tempDiv);
tempDiv.contentEditable = "true";
tempDiv.focus();
//we have to wait for the change to occur.
//attach a mutation observer
if (window['MutationObserver']) {
//this is new functionality
//observer is present in firefox/chrome and IE11
// select the target node
// create an observer instance
tempDiv.observer = new MutationObserver(pasteMutationObserver.bind(window, callback));
// configuration of the observer:
var config = {
attributes: false,
childList: true,
characterData: true,
subtree: true
};
// pass in the target node, as well as the observer options
tempDiv.observer.observe(tempDiv, config);
}
}
function pasteMutationObserver(callback) {
document.getElementById("id_tempDiv_paste_editor").observer.disconnect();
delete document.getElementById("id_tempDiv_paste_editor").observer;
if (callback) {
//return the copied dom tree to the supplied callback.
//copy to avoid closures.
callback.apply(document.getElementById("id_tempDiv_paste_editor").cloneNode(true));
}
document.body.removeChild(document.getElementById("id_tempDiv_paste_editor"));
}
function pasteCallBack() {
//paste the content into the element.
restoreSelection(document.getElementById("target_paste_element"), savedCaret);
delete savedCaret;
//edit the copied content by slicing
pasteHtmlAtCaret(this.innerHTML.slice(3), false, true);
}
saveSelection = function(containerEl) {
if (containerEl == document.activeElement) {
var range = window.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
};
}
};
restoreSelection = function(containerEl, savedSel) {
containerEl.focus();
var charIndex = 0,
range = document.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl],
node, foundStart = false,
stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
function pasteHtmlAtCaret(html, returnInNode, selectPastedContent) {
//function written by Tim Down
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// only relatively recently standardized and is not supported in
// some browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(),
node, lastNode;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
var firstNode = frag.firstChild;
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
if (returnInNode) {
range.setStart(lastNode, 0); //this part is edited, set caret inside pasted node.
} else {
range.setStartAfter(lastNode);
}
if (selectPastedContent) {
range.setStartBefore(firstNode);
} else {
range.collapse(true);
}
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if ((sel = document.selection) && sel.type != "Control") {
// IE < 9
var originalRange = sel.createRange();
originalRange.collapse(true);
sel.createRange().pasteHTML(html);
if (selectPastedContent) {
range = sel.createRange();
range.setEndPoint("StartToStart", originalRange);
range.select();
}
}
}
div {
border: 1px solid black;
height: 50px;
padding: 5px;
}
<div contenteditable="true" id="target_paste_element"></div>
Many thanks to Tim Down
See this post for the answer:
Get the pasted content on document on paste event
First that comes to mind is the pastehandler of google's closure lib
http://closure-library.googlecode.com/svn/trunk/closure/goog/demos/pastehandler.html
Solution that works for me is adding event listener to paste event if you are pasting to a text input.
Since paste event happens before text in input changes, inside my on paste handler I create a deferred function inside which I check for changes in my input box that happened on paste:
onPaste: function() {
var oThis = this;
setTimeout(function() { // Defer until onPaste() is done
console.log('paste', oThis.input.value);
// Manipulate pasted input
}, 1);
}
Simple solution:
document.onpaste = function(e) {
var pasted = e.clipboardData.getData('Text');
console.log(pasted)
}
This worked for me :
function onPasteMe(currentData, maxLen) {
// validate max length of pasted text
var totalCharacterCount = window.clipboardData.getData('Text').length;
}
<input type="text" onPaste="return onPasteMe(this, 50);" />
function myFunct( e ){
e.preventDefault();
var pastedText = undefined;
if( window.clipboardData && window.clipboardData.getData ){
pastedText = window.clipboardData.getData('Text');
}
else if( e.clipboardData && e.clipboardData.getData ){
pastedText = e.clipboardData.getData('text/plain');
}
//work with text
}
document.onpaste = myFunct;
You can do this in this way:
use this jQuery plugin for pre & post paste events:
$.fn.pasteEvents = function( delay ) {
if (delay == undefined) delay = 20;
return $(this).each(function() {
var $el = $(this);
$el.on("paste", function() {
$el.trigger("prepaste");
setTimeout(function() { $el.trigger("postpaste"); }, delay);
});
});
};
Now you can use this plugin;:
$('#txt').on("prepaste", function() {
$(this).find("*").each(function(){
var tmp=new Date.getTime();
$(this).data("uid",tmp);
});
}).pasteEvents();
$('#txt').on("postpaste", function() {
$(this).find("*").each(function(){
if(!$(this).data("uid")){
$(this).removeClass();
$(this).removeAttr("style id");
}
});
}).pasteEvents();
Explaination
First set a uid for all existing elements as data attribute.
Then compare all nodes POST PASTE event. So by comparing you can identify the newly inserted one because they will have a uid, then just remove style/class/id attribute from newly created elements, so that you can keep your older formatting.
$('#dom').on('paste',function (e){
setTimeout(function(){
console.log(e.currentTarget.value);
},0);
});
Just let the browser paste as usual in its content editable div and then after the paste swap any span elements used for custom text styles with the text itself. This seems to work okay in internet explorer and the other browsers I tried...
$('[contenteditable]').on('paste', function (e) {
setTimeout(function () {
$(e.target).children('span').each(function () {
$(this).replaceWith($(this).text());
});
}, 0);
});
This solution assumes that you are running jQuery and that you don't want text formatting in any of your content editable divs.
The plus side is that it's super simple.
This solution is replace the html tag, it's simple and cross-browser; check this jsfiddle: http://jsfiddle.net/tomwan/cbp1u2cx/1/, core code:
var $plainText = $("#plainText");
var $linkOnly = $("#linkOnly");
var $html = $("#html");
$plainText.on('paste', function (e) {
window.setTimeout(function () {
$plainText.html(removeAllTags(replaceStyleAttr($plainText.html())));
}, 0);
});
$linkOnly.on('paste', function (e) {
window.setTimeout(function () {
$linkOnly.html(removeTagsExcludeA(replaceStyleAttr($linkOnly.html())));
}, 0);
});
function replaceStyleAttr (str) {
return str.replace(/(<[\w\W]*?)(style)([\w\W]*?>)/g, function (a, b, c, d) {
return b + 'style_replace' + d;
});
}
function removeTagsExcludeA (str) {
return str.replace(/<\/?((?!a)(\w+))\s*[\w\W]*?>/g, '');
}
function removeAllTags (str) {
return str.replace(/<\/?(\w+)\s*[\w\W]*?>/g, '');
}
notice: you should do some work about xss filter on the back side because this solution cannot filter strings like '<<>>'
This is an existing code posted above but I have updated it for IE's, the bug was when the existing text is selected and pasted will not delete the selected content. This has been fixed by the below code
selRange.deleteContents();
See complete code below
$('[contenteditable]').on('paste', function (e) {
e.preventDefault();
if (window.clipboardData) {
content = window.clipboardData.getData('Text');
if (window.getSelection) {
var selObj = window.getSelection();
var selRange = selObj.getRangeAt(0);
selRange.deleteContents();
selRange.insertNode(document.createTextNode(content));
}
} else if (e.originalEvent.clipboardData) {
content = (e.originalEvent || e).clipboardData.getData('text/plain');
document.execCommand('insertText', false, content);
}
});
The paste event is fired when the user has initiated a "paste" action through the browser's user interface.
HTML
<div class="source" contenteditable="true">Try copying text from this box...</div>
<div class="target" contenteditable="true">...and pasting it into this one</div>
JavaScript
const target = document.querySelector('div.target');
target.addEventListener('paste', (event) => {
let paste = (event.clipboardData || window.clipboardData).getData('text');
paste = paste.toUpperCase();
const selection = window.getSelection();
if (!selection.rangeCount) return false;
selection.deleteFromDocument();
selection.getRangeAt(0).insertNode(document.createTextNode(paste));
event.preventDefault();
});
Know more
In order to support the copy and paste of plain text both on IE11 and Chrome I used the following function.
It has two if statements distinguishing IE from chome and executing the approriate code. In the first part the code reads the text from the clipboard, in the second part it pastes the text right in the cursor position replacing the selected text if present.
In particular, for the paste on IE the code gets the selection range, deletes the selected text, inserts the text from the clipboard in a new html text node, reconfigure the range to insert the text node at the cursor position plus the text length.
The code is the following:
editable.addEventListener("paste", function(e) {
e.preventDefault();
// Get text from the clipboard.
var text = '';
if (e.clipboardData || (e.originalEvent && e.originalEvent.clipboardData)) {
text = (e.originalEvent || e).clipboardData.getData('text/plain');
} else if (window.clipboardData) {
text = window.clipboardData.getData('Text');
}
// bool to indicate if the user agent is internet explorer
let isIE = /Trident/.test(navigator.userAgent);
if (document.queryCommandSupported('insertText') && !isIE) {
// Chrome etc.
document.execCommand('insertText', false, text);
} else {
// IE.
// delete content from selection.
var sel = window.getSelection();
var range = sel.getRangeAt(0);
document.execCommand("delete");
// paste plain text in a new node.
var newNode = document.createTextNode(text);
range.insertNode(newNode);
range.setStart(newNode, 0)
range.setEnd(newNode, newNode.childNodes.length);
sel.removeAllRanges;
sel.addRange(range);
}
}, false);
In particular, in order to paste text on IE many answers I found this instruction document.execCommand('paste', false, text); that doesn't work on IE11 because the browser calls the paste event again and again many times. So I replaced it with the functions on the range object.
Another issue is that on IE11, depending on the version, the function document.execCommand('insertText', false, text); sometimes is available other times not, so I checked explicitly whether the browser is IE and for it executed the part of the code based on the range selection (see else).
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.
Is it the best solution?
Is it bulletproof to avoid any HTML markup in paste?
How to add listener to Right Click -> Paste?
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);
});
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);
}
});
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);
}
});
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
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.
Note that execCommand() is deprecated, so avoid using it in your production environments. Do something like this instead:
const editor = document.querySelector('[contentEditable]')
editor.addEventListener('paste', handlePaste)
function handlePaste(e) {
e.preventDefault()
const text = (e.clipboardData || window.clipboardData).getData('text')
const selection = window.getSelection()
if (selection.rangeCount) {
selection.deleteFromDocument()
selection.getRangeAt(0).insertNode(document.createTextNode(text))
}
}
[contentEditable] {
padding: 1em;
border: 1px solid black;
}
<div contentEditable>Paste HTML here</div>
References:
https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event
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);
}
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
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.
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);
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>
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>
In 2022, you can use CSS user-modify: read-write-plaintext-only to archive this
.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;
}
<div contenteditable class="plain-text-only">Can't paste HTML here</div>
<div contenteditable>HTML styled text free</div>
How can a web application detect a paste event and retrieve the data to be pasted?
I would like to remove HTML content before the text is pasted into a rich text editor.
Cleaning the text after being pasted afterwards works, but the problem is that all previous formatting is lost. For example, I can write a sentence in the editor and make it bold, but when I paste new text, all formatting is lost. I want to clean just the text that is pasted, and leave any previous formatting untouched.
Ideally, the solution should work across all modern browsers (e.g., MSIE, Gecko, Chrome, and Safari).
Note that MSIE has clipboardData.getData(), but I could not find similar functionality for other browsers.
Solution #1 (Plain Text only and requires Firefox 22+)
Works for IE6+, FF 22+, Chrome, Safari, Edge
(Only tested in IE9+, but should work for lower versions)
If you need support for pasting HTML or Firefox <= 22, see Solution #2.
function handlePaste(e) {
var clipboardData, pastedData;
// Stop data actually being pasted into div
e.stopPropagation();
e.preventDefault();
// Get pasted data via clipboard API
clipboardData = e.clipboardData || window.clipboardData;
pastedData = clipboardData.getData('Text');
// Do whatever with pasteddata
alert(pastedData);
}
document.getElementById('editableDiv').addEventListener('paste', handlePaste);
<div id='editableDiv' contenteditable='true'>Paste</div>
JSFiddle
Note that this solution uses the parameter 'Text' for the getData function, which is non-standard. However, it works in all browsers at the time of writing.
Solution #2 (HTML and works for Firefox <= 22)
Tested in IE6+, FF 3.5+, Chrome, Safari, Edge
var editableDiv = document.getElementById('editableDiv');
function handlepaste(e) {
var types, pastedData, savedContent;
// Browsers that support the 'text/html' type in the Clipboard API (Chrome, Firefox 22+)
if (e && e.clipboardData && e.clipboardData.types && e.clipboardData.getData) {
// Check for 'text/html' in types list. See abligh's answer below for deatils on
// why the DOMStringList bit is needed. We cannot fall back to 'text/plain' as
// Safari/Edge don't advertise HTML data even if it is available
types = e.clipboardData.types;
if (((types instanceof DOMStringList) && types.contains("text/html")) || (types.indexOf && types.indexOf('text/html') !== -1)) {
// Extract data and pass it to callback
pastedData = e.clipboardData.getData('text/html');
processPaste(editableDiv, pastedData);
// Stop the data from actually being pasted
e.stopPropagation();
e.preventDefault();
return false;
}
}
// Everything else: Move existing element contents to a DocumentFragment for safekeeping
savedContent = document.createDocumentFragment();
while (editableDiv.childNodes.length > 0) {
savedContent.appendChild(editableDiv.childNodes[0]);
}
// Then wait for browser to paste content into it and cleanup
waitForPastedData(editableDiv, savedContent);
return true;
}
function waitForPastedData(elem, savedContent) {
// If data has been processes by browser, process it
if (elem.childNodes && elem.childNodes.length > 0) {
// Retrieve pasted content via innerHTML
// (Alternatively loop through elem.childNodes or elem.getElementsByTagName here)
var pastedData = elem.innerHTML;
// Restore saved content
elem.innerHTML = "";
elem.appendChild(savedContent);
// Call callback
processPaste(elem, pastedData);
}
// Else wait 20ms and try again
else {
setTimeout(function() {
waitForPastedData(elem, savedContent)
}, 20);
}
}
function processPaste(elem, pastedData) {
// Do whatever with gathered data;
alert(pastedData);
elem.focus();
}
// Modern browsers. Note: 3rd argument is required for Firefox <= 6
if (editableDiv.addEventListener) {
editableDiv.addEventListener('paste', handlepaste, false);
}
// IE <= 8
else {
editableDiv.attachEvent('onpaste', handlepaste);
}
<div id='div' contenteditable='true'>Paste</div>
JSFiddle
Explanation
The onpaste event of the div has the handlePaste function attached to it and passed a single argument: the event object for the paste event. Of particular interest to us is the clipboardData property of this event which enables clipboard access in non-ie browsers. In IE the equivalent is window.clipboardData, although this has a slightly different API.
See resources section below.
The handlepaste function:
This function has two branches.
The first checks for the existence of event.clipboardData and checks whether it's types property contains 'text/html' (types may be either a DOMStringList which is checked using the contains method, or a string which is checked using the indexOf method). If all of these conditions are fulfilled, then we proceed as in solution #1, except with 'text/html' instead of 'text/plain'. This currently works in Chrome and Firefox 22+.
If this method is not supported (all other browsers), then we
Save the element's contents to a DocumentFragment
Empty the element
Call the waitForPastedData function
The waitforpastedata function:
This function first polls for the pasted data (once per 20ms), which is necessary because it doesn't appear straight away. When the data has appeared it:
Saves the innerHTML of the editable div (which is now the pasted data) to a variable
Restores the content saved in the DocumentFragment
Calls the 'processPaste' function with the retrieved data
The processpaste function:
Does arbitrary things with the pasted data. In this case we just alert the data, you can do whatever you like. You will probably want to run the pasted data through some kind of data sanitizing process.
Saving and restoring the cursor position
In a real situation you would probably want to save the selection before, and restore it afterwards (Set cursor position on contentEditable <div>). You could then insert the pasted data at the position the cursor was in when the user initiated the paste action.
Resources on MDN
paste event
DocumentFragment
DomStringList
Thanks to Tim Down to suggesting the use of a DocumentFragment, and abligh for catching an error in Firefox due to the use of DOMStringList instead of a string for clipboardData.types
The situation has changed since writing this answer: now that Firefox has added support in version 22, all major browsers now support accessing the clipboard data in a paste event. See Nico Burns's answer for an example.
In the past this was not generally possible in a cross-browser way. The ideal would be to be able to get the pasted content via the paste event, which is possible in recent browsers but not in some older browsers (in particular, Firefox < 22).
When you need to support older browsers, what you can do is quite involved and a bit of a hack that will work in Firefox 2+, IE 5.5+ and WebKit browsers such as Safari or Chrome. Recent versions of both TinyMCE and CKEditor use this technique:
Detect a ctrl-v / shift-ins event using a keypress event handler
In that handler, save the current user selection, add a textarea element off-screen (say at left -1000px) to the document, turn designMode off and call focus() on the textarea, thus moving the caret and effectively redirecting the paste
Set a very brief timer (say 1 millisecond) in the event handler to call another function that stores the textarea value, removes the textarea from the document, turns designMode back on, restores the user selection and pastes the text in.
Note that this will only work for keyboard paste events and not pastes from the context or edit menus. By the time the paste event fires, it's too late to redirect the caret into the textarea (in some browsers, at least).
In the unlikely event that you need to support Firefox 2, note that you'll need to place the textarea in the parent document rather than the WYSIWYG editor iframe's document in that browser.
Simple version:
document.querySelector('[contenteditable]').addEventListener('paste', (e) => {
e.preventDefault();
const text = (e.originalEvent || e).clipboardData.getData('text/plain');
window.document.execCommand('insertText', false, text);
});
Using clipboardData
Demo : http://jsbin.com/nozifexasu/edit?js,output
Edge, Firefox, Chrome, Safari, Opera tested.
⚠ Document.execCommand() is obsolete now.
Note: Remember to check input/output at server-side also (like PHP strip-tags)
Live Demo
Tested on Chrome / FF / IE11
There is a Chrome/IE annoyance which is that these browsers add <div> element for each new line. There is a post about this here and it can be fixed by setting the contenteditable element to be display:inline-block
Select some highlighted HTML and paste it here:
function onPaste(e){
var content;
e.preventDefault();
if( e.clipboardData ){
content = e.clipboardData.getData('text/plain');
document.execCommand('insertText', false, content);
return false;
}
else if( window.clipboardData ){
content = window.clipboardData.getData('Text');
if (window.getSelection)
window.getSelection().getRangeAt(0).insertNode( document.createTextNode(content) );
}
}
/////// EVENT BINDING /////////
document.querySelector('[contenteditable]').addEventListener('paste', onPaste);
[contenteditable]{
/* chroem bug: https://stackoverflow.com/a/24689420/104380 */
display:inline-block;
width: calc(100% - 40px);
min-height:120px;
margin:10px;
padding:10px;
border:1px dashed green;
}
/*
mark HTML inside the "contenteditable"
(Shouldn't be any OFC!)'
*/
[contenteditable] *{
background-color:red;
}
<div contenteditable></div>
I've written a little proof of concept for Tim Downs proposal here with off-screen textarea. And here goes the code:
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script language="JavaScript">
$(document).ready(function()
{
var ctrlDown = false;
var ctrlKey = 17, vKey = 86, cKey = 67;
$(document).keydown(function(e)
{
if (e.keyCode == ctrlKey) ctrlDown = true;
}).keyup(function(e)
{
if (e.keyCode == ctrlKey) ctrlDown = false;
});
$(".capture-paste").keydown(function(e)
{
if (ctrlDown && (e.keyCode == vKey || e.keyCode == cKey)){
$("#area").css("display","block");
$("#area").focus();
}
});
$(".capture-paste").keyup(function(e)
{
if (ctrlDown && (e.keyCode == vKey || e.keyCode == cKey)){
$("#area").blur();
//do your sanitation check or whatever stuff here
$("#paste-output").text($("#area").val());
$("#area").val("");
$("#area").css("display","none");
}
});
});
</script>
</head>
<body class="capture-paste">
<div id="paste-output"></div>
<div>
<textarea id="area" style="display: none; position: absolute; left: -99em;"></textarea>
</div>
</body>
</html>
Just copy and paste the whole code into one html file and try to paste (using ctrl-v) text from clipboard anywhere on the document.
I've tested it in IE9 and new versions of Firefox, Chrome and Opera. Works quite well. Also it's good that one can use whatever key combination he prefers to triger this functionality. Of course don't forget to include jQuery sources.
Feel free to use this code and if you come with some improvements or problems please post them back. Also note that I'm no Javascript developer so I may have missed something (=>do your own testign).
Based on l2aelba anwser. This was tested on FF, Safari, Chrome, IE (8,9,10 and 11)
$("#editText").on("paste", function (e) {
e.preventDefault();
var text;
var clp = (e.originalEvent || e).clipboardData;
if (clp === undefined || clp === null) {
text = window.clipboardData.getData("text") || "";
if (text !== "") {
if (window.getSelection) {
var newNode = document.createElement("span");
newNode.innerHTML = text;
window.getSelection().getRangeAt(0).insertNode(newNode);
} else {
document.selection.createRange().pasteHTML(text);
}
}
} else {
text = clp.getData('text/plain') || "";
if (text !== "") {
document.execCommand('insertText', false, text);
}
}
});
This one does not use any setTimeout().
I have used this great article to achieve cross browser support.
$(document).on("focus", "input[type=text],textarea", function (e) {
var t = e.target;
if (!$(t).data("EventListenerSet")) {
//get length of field before paste
var keyup = function () {
$(this).data("lastLength", $(this).val().length);
};
$(t).data("lastLength", $(t).val().length);
//catch paste event
var paste = function () {
$(this).data("paste", 1);//Opera 11.11+
};
//process modified data, if paste occured
var func = function () {
if ($(this).data("paste")) {
alert(this.value.substr($(this).data("lastLength")));
$(this).data("paste", 0);
this.value = this.value.substr(0, $(this).data("lastLength"));
$(t).data("lastLength", $(t).val().length);
}
};
if (window.addEventListener) {
t.addEventListener('keyup', keyup, false);
t.addEventListener('paste', paste, false);
t.addEventListener('input', func, false);
}
else {//IE
t.attachEvent('onkeyup', function () {
keyup.call(t);
});
t.attachEvent('onpaste', function () {
paste.call(t);
});
t.attachEvent('onpropertychange', function () {
func.call(t);
});
}
$(t).data("EventListenerSet", 1);
}
});
This code is extended with selection handle before paste:
demo
For cleaning the pasted text and replacing the currently selected text with the pasted text the matter is pretty trivial:
<div id='div' contenteditable='true' onpaste='handlepaste(this, event)'>Paste</div>
JS:
function handlepaste(el, e) {
document.execCommand('insertText', false, e.clipboardData.getData('text/plain'));
e.preventDefault();
}
This was too long for a comment on Nico's answer, which I don't think works on Firefox any more (per the comments), and didn't work for me on Safari as is.
Firstly, you now appear to be able to read directly from the clipboard. Rather than code like:
if (/text\/plain/.test(e.clipboardData.types)) {
// shouldn't this be writing to elem.value for text/plain anyway?
elem.innerHTML = e.clipboardData.getData('text/plain');
}
use:
types = e.clipboardData.types;
if (((types instanceof DOMStringList) && types.contains("text/plain")) ||
(/text\/plain/.test(types))) {
// shouldn't this be writing to elem.value for text/plain anyway?
elem.innerHTML = e.clipboardData.getData('text/plain');
}
because Firefox has a types field which is a DOMStringList which does not implement test.
Next Firefox will not allow paste unless the focus is in a contenteditable=true field.
Finally, Firefox will not allow paste reliably unless the focus is in a textarea (or perhaps input) which is not only contenteditable=true but also:
not display:none
not visibility:hidden
not zero sized
I was trying to hide the text field so I could make paste work over a JS VNC emulator (i.e. it was going to a remote client and there was no actually textarea etc to paste into). I found trying to hide the text field in the above gave symptoms where it worked sometimes, but typically failed on the second paste (or when the field was cleared to prevent pasting the same data twice) as the field lost focus and would not properly regain it despite focus(). The solution I came up with was to put it at z-order: -1000, make it display:none, make it as 1px by 1px, and set all the colours to transparent. Yuck.
On Safari, you the second part of the above applies, i.e. you need to have a textarea which is not display:none.
This should work on all browsers that support the onpaste event and the mutation observer.
This solution goes a step beyond getting the text only, it actually allows you to edit the pasted content before it get pasted into an element.
It works by using contenteditable, onpaste event (supported by all major browsers) en mutation observers (supported by Chrome, Firefox and IE11+)
step 1
Create a HTML-element with contenteditable
<div contenteditable="true" id="target_paste_element"></div>
step 2
In your Javascript code add the following event
document.getElementById("target_paste_element").addEventListener("paste", pasteEventVerifierEditor.bind(window, pasteCallBack), false);
We need to bind pasteCallBack, since the mutation observer will be called asynchronously.
step 3
Add the following function to your code
function pasteEventVerifierEditor(callback, e)
{
//is fired on a paste event.
//pastes content into another contenteditable div, mutation observer observes this, content get pasted, dom tree is copied and can be referenced through call back.
//create temp div
//save the caret position.
savedCaret = saveSelection(document.getElementById("target_paste_element"));
var tempDiv = document.createElement("div");
tempDiv.id = "id_tempDiv_paste_editor";
//tempDiv.style.display = "none";
document.body.appendChild(tempDiv);
tempDiv.contentEditable = "true";
tempDiv.focus();
//we have to wait for the change to occur.
//attach a mutation observer
if (window['MutationObserver'])
{
//this is new functionality
//observer is present in firefox/chrome and IE11
// select the target node
// create an observer instance
tempDiv.observer = new MutationObserver(pasteMutationObserver.bind(window, callback));
// configuration of the observer:
var config = { attributes: false, childList: true, characterData: true, subtree: true };
// pass in the target node, as well as the observer options
tempDiv.observer.observe(tempDiv, config);
}
}
function pasteMutationObserver(callback)
{
document.getElementById("id_tempDiv_paste_editor").observer.disconnect();
delete document.getElementById("id_tempDiv_paste_editor").observer;
if (callback)
{
//return the copied dom tree to the supplied callback.
//copy to avoid closures.
callback.apply(document.getElementById("id_tempDiv_paste_editor").cloneNode(true));
}
document.body.removeChild(document.getElementById("id_tempDiv_paste_editor"));
}
function pasteCallBack()
{
//paste the content into the element.
restoreSelection(document.getElementById("target_paste_element"), savedCaret);
delete savedCaret;
pasteHtmlAtCaret(this.innerHTML, false, true);
}
saveSelection = function(containerEl) {
if (containerEl == document.activeElement)
{
var range = window.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
};
}
};
restoreSelection = function(containerEl, savedSel) {
containerEl.focus();
var charIndex = 0, range = document.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl], node, foundStart = false, stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
function pasteHtmlAtCaret(html, returnInNode, selectPastedContent) {
//function written by Tim Down
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// only relatively recently standardized and is not supported in
// some browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(), node, lastNode;
while ( (node = el.firstChild) ) {
lastNode = frag.appendChild(node);
}
var firstNode = frag.firstChild;
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
if (returnInNode)
{
range.setStart(lastNode, 0); //this part is edited, set caret inside pasted node.
}
else
{
range.setStartAfter(lastNode);
}
if (selectPastedContent) {
range.setStartBefore(firstNode);
} else {
range.collapse(true);
}
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if ( (sel = document.selection) && sel.type != "Control") {
// IE < 9
var originalRange = sel.createRange();
originalRange.collapse(true);
sel.createRange().pasteHTML(html);
if (selectPastedContent) {
range = sel.createRange();
range.setEndPoint("StartToStart", originalRange);
range.select();
}
}
}
What the code does:
Somebody fires the paste event by using ctrl-v, contextmenu or other means
In the paste event a new element with contenteditable is created (an element with contenteditable has elevated privileges)
The caret position of the target element is saved.
The focus is set to the new element
The content gets pasted into the new element and is rendered in the DOM.
The mutation observer catches this (it registers all changes to the dom tree and content). Then fires the mutation event.
The dom of the pasted content gets cloned into a variable and returned to the callback. The temporary element is destroyed.
The callback receives the cloned DOM. The caret is restored. You can edit this before you append it to your target. element. In this example I'm using Tim Downs functions for saving/restoring the caret and pasting HTML into the element.
Example
document.getElementById("target_paste_element").addEventListener("paste", pasteEventVerifierEditor.bind(window, pasteCallBack), false);
function pasteEventVerifierEditor(callback, e) {
//is fired on a paste event.
//pastes content into another contenteditable div, mutation observer observes this, content get pasted, dom tree is copied and can be referenced through call back.
//create temp div
//save the caret position.
savedCaret = saveSelection(document.getElementById("target_paste_element"));
var tempDiv = document.createElement("div");
tempDiv.id = "id_tempDiv_paste_editor";
//tempDiv.style.display = "none";
document.body.appendChild(tempDiv);
tempDiv.contentEditable = "true";
tempDiv.focus();
//we have to wait for the change to occur.
//attach a mutation observer
if (window['MutationObserver']) {
//this is new functionality
//observer is present in firefox/chrome and IE11
// select the target node
// create an observer instance
tempDiv.observer = new MutationObserver(pasteMutationObserver.bind(window, callback));
// configuration of the observer:
var config = {
attributes: false,
childList: true,
characterData: true,
subtree: true
};
// pass in the target node, as well as the observer options
tempDiv.observer.observe(tempDiv, config);
}
}
function pasteMutationObserver(callback) {
document.getElementById("id_tempDiv_paste_editor").observer.disconnect();
delete document.getElementById("id_tempDiv_paste_editor").observer;
if (callback) {
//return the copied dom tree to the supplied callback.
//copy to avoid closures.
callback.apply(document.getElementById("id_tempDiv_paste_editor").cloneNode(true));
}
document.body.removeChild(document.getElementById("id_tempDiv_paste_editor"));
}
function pasteCallBack() {
//paste the content into the element.
restoreSelection(document.getElementById("target_paste_element"), savedCaret);
delete savedCaret;
//edit the copied content by slicing
pasteHtmlAtCaret(this.innerHTML.slice(3), false, true);
}
saveSelection = function(containerEl) {
if (containerEl == document.activeElement) {
var range = window.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
};
}
};
restoreSelection = function(containerEl, savedSel) {
containerEl.focus();
var charIndex = 0,
range = document.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl],
node, foundStart = false,
stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
function pasteHtmlAtCaret(html, returnInNode, selectPastedContent) {
//function written by Tim Down
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// only relatively recently standardized and is not supported in
// some browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(),
node, lastNode;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
var firstNode = frag.firstChild;
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
if (returnInNode) {
range.setStart(lastNode, 0); //this part is edited, set caret inside pasted node.
} else {
range.setStartAfter(lastNode);
}
if (selectPastedContent) {
range.setStartBefore(firstNode);
} else {
range.collapse(true);
}
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if ((sel = document.selection) && sel.type != "Control") {
// IE < 9
var originalRange = sel.createRange();
originalRange.collapse(true);
sel.createRange().pasteHTML(html);
if (selectPastedContent) {
range = sel.createRange();
range.setEndPoint("StartToStart", originalRange);
range.select();
}
}
}
div {
border: 1px solid black;
height: 50px;
padding: 5px;
}
<div contenteditable="true" id="target_paste_element"></div>
Many thanks to Tim Down
See this post for the answer:
Get the pasted content on document on paste event
First that comes to mind is the pastehandler of google's closure lib
http://closure-library.googlecode.com/svn/trunk/closure/goog/demos/pastehandler.html
Solution that works for me is adding event listener to paste event if you are pasting to a text input.
Since paste event happens before text in input changes, inside my on paste handler I create a deferred function inside which I check for changes in my input box that happened on paste:
onPaste: function() {
var oThis = this;
setTimeout(function() { // Defer until onPaste() is done
console.log('paste', oThis.input.value);
// Manipulate pasted input
}, 1);
}
Simple solution:
document.onpaste = function(e) {
var pasted = e.clipboardData.getData('Text');
console.log(pasted)
}
This worked for me :
function onPasteMe(currentData, maxLen) {
// validate max length of pasted text
var totalCharacterCount = window.clipboardData.getData('Text').length;
}
<input type="text" onPaste="return onPasteMe(this, 50);" />
function myFunct( e ){
e.preventDefault();
var pastedText = undefined;
if( window.clipboardData && window.clipboardData.getData ){
pastedText = window.clipboardData.getData('Text');
}
else if( e.clipboardData && e.clipboardData.getData ){
pastedText = e.clipboardData.getData('text/plain');
}
//work with text
}
document.onpaste = myFunct;
You can do this in this way:
use this jQuery plugin for pre & post paste events:
$.fn.pasteEvents = function( delay ) {
if (delay == undefined) delay = 20;
return $(this).each(function() {
var $el = $(this);
$el.on("paste", function() {
$el.trigger("prepaste");
setTimeout(function() { $el.trigger("postpaste"); }, delay);
});
});
};
Now you can use this plugin;:
$('#txt').on("prepaste", function() {
$(this).find("*").each(function(){
var tmp=new Date.getTime();
$(this).data("uid",tmp);
});
}).pasteEvents();
$('#txt').on("postpaste", function() {
$(this).find("*").each(function(){
if(!$(this).data("uid")){
$(this).removeClass();
$(this).removeAttr("style id");
}
});
}).pasteEvents();
Explaination
First set a uid for all existing elements as data attribute.
Then compare all nodes POST PASTE event. So by comparing you can identify the newly inserted one because they will have a uid, then just remove style/class/id attribute from newly created elements, so that you can keep your older formatting.
$('#dom').on('paste',function (e){
setTimeout(function(){
console.log(e.currentTarget.value);
},0);
});
Just let the browser paste as usual in its content editable div and then after the paste swap any span elements used for custom text styles with the text itself. This seems to work okay in internet explorer and the other browsers I tried...
$('[contenteditable]').on('paste', function (e) {
setTimeout(function () {
$(e.target).children('span').each(function () {
$(this).replaceWith($(this).text());
});
}, 0);
});
This solution assumes that you are running jQuery and that you don't want text formatting in any of your content editable divs.
The plus side is that it's super simple.
This solution is replace the html tag, it's simple and cross-browser; check this jsfiddle: http://jsfiddle.net/tomwan/cbp1u2cx/1/, core code:
var $plainText = $("#plainText");
var $linkOnly = $("#linkOnly");
var $html = $("#html");
$plainText.on('paste', function (e) {
window.setTimeout(function () {
$plainText.html(removeAllTags(replaceStyleAttr($plainText.html())));
}, 0);
});
$linkOnly.on('paste', function (e) {
window.setTimeout(function () {
$linkOnly.html(removeTagsExcludeA(replaceStyleAttr($linkOnly.html())));
}, 0);
});
function replaceStyleAttr (str) {
return str.replace(/(<[\w\W]*?)(style)([\w\W]*?>)/g, function (a, b, c, d) {
return b + 'style_replace' + d;
});
}
function removeTagsExcludeA (str) {
return str.replace(/<\/?((?!a)(\w+))\s*[\w\W]*?>/g, '');
}
function removeAllTags (str) {
return str.replace(/<\/?(\w+)\s*[\w\W]*?>/g, '');
}
notice: you should do some work about xss filter on the back side because this solution cannot filter strings like '<<>>'
This is an existing code posted above but I have updated it for IE's, the bug was when the existing text is selected and pasted will not delete the selected content. This has been fixed by the below code
selRange.deleteContents();
See complete code below
$('[contenteditable]').on('paste', function (e) {
e.preventDefault();
if (window.clipboardData) {
content = window.clipboardData.getData('Text');
if (window.getSelection) {
var selObj = window.getSelection();
var selRange = selObj.getRangeAt(0);
selRange.deleteContents();
selRange.insertNode(document.createTextNode(content));
}
} else if (e.originalEvent.clipboardData) {
content = (e.originalEvent || e).clipboardData.getData('text/plain');
document.execCommand('insertText', false, content);
}
});
The paste event is fired when the user has initiated a "paste" action through the browser's user interface.
HTML
<div class="source" contenteditable="true">Try copying text from this box...</div>
<div class="target" contenteditable="true">...and pasting it into this one</div>
JavaScript
const target = document.querySelector('div.target');
target.addEventListener('paste', (event) => {
let paste = (event.clipboardData || window.clipboardData).getData('text');
paste = paste.toUpperCase();
const selection = window.getSelection();
if (!selection.rangeCount) return false;
selection.deleteFromDocument();
selection.getRangeAt(0).insertNode(document.createTextNode(paste));
event.preventDefault();
});
Know more
In order to support the copy and paste of plain text both on IE11 and Chrome I used the following function.
It has two if statements distinguishing IE from chome and executing the approriate code. In the first part the code reads the text from the clipboard, in the second part it pastes the text right in the cursor position replacing the selected text if present.
In particular, for the paste on IE the code gets the selection range, deletes the selected text, inserts the text from the clipboard in a new html text node, reconfigure the range to insert the text node at the cursor position plus the text length.
The code is the following:
editable.addEventListener("paste", function(e) {
e.preventDefault();
// Get text from the clipboard.
var text = '';
if (e.clipboardData || (e.originalEvent && e.originalEvent.clipboardData)) {
text = (e.originalEvent || e).clipboardData.getData('text/plain');
} else if (window.clipboardData) {
text = window.clipboardData.getData('Text');
}
// bool to indicate if the user agent is internet explorer
let isIE = /Trident/.test(navigator.userAgent);
if (document.queryCommandSupported('insertText') && !isIE) {
// Chrome etc.
document.execCommand('insertText', false, text);
} else {
// IE.
// delete content from selection.
var sel = window.getSelection();
var range = sel.getRangeAt(0);
document.execCommand("delete");
// paste plain text in a new node.
var newNode = document.createTextNode(text);
range.insertNode(newNode);
range.setStart(newNode, 0)
range.setEnd(newNode, newNode.childNodes.length);
sel.removeAllRanges;
sel.addRange(range);
}
}, false);
In particular, in order to paste text on IE many answers I found this instruction document.execCommand('paste', false, text); that doesn't work on IE11 because the browser calls the paste event again and again many times. So I replaced it with the functions on the range object.
Another issue is that on IE11, depending on the version, the function document.execCommand('insertText', false, text); sometimes is available other times not, so I checked explicitly whether the browser is IE and for it executed the part of the code based on the range selection (see else).