9

I am trying to highlight a part of the text on my website. This highlighted text will be saved for the specific user in a database and when the document is opened again it will show the previously highlighted text. I assumed I would be using javascript to highlight the text but I cannot seem to find a way to pinpoint where the word is that I am highlighting.

function getSelText()
{
    var txt = '';
     if (window.getSelection)
    {
        txt = window.getSelection();
             }
    else if (document.getSelection)
    {
        txt = document.getSelection();
            }
    else if (document.selection)
    {
        txt = document.selection.createRange().text;
            }
    else return "";
    return txt;
}

I am using that to get the selection but I cannot figure out where the selection is in the text. The biggest annoyance is when I have duplicates within the line or text so if I were to use search then I would find the first instance and not the one I was looking for.

So the question is : How do you pinpoint a word or a selection in the entire document?

Greg
  • 21,235
  • 17
  • 84
  • 107
vman
  • 321
  • 4
  • 10

3 Answers3

4

You can use my Rangy library and its selection serialization module for this. Rangy's core provides a consistent Selection and Range API for all browsers and the serializer module builds on this by converting each selection boundary into a path through the document. See the linked documentation for more details.

Tim Down
  • 318,141
  • 75
  • 454
  • 536
3

This certainly isn't a trivial task, since you're faced with the two major problems of locating the text in the current document, and then being able to find it again on a subsequent page load. The problem is further complicated if the content of your page is subject to change, since you can't even rely on the relative position of the text to stay the same.

You may want to consider whether or not this is the best approach for whatever you're trying to accomplish given the effort required, but here's something that might get you started in the right direction:

function getSelection() {
    var selection, position;

    if (window.getSelection) {
        selection = window.getSelection();

        if (selection && !selection.isCollapsed) {
            position = {
                'offset': selection.anchorOffset,
                'length': selection.toString().length,
                // We're assuming this will be a text node
                'node': selection.anchorNode.parentNode
            };
        }
    } else if (document.selection) {
        selection = document.selection.createRange();

        if (selection && selection.text.length) {
            var text = selection.parentElement().innerText,
                range = document.body.createTextRange(),
                last = 0, index = -1;

            range.moveToElementText(selection.parentElement());

            // Figure out which instance of the selected text in the overall
            // text is the correct one by walking through the occurrences
            while ((index = text.indexOf(selection.text, ++index)) !== -1) {
                range.moveStart('character', index - last);
                last = index;

                if (selection.offsetLeft == range.offsetLeft && selection.offsetTop == range.offsetTop) {
                    break;
                }
            }

            position = {
                'offset': index,
                'length': selection.text.length,
                'node': selection.parentElement()
            };
        }
    }

    return position;
}

As well as a method to select the text again:

function setSelection(position) {
    if (!position || !position.node) {
        return;
    }

    var selection, range, element;

    if (document.createRange) {
        element = position.node.childNodes[0];
        range = document.createRange();
        range.setStart(element, position.offset);
        range.setEnd(element, position.offset + position.length);
        selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
    } else if (document.body.createTextRange) {
        range = document.body.createTextRange();
        range.moveToElementText(position.node);
        range.collapse(true);
        range.move('character', position.offset);
        range.moveEnd('character', position.length);
        range.select();
    }
}

This code makes the rather naïve assumption that the selected text all resides in the same DOM element. Chances are high that if a user is selecting arbitrary text this won't be the case.

Given that the Selection object accounts for this with the anchorNode and focusNode properties, you could try and work around this, though dealing with the TextRange object in Internet Explorer might prove to be a bit more problematic.

There's also the problem of how to keep track of the position.node value across page requests. In my jsFiddle sample, I've used a slightly modified version of a selector-generating jQuery function to generate a selector string that can be saved and used to reselect the correct node later on. Note that the process is relatively trivial, so you could easily do it without jQuery – it just happened to save some effort in this case.

Of course if you're changing the DOM between visits, this approach will likely be fairly unstable. If you aren't though, I feel like it's probably one of the more reliable options.

Community
  • 1
  • 1
Tim Stone
  • 19,119
  • 6
  • 56
  • 66
  • 1
    This is a similar (but less fully realised) approach to the Serializer module in my Rangy library: http://code.google.com/p/rangy/wiki/SerializerModule – Tim Down Mar 07 '11 at 10:35
  • @TimDown Oh wow, I'm a bit ashamed I didn't think of Rangy when writing the answer given my positive past experiences with it. Thanks for the link. – Tim Stone Mar 07 '11 at 15:57
  • @TimStone if i try to select letters from 'World', like only 'orl'. but i start with right to left selection, then the offset position gives me 3, but it should have given me 1, cause o comes first, not l.. so how i can i tackle right to left selection of letters/words? – Basit Mar 04 '12 at 15:17
1

createRange creates a textRange object. It has all kinds of useful information:

            var range = document.selection.createRange();

            var left = range.boundingLeft;
            var top = range.boundingTop;
Steve Wellens
  • 20,506
  • 2
  • 28
  • 69
  • That won't really work since I have no textarea to speak of, I simply have a document that is read out of the database and displayed. – vman Mar 07 '11 at 03:31