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 backspace key to remove a line break (jump back up a line) the browser inserts a tag with inline style set, around that text.
This is really frustrating because not only does it do that, it also adds an inline style to that span tag, for example if my font color is currently black, it adds a style="color:black" to that span tag.
The result is that I can no longer edit the color of that text with my toolbar, since it has been set hard to the span tag by the inline style. Same thing happens with font size if I do this same thing backing up a line with the delete key.
Anyone here that could teach me a thing or two about contenteditable, or suggest a way to remove the span:s, if it isnät possible to prevent this browser behaviour..
** To reproduce this problem ** - Create a div in the browser, set inline style on it with for instance font-size:36px - Edit the div's text with content editable in the browser, write a couple of lines with manual linebreaks. - Now put cursor IN FRONT OF / BEFORE a paragraph of text and hit backspace. A span tag should now be generated around the text that is IN FRONT OF your cursor, and the style of it changed.
UPDATE ** So I have tried a few different solutions with limited success. First I tried to remove all tags, but then I lost all linebreaks as well (maybe a better regexp could solve this, I am no expert on regExp writing).
All functions below were first called upon keyup event, but then I attached them to unselecting the textbox. How to fire below functions is irrelevant to the question.
//option to remove all tags
var withTags = $(textObject).html();
var withoutTags = withTags.replace(/<(?:.|\n)*?>/gm, '');
$(textObject).html(withoutTags);
My second attempt was more successful, by removing style tags of objects underneath the textbox (the divs ans spans inside the textbox that chrome added), here was my first code
//option to remove style of elements
$(textObject).children().each(function() {
$(this).removeAttr('style');
console.log('removing style from first level element: '+this);
});
Then I realized that every time I edit a textbox, chrome might add a new nested level of div/span tags that the above code won't reach, so I did this:
//option to remove style of elements
$(textObject).children().each(function() {
$(this).removeAttr('style');
console.log('removing style from first level element: '+this);
//And of course it would be all too easy if chrome added only one level of elements...
$(this).children().each(function() {
$(this).removeAttr('style');
console.log('removing style from second level element: '+this);
});
});
But that isn't enough since the amount of nesting could theoretically be unlimited. Still working on a better solution. Any input is appreciated =)
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