Prevent Contenteditable Mode from Creating <Span> Tags

prevent contenteditable mode from creating span tags

Here is how I solved this

jquery remove <span> tags while preserving their contents (and replace <divs> and <p>:s with <br>)

I preserve the text of all div, span and p tags, remove those tags, and substitute all div and p with a br. I do it on blur, optimal would be to do it on every keydown, but that is eating too much cpu since I have to loop through all these %#¤¤%& (you said it) nested elements.

How do I prevent the contenteditable tag from being inherited in child elements?

Add contenteditable="false" to your span tag thus this prevents user from editing your span tag.

#editable {  background-color: #ccc;  padding: 5px;  width: 400px;  text-align: center;}
span { background-color: green; color: white; padding: 2px; }
<div id="editable" contenteditable>  Sample text including a <span contenteditable="false">FANCY TAG</span> in the middle.</div>

contenteditable div - divide a span tag - avoid nesting

Finally I made it with this function. Basically I split in half the current tag text at the caret position, create two separate tags each with half of the text, create a new tag with the current color in the middle and put the caret in it.

function typeInKanji() {

var sel = window.getSelection();
var range = sel.getRangeAt(0);

var currentNode = sel.anchorNode;
var elementNode = getSelectionElement();

var caretPosition = getCaretCharacterOffsetWithin(elementNode);

if (elementNode.tagName == "SPAN") {

if (elementNode.style.color == currentColor) {

console.log("span of same color, do nothing and keep typing");

} else {

console.log("span of a different color, split it");

var tagText = elementNode.innerText;

console.log("text: " + tagText + " position: " + caretPosition);

var firstHalf = tagText.substr(0, caretPosition);
var secondHalf = tagText.replace(firstHalf, "");

// remove the old element with all the text
$(elementNode).remove();

var firstTag = "<span style='color: " + elementNode.style.color + "'>" + firstHalf + "</span>"
var secondTag = "<span style='color: " + elementNode.style.color + "'>" + secondHalf + "</span>"
var middleTag = "<span id='middleTag' style='color: " + currentColor + "'>00</span>"

var firstFrag = range.createContextualFragment(firstTag);
var secondFrag = range.createContextualFragment(secondTag);
var middleFrag = range.createContextualFragment(middleTag);

range.insertNode(secondFrag);
range.insertNode(middleFrag);
range.insertNode(firstFrag);

var getMiddleTag = document.getElementById("middleTag");

console.log(getMiddleTag);

range.setStart(getMiddleTag, 0);
range.setEnd(getMiddleTag, 0);

sel.removeAllRanges();
sel.addRange(range);

$("#middleTag").removeAttr("id");
getMiddleTag.innerHTML = "";

}

} else {

// if there is not SPAN tag create one
insertTag("<span id='newSpan' style='color: " + currentColor + "'>00</span>");

var dummySpan = document.getElementById("newSpan");

range.setStart(dummySpan, 1);
range.setEnd(dummySpan, 1);

sel.removeAllRanges();
sel.addRange(range);

$("#newSpan").removeAttr("id");
dummySpan.innerHTML = "";

}

typeKanjiNow = false;

}

And here the other two functions I use:

function getCaretCharacterOffsetWithin(element) {

var caretOffset = 0;
var doc = element.ownerDocument || element.document;
var win = doc.defaultView || doc.parentWindow;
var sel;

if (typeof win.getSelection != "undefined") {

sel = win.getSelection();

if (sel.rangeCount > 0) {

var range = win.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
caretOffset = preCaretRange.toString().length;

}

} else if ( (sel = doc.selection) && sel.type != "Control") {

var textRange = sel.createRange();
var preCaretTextRange = doc.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToEnd", textRange);
caretOffset = preCaretTextRange.text.length;

}

return caretOffset;

}

function insertTag(tag) {

var sel = window.getSelection();
var range = sel.getRangeAt(0);

var currentNode = sel.anchorNode;

var documentFragment = range.createContextualFragment(tag);
range.insertNode(documentFragment);

}

How to prevent non-breaking spaces from being created in a contenteditable element?

You need to assign the innerHTML to the changed text like so:

document.getElementById("div").oninput = function() {
document.getElementById("div").innerHTML = document.getElementById("div").innerHTML.replace(" ","");
}

Because as shown in this MDN page:

The original string is left unchanged

So you need to assign the result of replace to something.

Span with contenteditable attribute won't behave as an inline element in IE11

OK.
I have a fix for my case:

https://jsfiddle.net/on695rz2/2/

[contenteditable=true]:before {  content: attr(before-content);  margin-right: 2px;}
<div style="width:200px">  <span before-content="4.5" contenteditable="true">Test this simple layout in IE11 and see the wonders of the internet!</span></div>

Selection and deletion problems with Non-Editable Span inside ContentEditable DIV

The challenge to this is to get IE11 to backspace from the right directly against the <span>.  Then the next backspace will select and highlight it.  This seems like such a simple objective, but IE11 just won't cooperate.  There should be a quick easy patch, right?  And so the bugs begin.

The approach I came up with is to walk the tree backwards to the first previous non-empty node, clearing the empty nodes between to appease IE, and then evaluate a few conditions.  If the caret should end up at the right side of the <span>, then do it manually (because IE won't) by creating a new range obj with the selection there at the end of the <span>.

online demo

I added an additional kludge for IE in the case that two spans are dragged against eachother.  For example, Field2Field3.  When you then backspace from the right onto Field3, then backspace once again to delete it, IE would jump the caret leftward over Field2.  Skip right over Field2.  grrr.  The workaround is to intercept that and insert a space between the pair of spans.  I wasn't confident you'd be happy with that.  But, you know, it's a workaround.  Anyway, that turned-up yet another bug, where IE changes the inserted space into two empty textnodes.  more grrr.  And so a workaround for the workaround.  See the non-isCollapsed code. 

CODE SNIPPET

var EditableDiv = document.getElementById('EditableDiv');
EditableDiv.onkeydown = function(event) { var ignoreKey; var key = event.keyCode || event.charCode; if (!window.getSelection) return; var selection = window.getSelection(); var focusNode = selection.focusNode, anchorNode = selection.anchorNode;
var anchorOffset = selection.anchorOffset;
if (!anchorNode) return
if (anchorNode.nodeName.toLowerCase() != '#text') { if (anchorOffset < anchorNode.childNodes.length) anchorNode = anchorNode.childNodes[anchorOffset] else { while (!anchorNode.nextSibling) anchorNode = anchorNode.parentNode // this might step out of EditableDiv to "justincase" comment node anchorNode = anchorNode.nextSibling } anchorOffset = 0 }
function backseek() {
while ((anchorOffset == 0) && (anchorNode != EditableDiv)) {
if (anchorNode.previousSibling) { if (anchorNode.previousSibling.nodeName.toLowerCase() == '#text') { if (anchorNode.previousSibling.nodeValue.length == 0) anchorNode.parentNode.removeChild(anchorNode.previousSibling) else { anchorNode = anchorNode.previousSibling anchorOffset = anchorNode.nodeValue.length } } else if ((anchorNode.previousSibling.offsetWidth == 0) && (anchorNode.previousSibling.offsetHeight == 0)) anchorNode.parentNode.removeChild(anchorNode.previousSibling)
else { anchorNode = anchorNode.previousSibling
while ((anchorNode.lastChild) && (anchorNode.nodeName.toUpperCase() != 'SPAN')) {
if ((anchorNode.lastChild.offsetWidth == 0) && (anchorNode.lastChild.offsetHeight == 0)) anchorNode.removeChild(anchorNode.lastChild)
else if (anchorNode.lastChild.nodeName.toLowerCase() != '#text') anchorNode = anchorNode.lastChild
else if (anchorNode.lastChild.nodeValue.length == 0) anchorNode.removeChild(anchorNode.lastChild)
else { anchorNode = anchorNode.lastChild anchorOffset = anchorNode.nodeValue.length //break //don't need to break, textnode has no children } } break } } else while (((anchorNode = anchorNode.parentNode) != EditableDiv) && !anchorNode.previousSibling) {} } }
if (key == 8) { //backspace if (!selection.isCollapsed) {
try { document.createElement("select").size = -1 } catch (e) { //kludge for IE when 2+ SPANs are back-to-back adjacent
if (anchorNode.nodeName.toUpperCase() == 'SPAN') { backseek() if (anchorNode.nodeName.toUpperCase() == 'SPAN') { var k = document.createTextNode(" ") // doesn't work here between two spans. IE makes TWO EMPTY textnodes instead ! anchorNode.parentNode.insertBefore(k, anchorNode) // this works anchorNode.parentNode.insertBefore(anchorNode, k) // simulate "insertAfter" } } }

} else { backseek()
if (anchorNode == EditableDiv) ignoreKey = true
else if (anchorNode.nodeName.toUpperCase() == 'SPAN') { SelectText(event, anchorNode) ignoreKey = true } else if ((anchorNode.nodeName.toLowerCase() == '#text') && (anchorOffset <= 1)) {
var prev, anchorNodeSave = anchorNode, anchorOffsetSave = anchorOffset anchorOffset = 0 backseek() if (anchorNode.nodeName.toUpperCase() == 'SPAN') prev = anchorNode anchorNode = anchorNodeSave anchorOffset = anchorOffsetSave
if (prev) { if (anchorOffset == 0) SelectEvent(prev)
else { var r = document.createRange() selection.removeAllRanges()
if (anchorNode.nodeValue.length > 1) { r.setStart(anchorNode, 0) selection.addRange(r) anchorNode.deleteData(0, 1) } else { for (var i = 0, p = prev.parentNode; true; i++) if (p.childNodes[i] == prev) break r.setStart(p, ++i) selection.addRange(r) anchorNode.parentNode.removeChild(anchorNode) } } ignoreKey = true } } } } if (ignoreKey) { var evt = event || window.event; if (evt.stopPropagation) evt.stopPropagation(); evt.preventDefault(); return false; } }
function SelectText(event, element) { var range, selection; EditableDiv.focus(); if (window.getSelection) { selection = window.getSelection(); range = document.createRange(); range.selectNode(element) selection.removeAllRanges(); selection.addRange(range); } else { range = document.body.createTextRange(); range.moveToElementText(element); range.select(); } var evt = (event) ? event : window.event; if (evt.stopPropagation) evt.stopPropagation(); if (evt.cancelBubble != null) evt.cancelBubble = true; return false; }
#EditableDiv {          height: 75px;          width: 500px;          font-family: Consolas;          font-size: 10pt;          font-weight: normal;          letter-spacing: 1px;          background-color: white;          overflow-y: scroll;          overflow-x: hidden;          border: 1px solid black;          padding: 5px;        }        #EditableDiv span {          color: brown;          font-family: Verdana;          font-size: 8.5pt;          min-width: 10px;          /*_width: 10px;*/          /* what is this? */        }        #EditableDiv p,        #EditableDiv br {          display: inline;        }
<div id="EditableDiv" contenteditable="true"> (<span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>Field1</span> < 500)  <span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>OR</span> (<span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>Field2</span> > 100 <span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>AND</span> (<span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>Field3</span> <= 200) )</div>


Related Topics



Leave a reply



Submit