tags:

views:

103

answers:

3

i want a function that finds some strings i've got in an array in the DOM and emphasize it.

eg.

keywords[0] = 'linux';
keywords[1] = 'suse pro';

<body>
    im a huge fan of <em>linux</em> and at the moment im using <em>suse pro</em> and finds it amazing.
</body>

how do i do it in the easiest way. thanks in advance

EDIT: i found a very easy way to accomplish this: jquery highlight plugin! cheers!

A: 

This is probably the easiest way, but maybe not the best way: http://www.tizag.com/javascriptT/javascript-string-replace.php (scroll down to the bottom, where it says "Replace Function: Global Regular Expression"

You would replace linux with <em>linux</em>, etc.

MatrixFrog
I have a feeling there is a better way. This was just the first thing that came to mind.
MatrixFrog
+2  A: 

I had to do this a couple of months ago. Originally someone used string manipulation of innerHTML as suggested by others here but that road leads to madness. The troublesome corner cases are: What if the keyword being marked up is an element's class name or id. We also don't want to mangle any embedded javascript and accidentally cause invalid markup. Also need to handle embedded css styles. In the end you end up writing a text parser for HTML4.0 + ECMAscript + css which, even in the limited case, is a lot of work - it's writing your own web browser - IN JAVASCRIPT!

But we already have a HTML+JS+CSS parser in the web browser that generates a nice DOM tree for us. So I decided to use that. This is what I came up with in the end:

keywords = ['hello world','goodbye cruel world'];
function replaceKeywords (domNode) {
    if (domNode.nodeType === Node.ELEMENT_NODE) { // We only want to scan html elements
        var children = domNode.childNodes;
        for (var i=0;i<children.length;i++) {
            var child = children[i];

            // Filter out unwanted nodes to speed up processing.
            // For example, you can ignore 'SCRIPT' nodes etc.
            if (child.nodeName != 'EM') {
                replaceKeywords(child); // Recurse!
            }
        }
    }
    else if (domNode.nodeType === Node.TEXT_NODE) { // Process text nodes
        var text = domNode.nodeValue;

        // This is another place where it might be prudent to add filters

        for (var i=0;i<keywords.length;i++) {
            var match = text.indexOf(keywords[i]); // you may use search instead
            if (match != -1) {
                // create the EM node:
                var em = document.createElement('EM');

                // split text into 3 parts: before, mid and after
                var mid = domNode.splitText(match);
                mid.splitText(keywords[i].length);

                // then assign mid part to EM
                mid.parentNode.insertBefore(em,mid);
                mid.parentNode.removeChild(mid);
                em.appendChild(mid);
            }
        }
    }
}

As long as you're careful about filtering out things you don't want to process (for example, empty text nodes that contain only whitespace - there are lots of them, trust me) this is very fast.

Additional filtering not implemented for algorithmic clarity - left as an exercise to the reader.

slebetman
what should i replace the domNode with? i have the text in a div with an id.
weng
@unknown You can invoke this as `replaceKeywords(document.getElementById("someid"))`. @slebetman You might want to replace the `1` and `3` constants with `Node.ELEMENT_NODE` and `Node.TEXT_NODE` for greater clarity.
Brian Campbell
@Brian will do.Also for processing the whole document you can call it as `replaceKeywords(document.body)` the filter that ignores 'EM' will prevent it from processing text already processed.
slebetman