Reputation: 6622
I am trying to create a text search function but I am having a hard time getting it to work when there is html inside the element. Here is some simple html to demonstrate my problem.
<b>
<input type="checkbox" value="" />
I need replaced
</b>
And here is where I am currently at for javascript. It works great assuming there is no html inside.
$("*", search_container).each(function() {
var replaceTxt = $(this).text().replace(new RegExp("(" + search_term + ")", 'i'), '<span style="color: #0095DA;" class="textSearchFound">$1</span>');
$(this).text().replaceWith(replaceTxt);
});
As the user is typing I need to replace the text with the span. So the content should look like the following as he/she types.
<b>
<input type="checkbox" value="" />
<span style="color: #0095DA" class="textSearchFound">I need</span> replaced
</b>
UPDATE
After looking over the question that was voted a duplicate I came up with this. Which although may be close, it inserts the html into the DOM as text which I do not want. Please vote to reopen this.
$("*", search_container).each(function() {
var node = $(this).get(0);
var childs = node.childNodes;
for(var inc = 0; inc < childs.length; inc++) {
//Text node
if(childs[inc].nodeType == 3){
if(childs[inc].textContent) {
childs[inc].textContent = childs[inc].textContent.replace(new RegExp("(" + search_term + ")", 'i'), '<span style="color: #0095DA;" class="textSearchFound">$1</span>');
}
//IE
else {
childs[inc].nodeValue = childs[inc].nodeValue.replace(new RegExp("(" + search_term + ")", 'i'), '<span style="color: #0095DA;" class="textSearchFound">$1</span>');
}
}
}
});
Upvotes: 6
Views: 2118
Reputation: 21482
You can use the JQuery .contents()
method to get the child nodes of an element. The text nodes will have nodeType == 3
. You can then get the text of a text node using its textContent
property.
var getNonEmptyChildTextNodes = function($element) {
return $element.contents().filter(function() {
return (this.nodeType == 3) && (this.textContent.trim() != '');
});
};
var $textNode = getNonEmptyChildTextNodes($('b')).first(),
text = $textNode[0].textContent;
Then to perform the replacement, you can do the following:
var search_term = 'I need',
regExp = new RegExp(search_term, 'gi');
var result = regExp.exec(text);
if (result !== null) {
var $elements = $(),
startIndex = 0;
while (result !== null) {
$elements = $elements.add(document.createTextNode(text.slice(startIndex, result.index)));
$elements = $elements.add('<span style="color: #0095DA;" class="textSearchFound">' + result[0] + '</span>');
startIndex = regExp.lastIndex;
result = regExp.exec(text);
}
$elements = $elements.add(document.createTextNode(text.slice(startIndex)));
$textNode.replaceWith($elements);
}
Here is a jsfiddle showing the complete search & wrap using the code above.
Upvotes: 1
Reputation: 4261
It's not pretty, but the best way to do this would be to loop through every node on the page recursively. When you come across a text node, check if it contains your search string. If you find it, delete the original text node and replace it with a text node of the text before the match, a new element with the highlighted match, and a text node of what's after the match.
Here's an example function:
var highlightSomeText = function(needle, node){
node = node || document.body; // initialize the first node, start at the body
if(node.nodeName == 'SCRIPT') return; // don't mess with script tags
if(node.nodeType == 3){ // if node type is text, search for our needle
var parts = node.nodeValue.split(needle); // split text by our needle
if(parts.length > 1){ // if we found our needle
var parent = node.parentNode
for(var i = 0, l = parts.length; i < l;){
var newText = document.createTextNode(parts[i++]); // create a new text node, increment i
parent.insertBefore(newText, node);
if(i != l){ // if we're not on the last part, insert a new span element for our highlighted text
var newSpan = document.createElement('span');
newSpan.className = 'textSearchFound';
newSpan.style.color = '#0095DA';
newSpan.innerHTML = needle;
parent.insertBefore(newSpan, node);
}
}
parent.removeChild(node); // delete the original text node
}
}
for(var i = node.childNodes.length; i--;) // loop through all child nodes
highlightSomeText(needle, node.childNodes[i]);
};
highlightSomeText('I need');
And a demo on jsfiddle: http://jsfiddle.net/8Mpqe/
Edit: Here is an updated method which uses Regexp to be case-insensitive: http://jsfiddle.net/RPuRG/
Updated lines:
var parts = node.nodeValue.split(new RegExp('(' + needle + ')','i'));
Using a regular expression which is wrapped in a capturing group ()
the results of the capture will be spliced into the array of parts.
newSpan.innerHTML = parts[i++];
The text for the new span element will be the next part in our array.
Edit 2: Since this function is being called on every keyup event of some text field, the asker wanted to remove the highlighted span tags before each run. To do this, it was necessary to unwrap each highlighted element, then to call normalize
on the parent node to merge adjacent text nodes back together. Here's a jQuery example:
$('.textSearchFound').each(function(i, el){
$(el).contents().unwrap().
parent()[0].normalize();
});
And here's the completed working demo: http://jsfiddle.net/xrL3F/
Upvotes: 6