views:

66

answers:

1

I am writing a script on which the user needs to be able to select some text which is sent via ajax to the backend script for further process.

I can select plain text nodes fine or text nodes that have bold, italic or underlined text inside it.

For e.g

<p>This is <strong>some</strong> cool <em>italic</em> text, <u>really!</u></p>

So, that works, that is cool.

However, the issue is, if the text node starts with hsome bold, italic or underlined text OR even headings it outputs the following error on firefox console:

The boundary-points of a range does not meet specific requirements." code: "1 range.surroundContents($('<span...wAnno_'+newLen+'"></span>').get(0));

The error is output when the user selects something like:

<strong>Mark says</strong> Hi

OR

<em>Mark says</em> Hi

OR

<u>Mark says</u> Hi

The same error outputs even if a text is enclosed inside heading tags e.g <h2>test</h2>

My code looks like:

var select = window.getSelection();
var parents = $(select.focusNode).parents('.the-content');

if($(select.focusNode).parent().hasClass('.highlighted')) {
    alert('This text is already highlighted');
} else {
    for(var i = 0; i < select.rangeCount; i++) {
        var range = select.getRangeAt(i); 
        range.surroundContents($('<span class="newHighlight" id="newHigh_'+newLen+'"></span>').get(0));
    }
}

var selectedText = select.toString();

I need help with fixing this.

Help with the code will be awesome.

A: 

The problem is that the surroundContents method of Range can't work on on Range where the start and end boundaries lie within different elements, because surrounding the contents of such a Range within an element would not produce valid HTML. If changing the background colour of your Range is all you need to do, you could use the following trick with document.execCommand:

function highlight(colour) {
    var range, sel;
    if (window.getSelection) {
        // Non-IE case
        sel = window.getSelection();
        if (sel.getRangeAt) {
            range = sel.getRangeAt(0);
        }
        document.designMode = "on";
        if (range) {
            sel.removeAllRanges();
            sel.addRange(range);
        }
        // Use HiliteColor since some browsers apply BackColor to the whole block
        if ( !document.execCommand("HiliteColor", false, colour) ) {
            document.execCommand("BackColor", false, colour);
        }
        document.designMode = "off";
    } else if (document.selection && document.selection.createRange) {
        // IE case
        range = document.selection.createRange();
        range.execCommand("BackColor", false, colour);
    }
}

Otherwise, you'll need to walk through the text nodes within the range and surround each with a <span>, which is not trivial. I've been working on a cross-browser range and selection library that includes a module for applying a CSS class to the contents of a selection or Range at http://code.google.com/p/rangy/, although that module is a few days away from being documented and released.

Tim Down
Can you guide me a little more on the method # 2? How do I surround each text node with span? I would love to use rangy library. It is looking awesome but there is no documentation to help me use it.
Haris
Yes, documentation is sorely lacking at the moment. Coding is so much more fun. I'll post something on the Google Code site now; I'll post back here as soon as I'm done.
Tim Down
Or is there a way to change class of the highlight text?
Haris
No, you're at the mercy of the browser's `execCommand` method and have no control over the mark-up it generates. I've updated Rangy now; let me know if you have any problems.
Tim Down
I am unable to implement the css class applier. I am getting invalid object toggleSelection.
Haris
This is my code. http://pastebin.com/pEifZFsx. Actual Error: Uncaught TypeError: Cannot call method 'toggleSelection' of undefined
Haris
Ah - the `rangy.addInitListener` bit is only there to make sure Rangy has loaded before trying to create the `CssClassApplier`, and you should only create it once rather than on every `mouseup` event. See http://pastebin.com/887kNJMH
Tim Down
Alright, rangy is loading now but now I get this error: Uncaught TypeError: Object #<an Object> has no method 'createCssClassApplier' ... This is how rangy.js looks like: http://pastebin.com/QQGnZdzq
Haris
Sorry, this is how it looks like: http://pastebin.com/hhGwadAd
Haris
It seems the module fails to initialize. If I write rangy.modules on firebug console, it says initialize: false for cssclassapplier module. It says true for DomRange and the other default modules.
Haris
Aha! I left development logging calls in, which was breaking it. I've uploaded a new version now. Sorry about the hassle. I also think I need to simplify the process of including modules.
Tim Down
Still the same "Uncaught TypeError: Object #<an Object> has no method 'createCssClassApplier'" bug. I just got the latest cssclassapplier.js from the google code repository.
Haris
OK. Now I'm confused. Is your code that creates the CssClassApplier inside a `$(document).ready(...)` event handler? If not, where is it? Thanks for having patience.
Tim Down
Yes, it is currently under $(document).ready({}) function
Haris
Screenshot of console.log http://is.gd/fP7lo
Haris
Are you sure you got the most recent cssclassapplier.js? It had zero downloads a few minutes ago before I downloaded it (twice).
Tim Down
I am sure about that. It still gives me the same error. http://is.gd/fPmdx. I redownloaded from site right now but it didn't add a count. This is how rangy.js (cssclassapplier.js appended to same file, bottom): http://pastebin.com/JC8hrLPz ... this is how test code looks like: http://pastebin.com/mWvjDHez
Haris
Tim Down
Also, I remembered you can initialize Rangy manually inside `$(document).ready`: http://pastebin.com/kFhQsuyn
Tim Down
Hey Tim. I got it work. Now the issue is that if there is a bold, italic or underline content between normal text nodes, it adds a separate span to their text nodes for e.g: <span class='myclass rangy_1'>this is some</span> <b><span class='myclass rangy_1'>bold</span></b> <span class='myclass rangy_1'>text</span>. Is it possible to wrap all the selected text in a container span? fo e.g <span class='myclass_container'><span class='myclass rangy_1'>this is some</span> <b><span class='myclass rangy_1'>bold</span></b> <span class='myclass rangy_1'>text</span></span>
Haris
I made that work, thanks for the help. Its working now except for 1 issue i.e after the CSS class is applied, the class is removed after you click anywhere else, can this be stopped?
Haris