When I use the browser contenteditable=true on a div in order to let the user update the text in it, I run into this problem (using Chrome):
When using the delete
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.
For now, I am using
$(textObject).find('*').removeAttr('style');
upon deselect of the editable region.
It could also be used upon keyup.
Add style display:inline-block; to contenteditable, it will not generate div, p, span automatic in chrome
The problem is, actually, that not only it inserts span's and p's, but if you happen to copy a table it will insert the whole table with tbody and rows there. So, the only option to handle this that I've found working for me is this:
$(document).on("DOMNodeInserted", $.proxy(function (e) {
if (e.target.parentNode.getAttribute("contenteditable") === "true") {
with (e.target.parentNode) {
replaceChild(e.target.firstChild, e.target);
normalize();
}
}
}, this));
Yes, it somewhat affects the overal page performance, but it blocks inserting tables and stuff into the contenteditables.
UPDATE:
The script above handles only basic cases, when the content you wish for is wrapped in one level of span/p tags. However, if you copy from, say, Word, you may end up copying even the whole tables.
So, here is the code that handles everything i've thrown at it so far:
$(document).on("DOMNodeInserted", $.proxy(function (e) {
if (e.target.parentNode.getAttribute("contenteditable") === "true") {
var newTextNode = document.createTextNode("");
function antiChrome(node) {
if (node.nodeType == 3) {
newTextNode.nodeValue += node.nodeValue.replace(/(\r\n|\n|\r)/gm, "")
}
else if (node.nodeType == 1 && node.childNodes) {
for (var i = 0; i < node.childNodes.length; ++i) {
antiChrome(node.childNodes[i]);
}
}
}
antiChrome(e.target);
e.target.parentNode.replaceChild(newTextNode, e.target);
}
}, this));
Also, feel free to modify the regex in the middle any way you like to remove symbols that are particularly hazardous in your case.
UPDATE 2
After thinking for a little while and googling, I've just wrapped the code above into simple jQuery plugin to ease the usage. Here is the GitHub link
Man, the timing on this question is crazy. What I'm doing with contenteditable is forcing all the content to be within paragraph tags, of the contenteditable div.
So the way I handle this bullshit spans that get created is by flattening every <p>
on every keystroke.
Since .text()
selects ALL the text of an element, including its subelements, I can just set (in coffeescript)
t = $(element).text()
$(element).children().remove()
$(element).text(t)
Which solves my problem. As an aside, these browser implementers really botched contenteditable pretty bad. There is a massive opportunity here to make this easy, because it really is the future of the web.