I've written a custom Live Search & Highlight function in vanilla JS. It is working and does what I expect it to.
The issue is that the more items I add to the page content to search, the slower the search/highlight function becomes. I'm looking for speed/efficiency improvements so that it can handle larger data sets without lag.
Here's the main function in question (I have similar functions for different data sets with different filters and layouts):
function portsliveSearch() {
// get all card elements
var cards = document.querySelectorAll('table.type-port');
// get ports checkboxes
var protocols = document.getElementsByClassName('protocolcheckbox');
// get the search query input
var search_query = document.getElementById("portssearchbox").value;
search_query = escapeHtml(search_query);
//split query on spaces populate to an array
var search_query_array = [];
if (search_query.length !== 0) {
search_query_array = search_query.trim().toLowerCase().split(/\s+/);
}
var searchableContent = [];
// let's handle the protocols toggle first
var active_protocols_array = [];
// first let's see what's active
for (n = 0; n < protocols.length; n++) {
if (protocols[n].checked === true) {
active_protocols_array.push(protocols[n].value);
}
}
var all_checkbox = document.getElementById('protocolsallcheckbox');
// if the active item count does not equal the total item count
if (active_protocols_array.length !== protocols.length) {
// are all of them checked EXCEPT for "all"?
if (active_protocols_array.length === protocols.length-1) {
if (!active_protocols_array.includes("all")) {
// let's enable the "all toggle"
all_checkbox.checked = true;
active_protocols_array.push("all");
// else, it means all is selected, but something else is not
} else {
// let's disable the all toggle
all_checkbox.checked = false;
active_protocols_array.splice(0, 1);
}
}
}
// now if the active item count is 1, and the value is "all"
if (active_protocols_array.length === 1) {
if (active_protocols_array[0] === "all") {
// let's disable the all toggle
all_checkbox.checked = false;
active_protocols_array.pop();
}
}
// if the protocols are empty
if (active_protocols_array.length === 0) {
for (var i = 0; i < cards.length; i++) {
// hide the card
cards[i].classList.add("is-hidden");
}
// else if all protocols are checked and there's no search query
} else if (active_protocols_array.length === protocols.length && search_query_array.length === 0) {
for (var i = 0; i < cards.length; i++) {
// show the card
cards[i].classList.remove("is-hidden");
// get all searchable content areas in card
searchableContent = cards[i].getElementsByClassName("searchable-port");
// remove all current mark tags
for (var l = 0; l < searchableContent.length; l++) {
searchableContent[l].innerHTML = displayHtml(searchableContent[l].innerHTML).replaceAll("<mark>","").replaceAll("</mark>","");
}
}
// else if there's items in the search query, or if there are any protocols deselectedselected
} else if (search_query_array.length !== 0 || active_protocols_array.length !== protocols.length) {
// loop through all cards
for (var i = 0; i < cards.length; i++) {
// let's thin out the search by only processing cards that match the selected ports
var include_card = false;
for (n = 0; n < protocols.length; n++) {
if (protocols[n].checked) {
if (cards[i].classList.contains('protocol-'+protocols[n].value) ) {
include_card = true;
break;
}
}
}
if (include_card === true) {
// get all searchable content areas in card
searchableContent = cards[i].getElementsByClassName("searchable-port");
// remove all current mark tags
for (var l = 0; l < searchableContent.length; l++) {
searchableContent[l].innerHTML = displayHtml(searchableContent[l].innerHTML).replaceAll("<mark>","").replaceAll("</mark>","");
}
// set all string matches to false
var item_match = [];
for (var j = 0; j < search_query_array.length; j++) {
item_match[j] = false;
}
// loop through all searchable content
for (var k = 0; k < searchableContent.length; k++) {
// loop through search strings
for (var j = 0; j < search_query_array.length; j++) {
// set array index to true if there's a match
if (searchableContent[k].innerHTML.toLowerCase().includes(displayHtml(search_query_array[j]))) {
item_match[j] = true;
}
}
}
// now let's check if any search strings are false and hide the card if so
if (item_match.includes(false)) {
cards[i].classList.add("is-hidden");
} else {
cards[i].classList.remove("is-hidden");
// no let's mark the content in this card
for (var k = 0; k < searchableContent.length; k++) {
// loop through search terms input array
for (var j = 0; j < search_query_array.length; j++) {
// if search term is in the searchable content
if (searchableContent[k].innerHTML.toLowerCase().includes(displayHtml(search_query_array[j]))) {
//do_mark = true;
searchableContent[k].innerHTML = searchableContent[k].innerHTML.replace(new RegExp(displayHtml(search_query_array[j]), "gi"), (match) => {
return `<mark>${match}</mark>`;
});
}
}
}
}
// else protocol for this card is not included in search
} else {
// hide the card
cards[i].classList.add("is-hidden");
}
}
// else, no search string or no protocol filters
} else {
// loop through all cards
for (var i = 0; i < cards.length; i++) {
// get all searchable content areas in card
searchableContent = cards[i].getElementsByClassName("searchable-port");
// loop through searchable content within card
for (var l = 0; l < searchableContent.length; l++) {
// remove all current mark tags
searchableContent[l].innerHTML = displayHtml(searchableContent[l].innerHTML).replaceAll("<mark>","").replaceAll("</mark>","");
}
// set all cards to show
cards[i].classList.remove("is-hidden");
}
}
}
Here's a working JS Fiddle with about 1400 entries (3 searchable content areas per entry) in the dataset. Yes, it works, but notice how the first character or two or three of the search string has a lag until the visible content areas thins out a little. Same thing for deleting characters from the search string. The more characters you have (less content areas to search), the faster it is. Same thing with toggling the "All" checkbox. Disable all is quick but re-enabling all has a delay. Those are the things I'm looking to improve speed/efficiency for.
This is about the 3rd revision of the search/highlight function. The first ones were way too slow with dataset entries larger than about 500. I've broken down handling specific cases of empty search string and empty filters, empty search string but has filters, and has search string and filters to minimize the processing per loop. This version is much better than the previous ones. I want to add more entries (about 2,500 total), but need to speed up the processing of this live search/highlight function before I can justify doing it! Thank you in advance for any suggestions you can offer to optimize this search/highlight function.