Reputation: 2577
I have made a function which highlights matching words in a div. but if there are two identical words separated by a different word then only the first word is highlight. so for example if the search criterion was the word "burn", and in the text was the sentence "burn baby burn", I would want it to highlight both "burn"'s. this jsFiddle demonstrates how it only highlights the first "burn". Here is the code below also.
if ($('#search').val().length !== 0) {
$('.searchable').each(function() {
$(this).html($(this).html().replace($('#search').val(), "<span class = 'highlight'>" + $('#search').val() + "</span>"));
});
}
.highlight {
font-weight: bold;
color: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<input id = "search" type ="text" value = "burn">
<div class="searchable">burn baby burn</div>
Upvotes: 4
Views: 21417
Reputation: 6073
The existing answers focus on innerHTML manipulation. It's possible to do entirely with DOM APIs:
function highlight(term) {
// Select all Elements. There is no method to query all Nodes directly.
for (const element of document.querySelectorAll("*")) {
// Skip element if it doesn't contain target string
if (!element.textContent.includes(term)) continue;
// Traverse each node by inspecting the childNodes of every element
for (const child of element.childNodes) {
// Skip node if its not a text node
if (child.nodeType !== Node.TEXT_NODE) continue;
// Skip node if it doesnt contain our target string
if (!child.textContent.includes(term)) continue;
// We want to break up the contents into chunks
// of text which don't need to be highlighted
// You can get creative here if you want
// to use more term advanced matching
const textNodes = child.data.split(term).map(substr => {
return document.createTextNode(substr);
});
// We don't want to modify the overall structure of the DOM
// a fragment lets us group multiple nodes together without
// bundling it under another new node
const fragment = document.createDocumentFragment();
// Populate the new fragment
for (const node of textNodes) {
// Add every text node to the new node
fragment.appendChild(node);
// Interweave the term in between them within a span
const el = document.createElement("span")
el.textContent = term;
// Add some styling
el.style.backgroundColor = 'yellow';
fragment.appendChild(el);
}
// Remove extra term that was
// generated the interweaving
fragment.removeChild(fragment.lastChild);
// Swap current child node for new fragment
element.replaceChild(fragment, child);
}
}
}
// highlight a word
highlight("JavaScript")
Upvotes: 0
Reputation: 1479
Use regular expression with :
Do not forget to escape special characters
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
const SEARCH_VALUE = $("#search").val().trim();
if (SEARCH_VALUE.length > 0) {
$('.searchable').each(function() {
const DIV_TEXT_CONTENT = $('.searchable').text();
const SPLIT_CONTENT = DIV_TEXT_CONTENT.split(" ");
SPLIT_CONTENT.forEach((word, index) => {
const SEARCH_REGEXP = new RegExp(escapeRegExp(SEARCH_VALUE), "gi")
if (SEARCH_REGEXP.test(word))
$(this).html($(this).html().replace(SEARCH_REGEXP, "<span class = 'highlight'>" + word + " </span>"));
});
})
}
function escapeRegExp(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
.highlight {
font-weight: bold;
color: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id="search" type="text" value="BuRn">
<div class="searchable">burn baby burn</div>
Upvotes: 0
Reputation:
Take care of regex special characters.
Maintain uppercase & lowercase.
So taking care of above things with case insensitive search (in most cases, this is desired; otherwise just remove "i"), here is the final code...
if ($('#search').val().length !== 0) {
$('.searchable').each(function() {
//Handle special characters used in regex
var searchregexp = new RegExp($("#search").val().replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), "gi");
//$& will maintain uppercase and lowercase characters.
$(this).html($(this).html().replace(searchregexp, "<span class = 'highlight'>$&</span>"));
});
}
Upvotes: 9
Reputation: 1857
Here's a working jsfiddle for you. Basically, get the text from the div, split it on space, loop through the words and see if one matches.
var term = $('#search').val().trim().toLowerCase();
if (term.length > 0) {
var source = $('.searchable').text();
var words = source.split(' ');
var output = '';
$.each(words, function(idx, word) {
if (term === word.toLowerCase()) {
output += '<span class="highlight">' + word + '</span> ';
} else {
output += word + ' ';
}
});
$('.searchable').html(output);
}
Upvotes: 1
Reputation: 3665
You can pass a regular expression into replace() instead of the string, with the g modifier to make replace perform a global match.
if($('#search').val().length !== 0){
$('.searchable').each(function(){
var search_value = $("#search").val();
var search_regexp = new RegExp(search_value, "g");
$(this).html($(this).html().replace(search_regexp,"<span class = 'highlight'>"+search_value+"</span>"));
});
}
Upvotes: 12