Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cross-browser way to insert BR or P tag when hitting Enter on a contentEditable element

Tags:

When you hit Enter on a contentEditable element every browser is handling the resulting code differently: Firefox inserts a BR tag, Chrome inserts a DIV tag while Internet Explorer inserts a P tag.

I was desperately looking for a solution to at least use a BR or P for all browsers and the most common answer was this:

inserting BR tag :

$("#editableElement").on("keypress", function(e){
      if (e.which == 13) {
        if (window.getSelection) {
          var selection = window.getSelection(),
              range = selection.getRangeAt(0),
              br = document.createElement("br");
          range.deleteContents();
          range.insertNode(br);
          range.setStartAfter(br);
          range.setEndAfter(br);
          selection.removeAllRanges();
          selection.addRange(range);
          return false;
        }
      }
    });

But this doesn't work because it seems that browsers don't know how to set the caret after <br> which means the following is not doing anything useful (especially if you hit enter when the caret is placed at the end of text):

range.setStartAfter(br);
range.setEndAfter(br);

Some people would say: use double <br><br> but this results in two line breaks when you hit enter inside a text node.

Others would say always add an additional <br> at the end of contentEditable, but if you have a <div contenteditable><p>text here</p></div> and you place the cursor at the end of text then hit enter, you will get the wrong behavior.

So I said to myself maybe we can use P instead of BR, and the common answer is:

inserting P tag:

document.execCommand('formatBlock', false, 'p');

But this doesn't work consistently either.

As you can see, all these solutions leave something to be desired. Is there another solution that solves this issue?

like image 957
medBouzid Avatar asked Feb 29 '16 17:02

medBouzid


1 Answers

One possible solution: append a text node with a zero-width space character after the <br> element. This is a non-printing zero-width character that's specifically designed to:

...indicate word boundaries to text processing systems when using scripts that do not use explicit spacing, or after characters (such as the slash) that are not followed by a visible space but after which there may nevertheless be a line break.

(Wikipedia)

Tested in Chrome 48, Firefox 43, and IE11.

$("#editableElement").on("keypress", function(e) {
  //if the last character is a zero-width space, remove it
  var contentEditableHTML = $("#editableElement").html();
  var lastCharCode = contentEditableHTML.charCodeAt(contentEditableHTML.length - 1);
  if (lastCharCode == 8203) {
    $("#editableElement").html(contentEditableHTML.slice(0, -1));
  }
  // handle "Enter" keypress
  if (e.which == 13) {
    if (window.getSelection) {
      var selection = window.getSelection();
      var range = selection.getRangeAt(0);
      var br = document.createElement("br");
      var zwsp = document.createTextNode("\u200B");
      var textNodeParent = document.getSelection().anchorNode.parentNode;
      var inSpan = textNodeParent.nodeName == "SPAN";
      var span = document.createElement("span");
      
      // if the carat is inside a <span>, move it out of the <span> tag
      if (inSpan) {
        range.setStartAfter(textNodeParent);
        range.setEndAfter(textNodeParent);
      }

      // insert the <br>
      range.deleteContents();
      range.insertNode(br);
      range.setStartAfter(br);
      range.setEndAfter(br);
      
      // create a new span on the next line
      if (inSpan) {
        range.insertNode(span);
        range.setStart(span, 0);
        range.setEnd(span, 0);
      }

      // add a zero-width character
      range.insertNode(zwsp);
      range.setStartBefore(zwsp);
      range.setEndBefore(zwsp);
      
      // insert the new range
      selection.removeAllRanges();
      selection.addRange(range);
      return false;
    }
  }
});
#editableElement {
  height: 150px;
  width: 500px;
  border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div contenteditable=true id="editableElement">
  <span>sample text</span>
</div>
like image 59
rphv Avatar answered Sep 27 '22 23:09

rphv