2

I am trying to replace text on a webpage with links. When I try this it just replaces the text with the tag and not a link. For example this code will replace "river" with:

<a href="http://www.cnn.com">asdf</a>

This is what I have so far:

function handleText(textNode)
{
    var v = textNode.nodeValue;
    v = v.replace(/\briver\b/g, '<a href="http://www.cnn.com">asdf</a>');
    textNode.nodeValue = v;
}
Peyman Mohamadpour
  • 17,954
  • 24
  • 89
  • 100
Door
  • 23
  • 4

1 Answers1

2

If all you wanted to do was change the text to other plain text, then you could change the contents of the text nodes directly. However, you are wanting to add an <a> element. For each <a> element you want to add, you are effectively wanting to add a child element. Text nodes can not have children. Thus, to do this you have to actually replace the text node with a more complicated structure. In doing so, you will want to make as little impact on the DOM as possible, in order to not disturb other scripts which rely on the current structure of the DOM. The simplest way to make little impact is to replace the text node with a <span> which contains the new text nodes (the text will split around the new <a>) and any new <a> elements.

The code below should do what you desire. It replaces the textNode with a <span> containing the new text nodes and the created <a> elements. It only makes the replacement when one or more <a> elements need to be inserted.

function handleTextNode(textNode) {
    if(textNode.nodeName !== '#text'
        || textNode.parentNode.nodeName === 'SCRIPT' 
        || textNode.parentNode.nodeName === 'STYLE'
    ) {
        //Don't do anything except on text nodes, which are not children 
        //  of <script> or <style>.
        return;
    }
    let origText = textNode.textContent;
    let newHtml=origText.replace(/\briver\b/g,'<a href="http://www.cnn.com">asdf</a>');
    //Only change the DOM if we actually made a replacement in the text.
    //Compare the strings, as it should be faster than a second RegExp operation and
    //  lets us use the RegExp in only one place for maintainability.
    if( newHtml !== origText) {
        let newSpan = document.createElement('span');
        newSpan.innerHTML = newHtml;
        textNode.parentNode.replaceChild(newSpan,textNode);
    }
}

//Testing: Walk the DOM of the <body> handling all non-empty text nodes
function processDocument() {
    //Create the TreeWalker
    let treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT,{
        acceptNode: function(node) { 
            if(node.textContent.length === 0) {
                //Alternately, could filter out the <script> and <style> text nodes here.
                return NodeFilter.FILTER_SKIP; //Skip empty text nodes
            } //else
            return NodeFilter.FILTER_ACCEPT;
        }
    }, false );
    //Make a list of the text nodes prior to modifying the DOM. Once the DOM is 
    //  modified the TreeWalker will become invalid (i.e. the TreeWalker will stop
    //  traversing the DOM after the first modification).
    let nodeList=[];
    while(treeWalker.nextNode()){
        nodeList.push(treeWalker.currentNode);
    }
    //Iterate over all text nodes, calling handleTextNode on each node in the list.
    nodeList.forEach(function(el){
        handleTextNode(el);
    });
} 
document.getElementById('clickTo').addEventListener('click',processDocument,false);
<input type="button" id="clickTo" value="Click to process"/>
<div id="testDiv">This text should change to a link -->river<--.</div>

The TreeWalker code was taken from my answer here.

Makyen
  • 31,849
  • 12
  • 86
  • 121
  • That works great, thanks! What if I wanted to replace "river" and "stream" and link them to two different websites? How would that work? – Door Oct 27 '16 at 18:51
  • I'm glad I was able to help. How to do a second replacement will depend on how generic you want the `handleText` function to be. If it is a static replacement which you always want to perform (like your `river` replacement), it is easy to just have a statement after the `let newHtml ...` that is newHtml = newHtml.replace(/\bstream\b/g, ...`. – Makyen Oct 27 '16 at 18:57