2

I want to create pages with urls such as:

http://xyzcorp/schedules/2015Aug24_Aug28/Jim_Hawkins
http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones
http://xyzcorp/schedules/2015Aug24_Aug28/John_Silver

These particular URLs would all contain the exact same content (the "2015Aug24_Aug28" page), but would highlight all instances of the name tagged on to the end. For example, "http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones" would show every instance of the name "Billy Bones" highlighted, as if a "Find" for that name was executed on the page via the browser.

I imagine something like this is required, client-side:

var employee = getLastURLPortion(); // return "Billy_Bones" (or whatever)
employee = humanifyTheName(employee); // replaces underscores with spaces, so that it's "Billy Bones" (etc.)
Highlight(employee); // this I have no clue how to do

Can this be done in HTML/CSS, or is JavaScript or jQuery also required for this?

B. Clay Shannon
  • 1,055
  • 124
  • 399
  • 759
  • jQuery is never _required_. – Sebastian Simon Aug 23 '15 at 12:56
  • 3
    Neither is beer, but it's awfully good on a hot day. – B. Clay Shannon Aug 23 '15 at 13:00
  • 1
    Use a highlighter plugin – charlietfl Aug 23 '15 at 13:04
  • 1
    I think the real question is how do the pages get rendered, and can you add something in the template for it? – Kelly J Andrews Aug 23 '15 at 13:13
  • 1
    A highlighter plugin is a wise choice here. I’m currently writing an answer, but it turns out to be slightly overly complex. You could just go with a plugin. – Sebastian Simon Aug 23 '15 at 13:22
  • 1
    This is a more interesting problem than I originally thought. Thanks for posting! – Kelly J Andrews Aug 23 '15 at 13:24
  • 2
    @Xufox that's why I suggested using a plugin that is already battle tested. Once anyone digs into this they realize it's not a trivial exercise to maintain html structure and not interfere with events – charlietfl Aug 23 '15 at 13:27
  • 2
    It doesn't look like the OP did *any* research at all before asking this question. OP seems to believe it might be possible using 3 specific technologies, but doesn't bother doing a basic search each on each one (eg. "highlight text with javascript" or "highlight text with css"). You're supposed to research before you ask your question, kiddies! – cimmanon Aug 23 '15 at 15:28

3 Answers3

3

I used the following regex to replace all the matching url to create anchors with highlighted text:

(http://xyzcorp/schedules/(.*?)/)(.*?)( |<|\n|\r|$)

Regular expression visualization

Debuggex Demo

The following code will replace all plain urls. If you don't need them to be replaced to links, just highlight them, remove the tags:

var str = "http://xyzcorp/schedules/2015Aug24_Aug28/Jim_Hawkins http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones http://xyzcorp/schedules/2015Aug24_Aug28/John_Silver ";

var highlighted = str.replace( new RegExp("(http://xyzcorp/schedules/(.*?)/)(.*?)( |<|\n|\r|$)","g"), "<a href='$1$3'>$1<span style='background-color: #d0d0d0'>$3</span></a>" );

The content of the highlighted string will be:

<a href='http://xyzcorp/schedules/2015Aug24_Aug28/Jim_Hawkins'>http://xyzcorp/schedules/2015Aug24_Aug28/<span style='background-color: #d0d0d0'>Jim_Hawkins</span></a>
<a href='http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones'>http://xyzcorp/schedules/2015Aug24_Aug28/<span style='background-color: #d0d0d0'>Billy_Bones</span></a>
<a href='http://xyzcorp/schedules/2015Aug24_Aug28/John_Silver'>http://xyzcorp/schedules/2015Aug24_Aug28/<span style='background-color: #d0d0d0'>John_Silver</span></a>

UPDATE:

This function will replace the matching names from the input text:

function highlight_names( html_in )
{
    var name = location.href.split("/").pop().replace("_"," ");
    return html_in.replace( new RegExp( "("+name+")", "g"), "<span style='background-color: #d0d0d0'>$1</span>" );
}
  • Impressive, but it's not the URLS that should be highlighted (they will only appear on the address bar); what should be highlighted is every instance of "Billy Bones" on the page (assuming the URL ends with "\Billy_Bones"). – B. Clay Shannon Aug 23 '15 at 13:45
3

If you call the function

highlight(employee);

this is what that function would look like in ECMAScript 2018+:

function highlight(employee){
  Array.from(document.querySelectorAll("body, body *:not(script):not(style):not(noscript)"))
    .flatMap(({childNodes}) => [...childNodes])
    .filter(({nodeType, textContent}) => nodeType === document.TEXT_NODE && textContent.includes(employee))
    .forEach((textNode) => textNode.replaceWith(...textNode.textContent.split(employee).flatMap((part) => [
        document.createTextNode(part),
        Object.assign(document.createElement("mark"), {
          textContent: employee
        })
      ])
      .slice(0, -1))); // The above flatMap creates a [text, employeeName, text, employeeName, text, employeeName]-pattern. We need to remove the last superfluous employeeName.
}

And this is an ECMAScript 5.1 version:

function highlight(employee){
  Array.prototype.slice.call(document.querySelectorAll("body, body *:not(script):not(style):not(noscript)")) // First, get all regular elements under the `<body>` element
    .map(function(elem){
      return Array.prototype.slice.call(elem.childNodes); // Then extract their child nodes and convert them to an array.
    })
    .reduce(function(nodesA, nodesB){
      return nodesA.concat(nodesB); // Flatten each array into a single array
    })
    .filter(function(node){
      return node.nodeType === document.TEXT_NODE && node.textContent.indexOf(employee) > -1; // Filter only text nodes that contain the employee’s name.
    })
    .forEach(function(node){
      var nextNode = node.nextSibling, // Remember the next node if it exists
        parent = node.parentNode, // Remember the parent node
        content = node.textContent, // Remember the content
        newNodes = []; // Create empty array for new highlighted content

      node.parentNode.removeChild(node); // Remove it for now.
      content.split(employee).forEach(function(part, i, arr){ // Find each occurrence of the employee’s name
        newNodes.push(document.createTextNode(part)); // Create text nodes for everything around it

        if(i < arr.length - 1){
          newNodes.push(document.createElement("mark")); // Create mark element nodes for each occurrence of the employee’s name
          newNodes[newNodes.length - 1].innerHTML = employee;
          // newNodes[newNodes.length - 1].setAttribute("class", "highlighted");
        }
      });

      newNodes.forEach(function(n){ // Append or insert everything back into place
        if(nextNode){
          parent.insertBefore(n, nextNode);
        }
        else{
          parent.appendChild(n);
        }
      });
    });
}

The major benefit of replacing individual text nodes is that event listeners don’t get lost. The site remains intact, only the text changes.

Instead of the mark element you can also use a span and uncomment the line with the class attribute and specify that in CSS.

This is an example where I used this function and a subsequent highlight("Text"); on the MDN page for Text nodes:

A website with all occurrences of “Text” highlighted

(The one occurrence that isn’t highlighted is an SVG node beyond an <iframe>).

Sebastian Simon
  • 14,320
  • 6
  • 42
  • 61
2

One solution would be, after window is loaded, to traverse all nodes recursively and wrap search terms in text nodes with a highlight class. This way, original structure and event subscriptions are not preserved.

(Here, using jquery, but could be done without):

Javascript:

$(function() {
  // get term from url
  var term = window.location.href.match(/\/(\w+)\/?$/)[1].replace('_', ' ');
  // search regexp
  var re = new RegExp('(' + term + ')', 'gi');
  // recursive function
  function highlightTerm(elem) {
    var contents = $(elem).contents();
    if(contents.length > 0) {
      contents.each(function() {
        highlightTerm(this);
      });
    } else {
      // text nodes
      if(elem.nodeType === 3) {
        var $elem = $(elem);
        var text = $elem.text();
        if(re.test(text)) {
          $elem.wrap("<span/>").parent().html(text.replace(re, '<span class="highlight">$1</span>'));
        }
      }
    }
  }
  highlightTerm(document.body);
});

CSS:

.highlight {
    background-color: yellow;
}

$(function() {
  // get term from url
  //var term = window.location.href.match(/\/(\w+)\/?$/)[1].replace('_', ' ');
  var term = 'http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones/'.match(/\/(\w+)\/?$/)[1].replace('_', ' ');
  // search regexp
  var re = new RegExp('(' + term + ')', 'gi');
  // recursive function
  function highlightTerm(elem) {
    var contents = $(elem).contents();
    if(contents.length > 0) {
      contents.each(function() {
        highlightTerm(this);
      });
    } else {
      // text nodes
      if(elem.nodeType === 3) {
        var $elem = $(elem);
        var text = $elem.text();
        if(re.test(text)) {
          $elem.wrap("<span/>").parent().html(text.replace(re, '<span class="highlight">$1</span>'));
        }
      }
    }
  }
  highlightTerm(document.body);
});
.highlight  {
  background-color: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <div>
      <div class="post-text" itemprop="text">
        <p>I want to create pages with urls such as:</p>
        <pre style="" class="default prettyprint prettyprinted">
          <code>
            <span class="pln">http</span>
            <span class="pun">:</span>
            <span class="com">//xyzcorp/schedules/2015Aug24_Aug28/Jim_Hawkins</span>
            <span class="pln">
http</span>
            <span class="pun">:</span>
            <span class="com">//xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones</span>
            <span class="pln">
http</span>
            <span class="pun">:</span>
            <span class="com">//xyzcorp/schedules/2015Aug24_Aug28/John_Silver</span>
          </code>
        </pre>
        <p>These particular URLs would all contain the exact same content (the "2015Aug24_Aug28" page), but would highlight all instances of the name tagged on to the end. For example, "                    <code>http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones</code>

" would show every instance of the name "Billy Bones" highlighted, as if a "Find" for that name was executed on the page via the browser.</p>
        <p>I imagine something like this is required, client-side:</p>
        <pre style="" class="default prettyprint prettyprinted">
          <code>
            <span class="kwd">var</span>
            <span class="pln"> employee </span>
            <span class="pun">=</span>
            <span class="pln"> getLastURLPortion</span>
            <span class="pun">();</span>
            <span class="pln"></span>
            <span class="com">// return "Billy_Bones" (or whatever)</span>
            <span class="pln">
employee </span>
            <span class="pun">=</span>
            <span class="pln"> humanifyTheName</span>
            <span class="pun">(</span>
            <span class="pln">employee</span>
            <span class="pun">);</span>
            <span class="pln"></span>
            <span class="com">// replaces underscores with spaces, so that it's "Billy Bones" (etc.)</span>
            <span class="pln"></span>
            <span class="typ">Highlight</span>
            <span class="pun">(</span>
            <span class="pln">employee</span>
            <span class="pun">);</span>
            <span class="pln"></span>
            <span class="com">// this I have no clue how to do</span>
          </code>
        </pre>
        <p>Can this be done in HTML/CSS, or is JavaScript or jQuery also required for this?</p>
      </div>

Demo: http://plnkr.co/edit/rhfqzWThLTu9ccBb1Amy?p=preview

manji
  • 45,615
  • 4
  • 87
  • 100