8

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?

Community
  • 1
  • 1
Horen
  • 10,510
  • 9
  • 60
  • 105
  • possible duplicate of [Sanitize/Rewrite HTML on the Client Side](http://stackoverflow.com/questions/295566/sanitize-rewrite-html-on-the-client-side) – Dave Jarvis Sep 30 '13 at 03:59

4 Answers4

4

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);
            }
        }
    }
}
​
Tim Down
  • 292,637
  • 67
  • 429
  • 506
  • 1
    man i also got link of RANGY plugin but i can't figure out the solution but you did thumbs up :) – Muhammad Talha Akbar Dec 14 '12 at 15:12
  • In Firefox, you can select multiple pieces of text by keeping the Ctrl key pressed while selecting. Your script removes the formatting only from the 1st selection. – user2428118 Dec 14 '12 at 16:25
  • @user2428118: That's true, but no other browser supports that or is likely to, and the [current spec](http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections) only talks about a single selection range. Therefore a while ago I stopped bothering to handle the multiple range case in Stack Overflow answers; you're the first to complain. I'll update my answer. – Tim Down Dec 14 '12 at 16:33
0

http://jsfiddle.net/fQCZT/2/

<div contenteditable="true">
    <p>
        Here is some <b>formatted text</b>. Please select some and watch the formatting
        <i>magically disappear</i>.
        <br>
        Look, some more <u>formatted</u> content.
    </p>
    <p>And <i>another</i> paragraph of it</p>
</div>
<button onmousedown="removeSelectionFormatting()">Change</button>​

It is duplicate of one of the above answers but with a button to change text!

Muhammad Talha Akbar
  • 9,424
  • 5
  • 33
  • 59
0

Based on your suggested function, I came up with this neat little script after a little bit of experimenting in the console. Didn't test this for cross browser compatibility though!

var selection = window.getSelection().getRangeAt(0);
var selectedText = selection.cloneContents().childNodes[0]; // This is your selected text.

Now, you can strip HTML tags from your selectedText and replace it using the function you already provided in your question.

For example, you could use strip_tags() from the php.js project

I hope this answers your question.

Robin van Baalen
  • 3,422
  • 2
  • 19
  • 33
  • That doesn't work. I cannot apply the `strip_tags` function to `selectedText`. It will return `TypeError: input.replace is not a function` – Horen Dec 14 '12 at 14:15
  • Maybe the strip_tags function is not what you seek and you could combine my answer with, for example, @gulty's answer. I assumed you already had a solution to replace the string, using the answer in the link you provided: http://stackoverflow.com/a/3997896/1503476 – Robin van Baalen Dec 14 '12 at 14:37
0

http://jsfiddle.net/tnyWD/

HTML:

<div class="test">
    Hello <b>there </b><a href="#">I am a link</a>
</div>
<button class="remove">Remove HTML</button>​

JS:

$(document).ready(function(){
    jQuery.fn.stripTags = function() { return this.replaceWith(this.html().replace(/<\/?[^>]+>/gi, '') ); };
$('.remove').click(function(){
    $('.test').stripTags();
});
});​

Is that what you're looking for?

gulty
  • 1,058
  • 1
  • 8
  • 13
  • I'm working on a selection in a `contenteditable` div. So the stripTags I need to apply only to the selection! – Horen Dec 14 '12 at 14:07