1

I would like to create a function that select a given text inside a HTML element.

For example calling selectText('world') would select world in a markup like <span>Hello </span><strong>world</strong>!

Lots of answers on similar questions suggests to use range and selection but none of them work in my case (some would select all the text, some won't work with such markup, ...).

For now this is what I have (it doesn't work):

function selectText ( element, textToSelect ) {
    var text  = element.textContent, 
        start = text.indexOf( textToSelect ),
        end   = start + textToSelect.length - 1,
        selection, range;

    element.focus();

    if( window.getSelection && document.createRange ) {
        range = document.createRange();

        range.setStart( element.firstChild, start );
        range.setEnd( element.lastChild, end );

        selection = window.getSelection();

        selection.removeAllRanges();
        selection.addRange( range );
    } else if (document.body.createTextRange) {
        range = document.body.createTextRange();

        range.moveToElementText( element );
        range.moveStart( 'character', start );
        range.collapse( true );
        range.moveEnd( 'character', end );
        range.select();
    }
}

Here is a jsfiddle so you see what is actually happening: http://jsfiddle.net/H2H2p/

Outputed error :

Uncaught IndexSizeError: Failed to execute 'setStart' on 'Range': The offset 11 is larger than or equal to the node's length (5). 

P.S.: no jQuery please :)

Gabin
  • 920
  • 10
  • 26
  • Here's how you select the text, **http://jsfiddle.net/H2H2p/1/**, it's `setStart(node, offset)`, and you're getting the offset from the text only, so it's 11, but the textnode you're targeting doesn't have 11 characters, so you end up with an error. – adeneo Jun 01 '14 at 13:55
  • http://jsfiddle.net/H2H2p/2/ – adeneo Jun 01 '14 at 14:06
  • @adeneo Thank you, I feel much closer to my goal :) Do you see a way to make it work if I want to select `Hello wo` in `Hello world!` ? – Gabin Jun 01 '14 at 14:28

1 Answers1

2

You could use a combination of your approach of finding the text within the element's textContent and this function.

Demo: http://jsfiddle.net/H2H2p/3/

Code:

function selectText(element, textToSelect) {
    var elementText;
    if (typeof element.textContent == "string" && document.createRange && window.getSelection) {
        elementText = element.textContent;
    } else if (document.selection && document.body.createTextRange) {
        var textRange = document.body.createTextRange();
        textRange.moveToElement(element);
        elementText = textRange.text;
    }

    var startIndex = elementText.indexOf(textToSelect);
    setSelectionRange(element, startIndex, startIndex + textToSelect.length);
}

function getTextNodesIn(node) {
    var textNodes = [];
    if (node.nodeType == 3) {
        textNodes.push(node);
    } else {
        var children = node.childNodes;
        for (var i = 0, len = children.length; i < len; ++i) {
            textNodes.push.apply(textNodes, getTextNodesIn(children[i]));
        }
    }
    return textNodes;
}

function setSelectionRange(el, start, end) {
    if (document.createRange && window.getSelection) {
        var range = document.createRange();
        range.selectNodeContents(el);
        var textNodes = getTextNodesIn(el);
        var foundStart = false;
        var charCount = 0, endCharCount;

        for (var i = 0, textNode; textNode = textNodes[i++]; ) {
            endCharCount = charCount + textNode.length;
            if (!foundStart && start >= charCount
                    && (start < endCharCount ||
                    (start == endCharCount && i < textNodes.length))) {
                range.setStart(textNode, start - charCount);
                foundStart = true;
            }
            if (foundStart && end <= endCharCount) {
                range.setEnd(textNode, end - charCount);
                break;
            }
            charCount = endCharCount;
        }

        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    } else if (document.selection && document.body.createTextRange) {
        var textRange = document.body.createTextRange();
        textRange.moveToElementText(el);
        textRange.collapse(true);
        textRange.moveEnd("character", end);
        textRange.moveStart("character", start);
        textRange.select();
    }
}
Community
  • 1
  • 1
Tim Down
  • 318,141
  • 75
  • 454
  • 536