Search body for string and then style
Asked Answered
C

6

7

I need to search through a document with jQuery to find a particular word. It's actually a brand name that has to be bold and italic wherever used.

I can do this using :contain but only on an individual element basis. I need to be able to go through anchors, lists divs etc.

$( "a:contains('brand name')" ).html().replace('brand name'.... 

Any ideas would be appreciated.

Update: I have got this far which works and replaces everything on a page but I now need to wrap in a span with a class. So close but stumped on this one. Again an ideas would be appreciated.

    $("body *").contents().each(function() {
  if(this.nodeType==3){
    this.nodeValue = this.nodeValue.replace(/brandname/g, 'colour');

  }
});
Candidacandidacy answered 19/11, 2015 at 12:41 Comment(3)
$( ":contains('brand name')" ) will return all the elements.Allysonalma
It's actually a brand name that has to be bold and italic wherever used & I can do this using :contain but only on an individual element basis. I need to be able to go through anchors, lists divs etc. But wrapping e.g div in b element isn't valid HTML markup. Not really clear what you are expecting?Metatarsus
@A.Wolff - expecting the word and only the word 'BrandName' to be wrapped in a span in any instance. Not <span class="brand"><div>BrandName</div></span>, but <div><span class="brand">BrandName</span></div>Candidacandidacy
B
3

You can replace the textNodes with a document fragment. This allows you to group text and styled nodes and swap it with the existing textNode.

var x = 0;
$("body *").contents().each(function() {

   if (this.nodeType == 3 && this.parentElement.tagName != "SCRIPT") {

     var text = this.nodeValue;
     var i = 0, lastIndex = 0;
     var search = /brandname/ig;
     var result;
     var parent = this.parentNode;
     var textNode = null;
     var highlight = null;
     var textRange = "";
     var fragment = document.createDocumentFragment();

     // Do search
     while ((result = search.exec(text)) !== null) {
    
       // add plain text before match
       textRange = text.substring(lastIndex, result.index);
       if (textRange != '') {
         textNode = document.createTextNode(textRange);
         fragment.appendChild(textNode);
       }
       
       // add highlight elements
       highlight = document.createElement('span');
       highlight.innerHTML = result[0];
       highlight.className = "hi";
       fragment.appendChild(highlight);
       lastIndex = search.lastIndex;
     }

     // Add trailing text
     textRange = text.substring(lastIndex);
     if (textRange != '') {
       textNode = document.createTextNode(textRange);
       fragment.appendChild(textNode);
     }
     
     // Replace textNode with our text+highlight
     if(fragment.children.length > 0) {
       this.parentNode.replaceChild(fragment, this);
     }
   }

 });
span.hi {
  background-color: #FFCC00;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>
  Hello world this is my brandname. The BrAnDNAMe is regex matched and we can change subelements like "<a href=''>Brandname</a>." It should not replace <b>elements</b> that don't match.
</p>
Broad answered 2/12, 2015 at 11:17 Comment(0)
I
2

$.fn.ignore = function(sel){
  return this.clone().find(sel||">*").remove().end();
};

function rebrand(el, word){
  if (!word.length && !el) return;
  var $el = $(el), html = $el.ignore("script").html(),
      rgx = new RegExp("(?![^<]+>)("+ word +")", "g");
  $el.html( html.replace(rgx, "<span class='brand'>$1</span>") );
}

$(function() { // DOM ready

  // !!!  IMPORTANT: REBRAND FIRST !!!
  rebrand("body", "Brand Name");
  // so the following code will not break on lost events, data bindings etc...

  // Other DOM ready code here like:
  $("button").click(function(){ $(this).css({color:"red"}); });
  
});

// Other JS, jQ code here...
.brand{
    color: rgba(255,0, 100,0.4);
    font: italic normal bold 1em/1 sans-serif;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
  <h1>Title with Brand Name!</h1>
  <p>
    Change styles only to Brand Name!<br>
    but don't mess with <a href="#">links to Brand Name are cool</a><br>
    <button>Button with Brand Name works!</button>
  </p>
  <ul>
    <li>Other Brand</li><li>Brand Name</li><li>Brandy</li>
  </ul>
</div>
<div>
  <h2>Subtitle <i>and italic Brand Name</i></h2>
  <p>The HTML will not get messed cause of Brand Nameing<br>
  Paragraph with a &lt;b&gt; tag <b>bold Brand Name actually</b></p>
  <code>This is a code tag with a Brand Name :)</code>
</div>

Kudos to this two answers:
jQuery.ignore Plugin
Highlight Words (Ignoring names of HTML tags)

Illconsidered answered 26/11, 2015 at 0:26 Comment(0)
H
2

if your code works, just add such a similar row of code:

this.nodeValue.wrap( "<span class='new'></span>" );
Hibernaculum answered 1/12, 2015 at 16:41 Comment(0)
F
2
var rgx_BRAND = /(foo)/ig;
$("*")
    .contents()
    .filter(function() {
        return this.nodeType === Node.TEXT_NODE && this.textContent.search(rgx_BRAND) > -1;
    })
    .each(function(){
        var html = this.textContent.replace(rgx_BRAND, '<b class="-my-brand">$1</b>');
        $(this).before(html).remove();
    });

Hope this helps, Cheers

Finegrained answered 2/12, 2015 at 17:23 Comment(0)
B
1

My solution is a little different from the previous one but in any case always valid, I assume.

In the following my jQuery function:

function replaceBrand(brandName) {
    $('body').find(":not(iframe)").contents().filter(function(index, element) {
    if (element.nodeType == 3 && element.nodeValue.indexOf(brandName) != -1) {
        var newInnerValue = element.nodeValue.replace(brandName, '<div><span class="brand">' + brandName + '</span></div>');
        $(element).replaceWith(newInnerValue);
    }
});

}

replaceBrand('Brand Name', 'new brand');

Bolan answered 30/11, 2015 at 19:5 Comment(0)
F
1

Going from your snippet:

$(function() {
    var parent, needle = 'brandname',
        regex = new RegExp(needle, 'g');
    $("body *").contents().each(function() {
        if(this.nodeType==3 && ((parent = $(this.parentNode)).length > 0) {
            parent.html(parent.html().replace(
                regex, '<span class="brand-name">' + needle + '</span>'));
        }
    }
});

It may not be the most efficient approach but fairly easy and get's the job done in my tests. Looking up the parentNode property should work in all browsers.

Forgiven answered 2/12, 2015 at 19:27 Comment(1)
be careful, this will also replace any class names or other attribute values within the parent that match brandname. this is likely why nodeValue was used by the OP.Rosa

© 2022 - 2024 — McMap. All rights reserved.