9

I am looking for a jquery plugin for multiple highlighted text in an element. I found a very popular plugin for exactly that: http://bartaz.github.com/sandbox.js/jquery.highlight.html along with many others.

They work fine, but in case that I want to highlight text which overlaps with earlier highlighted text - it does not work.

Does anyone know a jquery or javascript plugin that supports multiple highlighted texts and which correctly highlights overlapped texts?

7
  • do you mean the color of the double highlighted element should be added to itself (looks darker)? Commented Aug 18, 2012 at 11:48
  • I mean the new selection should higlighted text even this text is a part of older selections Commented Aug 20, 2012 at 7:52
  • So you search for string 'quick brown fox' and it is highlighted in (let's say) yellow. You then search for 'fox' and choose green. You want 'fox' to be green or yellow-green? You search for 'o' and choose red. The 'o' in brown is red or orange? The 'o' in fox is red or a sickly brown color? (I need to brush up on color theory, obviously). It's your +50 so I think it would behoove you to be exact about what you want. Oh, and +1, neat plugin. Commented Mar 1, 2013 at 3:35
  • Since it's my "+50," I'll chime in. First of all, none of this has to include searching. If I have a string, say "The quick brown fox," and "the quick" is highlighted originally, and then I go and highlight "quick brown fox", I would like the two highlighted sections to join into one highlighted section, so that "the quick brown fox" is highlighted. Commented Mar 1, 2013 at 3:39
  • 2
    Simply use jquery.mark Commented Mar 28, 2016 at 11:37

1 Answer 1

15
+50

You can do everything with the highlighter plugin that you found if you add a couple little tweaks. As an illustration I posted to fiddle a working example that I quickly put together this afternoon: http://jsfiddle.net/4bwgA/

Here I will go first step by step through all the things the plugin (the OP refers to) already does and at the end give the solution to the overlapping highlighted areas.

What the highlighter does, is put a tag around the word you apply it for. So if you have

   <p>Who is the quick brown fox?</p>

and you highlighted the word "brown" with

   $("p").highlight("brown");

you get

   <p>Who is the quick <span class="highlight">brown</span> fox?</p>

and an additional command

   $("p").highlight("brown fox");

will not find anything, cause the string does not have this substring anymore, as it now has tags around brown.

So if it is just about the overlap ... the solution is easy. The package offers an unhighlighting function, that you can apply before the new highlighting

   $("p").unhighlight().highlight("brown fox");

and get

   <p>Who is the quick <span class="highlight">brown fox</span>?</p>

Now this is nothing exciting so far. But what if we have highlighted "brown" in one go and then "fox" in the next one. We get this

   <p>Who is the quick <span class="highlight">brown</span> <span class="highlight">fox</span>?</p>

Then we want to join the neighboring highlighted areas. This can be done with an additional function, that does something like this:

    function mergenode(node) {
        var parent1 = node.contents();
        parent1.each(function (i) {
            if (i > 0 && this.nodeType !== 1 && //if text node
                this.data.search(/^\s*$/g) === 0 && //consisting only of hyphens or blanks
                parent1[i - 1].nodeName === "SPAN" && //bordering to SPAN elements
                parent1[i + 1].nodeName === "SPAN" && 
                parent1[i - 1].className === "highlight" && //of class highlight
                parent1[i + 1].className === "highlight") {
                    selected1.push(parent1[i - 1]
                        .textContent.concat(this.data, parent1[i + 1].textContent));
            }
            else if (this.nodeType === 1 && 
                this.nodeName === "SPAN" && 
                parent1[i + 1].nodeName === "SPAN" && 
                this.className === "highlight" && 
                parent1[i + 1].className === "highlight") {
                    selected1.push(this.textContent.concat(parent1[i + 1].textContent));
            }
        });
        $(node).unhighlight().highlight(selected1);
    }

This will merge all neighboring spans with class highlight (this is just written for the general highlighter tag, but could easily be extended with two extra arguments for nodeName and className, for custom tags). The result will look like this

    <p>Who is the quick <span class="highlight">brown fox</span>?</p>

All good so far. But what if our search strings overlap?!!! First ... the best thing to do with overlaps is to deselect the old selection before checking for overlapped strings on the cleared text. For this we need to remember the previous selection, which can for example be saved in an array (I called it selected1) that gets a value added to it at each additional select.

At every run the final selection (all merged) is saved into the selected1 array, so that it can be used for future merging and overlapping.

Now, how does highlighter deal with an array of strings? It applies the highlight function to each of them in the order they have been passed to the function. So, if two strings overlap completely, we can deal with that just by sorting the array of selection strings and first highlighting the longer ones. For example

    $("p").highlight(["brown fox","brown"]);

will just highlight

    <p>Who is the quick <span class="highlight">brown fox</span>?</p>

The array can be sorted by length with something like this

    words = words.sort(function (str1, str2) {
        return (str1.length < str2.length) ? 1 : 0;
    });

The completely overlapping and neighboring highlights are now dealt with. Now we have to make sure we get the partially overlapping strings. Here I wrote a function that compares two strings for overlap ... there are other ways to do this, but what I want to show in this answer is mostly just the sequence of steps that you need to do to get the highlighting the way you want =)

This function takes two strings, checks if they overlap, and if so, combines them and saves the new combined string into an array, toselect, that at the end gets appended to the original words array. There might be some useless combinations, but it wont hurt - they won't show at the end.

    function overlap(a, b) {
        var l = b.length,
            m = a.length;
        for (var j = 1; j < l; j++) {
            if (a.indexOf(b.substr(l - j)) === 0) {
                toselect.push(b.concat(a.substr(j)));
            } else if (a.substr(m - j).indexOf(b.substr(0, j)) === 0) {
                toselect.push(a.concat(b.substr(j)));
            }
        }
    }

Now we want to apply this function to all possible pairs of elements in the words array. We could iterate this with a loop, but I got a little excited and did this

    $.each(arr, function (i) {
        $.each(arr.slice(i + 1), function (i, v) {
            overlap(arr[i], v);
        });
    });

Now we just have to append the new toselect array, in which with all possible overlapping strings have been concatenated, to the original words array.

So now we have all the parts we need and we can put them together. Every time we want to highlight a new string or array of strings, we do the following steps:

  1. Unhighlight everything that is currently highlighted (anything that was highlighted will be in the selected1 array).
  2. Our new string or array of strings goes into the words array
  3. Append selected1 to words
  4. Combine all partially overlapping pairs of strings in words and add the new combined strings to words (using the overlap function and its wrapper that iterates through the whole array - overdiag in the fiddle example)
  5. Sort words by string length, so the longest strings will come first
  6. Highlight all the strings in words
  7. Merge all neighboring highlighted strings (using the mergenode function)
  8. The final set of highlighted strings is saved into selected1

I put this together quickly this afternoon, so it's not a perfect implementation of the idea, but it works =) I added my code into the highlighter script and added them to a simple example on jsfiddle for you to play with http://jsfiddle.net/4bwgA/. There are buttons so you can press through all the different steps and see what they do.

Sign up to request clarification or add additional context in comments.

4 Comments

Welcome to Stack Overflow! Thanks for your post! Please do not use signatures/taglines in your posts. Your user box counts as your signature, and you can use your profile to post any information about yourself you like. FAQ on signatures/taglines
@AndrewBarber Thanks, I did read the FAQ, but then just got so excited about the writing =) I won't do it again.
Great job, now all you need is packaging it all into a standalone pretty looking plugin, abstracting unhighlight calls so they happen automatically whenever you call highlight, and making sure actual external unhighlight calls remove its "memory", since you'd want to be able to remove everything highlighted for good aswell. But otherwise, this is probably as good as it can get without resorting to magic.
@Mirek Hi! I was just going through my older answers and was wandering if this answer wasn't helpful to you at all? Or if there was some other reason you didn't accept it. - I know that it does not provide a list of out of the box plugins - but you can feel free to use my solutions and modify them however you want for production, if you still need it =) Cheers!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.