Matt Welander
Matt Welander

Reputation: 8548

prevent contenteditable mode from creating <span> tags

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 =)

Upvotes: 22

Views: 13240

Answers (6)

Dieter Bender
Dieter Bender

Reputation: 37

My solution is to add .data('myTag',1) to each created Tag. Using the data() method does not affect attributes in the DOM. This way you can keep your Tags clean and filter out browser created Tags and remove/unwrap them.

$('[contenteditable]').on("DOMNodeInserted",e=>{
    //Filter out browser crated tags
    if(!$(e.target).data('myTag'))$(e.target).contents().unwrap();
})
$('#button.textcolor').click(e=>{
   //Here your script to createRange in contenteditable
   ...

   //This span should not be removed
    var tag=$('<span>').data('myTag',1).css('color','#f00')
    range.insertNode(tag[0])
})

Upvotes: 0

Le Tung Anh
Le Tung Anh

Reputation: 899

Add style display:inline-block; to contenteditable, it will not generate div, p, span automatic in chrome

Upvotes: 2

UndeadBane
UndeadBane

Reputation: 463

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

Upvotes: 6

Matt Welander
Matt Welander

Reputation: 8548

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.

Upvotes: 2

zachaysan
zachaysan

Reputation: 1816

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.

Upvotes: 0

Matt Welander
Matt Welander

Reputation: 8548

For now, I am using

        $(textObject).find('*').removeAttr('style');

upon deselect of the editable region.

It could also be used upon keyup.

Upvotes: 2

Related Questions