Reputation: 356
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');
}
});
Upvotes: 7
Views: 566
Reputation: 523
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.
Upvotes: 1
Reputation: 8018
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
Upvotes: 2
Reputation: 2661
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>
Upvotes: 3
Reputation: 109
if your code works, just add such a similar row of code:
this.nodeValue.wrap( "<span class='new'></span>" );
Upvotes: 2
Reputation: 42054
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');
Upvotes: 1
Reputation: 206525
$.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 <b> 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)
Upvotes: 2