38

I'm trying to make a contenteditable that only accepts plain text.

What I want is to listen to paste event and then remove all html and paste it on the contenteditable only as plain text.

In order to do that I've already tried two different approachs, based answers to similar questions, but those two approachs have problems.

First: This one isn't using "paste" listener, is instead listening for input (not ideal for this case), this was a solution to avoid using Clipboard API.

Problem: This works fine on chrome but not on firefox and IE, when you copy and paste the html it sucessfully removes html and only paste text, but when continuing to write text, the caret is always taken to the beginning of the contenteditable.

function convertToPlaintext() {
  var textContent = this.textContent;
  this.textContent = textContent;
}

var contentEditableNodes = document.querySelectorAll('.plaintext[contenteditable]');

[].forEach.call(contentEditableNodes, function(div) {
  div.addEventListener("input", convertToPlaintext, false);
});

You can try it here/code on which I based: http://jsbin.com/moruseqeha/edit?html,css,js,output

Second: Since the first one failed and isn't listening to "paste" event only, I've then decided to give it a try using Clipboard API. The problem here is that on IE document.execCommand("insertHTML", false, text); doesn't seem to work. This works on chrome, firefox (tested on v41 and v42) and edge.

document.querySelector(".plaintext[contenteditable]").addEventListener("paste", function(e) {
  e.preventDefault();
  var text = "";
  if (e.clipboardData && e.clipboardData.getData) {
    text = e.clipboardData.getData("text/plain");
  } else if (window.clipboardData && window.clipboardData.getData) {
    text = window.clipboardData.getData("Text");
  }
  document.execCommand("insertHTML", false, text);
});

You can try it here: http://jsfiddle.net/marinagon/461uye5p/

Can anyone help me find a solution for some of this problems or anyone has a better solution than the ones presented here?

I'm aware that I could use textarea, but I have reasons not to use it.

user229044
  • 232,980
  • 40
  • 330
  • 338
Marina
  • 433
  • 1
  • 4
  • 10
  • 1
    I used your 2nd approach. (I'd recommend you self-answer, and mark it correct). I needed one change, which was to also check for `else if(ev.originalEvent.clipboardData...` when `ev.clipboardData` is not set. – Darren Cook Apr 30 '17 at 20:51
  • 3
    Nice solution! Keep in mind, that via drag and drop you can still add HTML content to your area. – Markus Jun 02 '17 at 14:55
  • For my purposes, just disabling drag and drop was fine. The API for that is obtuse, but you can do something like this: `function blockDragDrop(ev) { ev.dataTransfer.effectAllowed = 'none'; ev.dataTransfer.dropEffect = 'none'; ev.preventDefault(); return false; } el.addEventListener('dragstart', blockDragDrop); el.addEventListener('dragover', blockDragDrop);` (Don't forget to provide for removing the events if you need to) – Kris Reeves Jul 19 '17 at 06:38
  • 1
    From @toremato's soon-to-be-deleted "answer": " Wanted to say that solution that you've found has a leak. I tried pasting `"< textarea >< / textarea >"` into the fiddle you gave, and it shows textarea. I don't know if there are other HTML elements you could paste as HTML, didn't check further." – Paul Roub Aug 04 '17 at 16:08
  • Additionally, answers (even to your own questions) should be posted **as answers**, not as edits to the question. – Paul Roub Aug 04 '17 at 16:09
  • Note: I can still hit `CTRL` + `B` and set the text to bold. Your solution requires *a lot* of use cases to be properly handled, and is hard to get right. Too bad there is no support in browsers to get plain text only. – BenMorel Aug 31 '18 at 23:37
  • @PaulRoub this is not at all a duplicate, could you please remove this status ? However I agree that putting answers in edit is a very bad pratice. Thanks :) – TOPKAT Oct 29 '19 at 20:51

2 Answers2

11

Dirty hack to clean the pasted data.

function onpaste(e, el){
      setTimeout(function(){
            el.innerText = el.innerText;
        }, 10);
}
2

Use a textarea element instead of a contentEditable element: (emphasis mine)

The textarea element represents a multiline plain text edit control for the element's raw value.

Example:

/* Auto resize height */
var textarea = document.querySelector('textarea');
(textarea.oninput = function() {
  textarea.style.height = 0;
  textarea.style.height = textarea.scrollHeight + 'px';
})();
textarea { width: 100%; }
<textarea>Hello! You can edit my content. But only plain-text!!!</textarea>
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • 19
    Thanks, but I'm trying to avoid using textarea, since I want a text area that autogrow/autosize, as the user inputs text, without the need of external dependencies and even those dependencies have proven to not work that well. – Marina Jan 13 '16 at 10:44
  • 8
    @Marina Autosizing a textarea is probably much more easy than hijacking paste. – Oriol Jan 13 '16 at 10:46
  • 7
    thanks again for your answer, but I opted for the solution that can be found on question update. The reason is to avoid the page to repaint every time user inputs something: http://jsfiddle.net/marinagon/z1uxL23p/ – Marina Jan 15 '16 at 15:36
  • 1
    Wanted to say that solution that you've found has a leak. I tried pasting "< textarea >< / textarea >" into the fiddle you gave, and it shows textarea. I don't know if there are other HTML elements you could paste as HTML, didn't check further. – toremato Aug 04 '17 at 15:12
  • @toremato The textarea is created by the fiddle code. Writing `< textarea >< / textarea >` inside the textarea should not create another one. If it happens it's a serious bug of your browser. – Oriol Apr 02 '18 at 18:52
  • 1
    @Orial It really shows a textarea as toremato wrote, but only in Firefox (tested on newest Firefox version 88.0.1 on macOS). – Toxiro May 08 '21 at 21:56