Search code examples
javascriptjquerywysiwygcontenteditablerangy

Remove all HTML tags inside of selection in contenteditable


I have a <div /> which is contenteditable and can contain several types of HTML elements such as <span />, <a />, <b />, <u /> and so on.

Now when I select text in my contenteditable I would like to have a button that removes all the styles within the selection.

Example 1:

The Selection:

Hello <b>there</b>. I am <u>a selection</u>

would become:

Hello there. I am a selection

Example 2:

The Selection:

<a href="#">I am a link</a>

would become:

I am a link

You get the idea...

I have found this helpful function https://stackoverflow.com/a/3997896/1503476 which replaces the current selection with custom text. But I just cannot get the content of the selection first and strip the tags out before replacing it. How can I do that?


Solution

  • The way I would do this is to iterate over the nodes within the selection and remove inline nodes (maybe leaving <br> elements alone). Here's an example, using my Rangy library for convenience. It works in all major browsers (including IE 6) but is not quite perfect: for example, it does not split partially selected formatting elements, meaning that a partially selected formatting element is completely removed rather than just the selected portion. To fix this would be more tricky.

    Demo: http://jsfiddle.net/fQCZT/4/

    Code:

    var getComputedDisplay = (typeof window.getComputedStyle != "undefined") ?
        function(el) {
            return window.getComputedStyle(el, null).display;
        } :
        function(el) {
            return el.currentStyle.display;
        };
    
    function replaceWithOwnChildren(el) {
        var parent = el.parentNode;
        while (el.hasChildNodes()) {
            parent.insertBefore(el.firstChild, el);
        }
        parent.removeChild(el);
    }
    
    
    function removeSelectionFormatting() {
        var sel = rangy.getSelection();
    
        if (!sel.isCollapsed) {
            for (var i = 0, range; i < sel.rangeCount; ++i) {
                range = sel.getRangeAt(i);
    
                // Split partially selected nodes 
                range.splitBoundaries();
    
                // Get formatting elements. For this example, we'll count any
                // element with display: inline, except <br>s.
                var formattingEls = range.getNodes([1], function(el) {
                    return el.tagName != "BR" && getComputedDisplay(el) == "inline";
                });
    
                // Remove the formatting elements
                for (var i = 0, el; el = formattingEls[i++]; ) {
                    replaceWithOwnChildren(el);
                }
            }
        }
    }
    ​