Search code examples
javascriptjquerydomsearchlivesearch

What is the efficient way to create a live search using javascript or jquery?


I was making a live search for more than 10000 rows of the dataset. I have mentioned the available dom structure. Although I try to make a live search check every result after a single input, my browser is getting hang. Is there any other efficient way that I can reduce its complexity.

<label class="label">
    <input type="checkbox" name="123" value="">
</label>
<label class="label">
    <input type="checkbox" name="123" value="   General AUX"> General AUX
</label>
<label class="label">
    <input type="checkbox" name="123" value=" Annser"> Annser
</label>
<label class="label">
    <input type="checkbox" name="123" value=" LRIPL"> LRIPL
</label>
<label class="label">
    <input type="checkbox" name="123" value=" Soy Impulse"> Soy Impulse
</label>

** Yes, live search against the DOM** JS code, that I am using for live search

 $(".form-container #filter").keyup(function() {
 var filter = $(this).val(),
                    count = 0;
                if(filter.length>=2){
                // Loop through the comment list
                $(".label").each(function() {

                    // If the list item does not contain the text phrase fade it out
                    if ($(this).text().search(new RegExp(filter, "i")) < 0) {
                        $(this).fadeOut();

                        // Show the list item if the phrase matches and increase the count by 1
                    } else {
                        $(this).show();
                        count++;
                        }
                 });
                // Update the count
                var numberItems = count;
                // $(".popopup-header").text(count + " results");
                //$("#filter-count").text(count + "results"); 
              }
            });

Solution

  • There are three different ways you can improve the performance of a live DOM search. (I'm going to take the inherent performance issue with rendering 10000 DOM rows as a given; this answer will only cover the search and its results.) Small details such as using .indexOf() instead of a regexp will help as well, but I'd guess that sort of fine detail is not really your bottleneck.

    Make the search faster

    Live-searching the DOM is always going to be much, much slower than searching against a simple data object. I'm going to guess that this is, by far, the biggest performance bottleneck you currently have.

    It looks like you're only matching against a single string per row, which makes things easier. If you can depend on the order of your rows to never change, then you could get away with searching against a simple array of those strings, and just use the array index to indicate which DOM rows you'll hide or reveal (more about that later) -- but if the row order may change, you'll need to include at least an ID for each so you can match the string to the correct row. So the simplest case might be

    var search = function(searchString) {
    var searchableRows = ["General AUX", "Ansser", "Etcetera", ...]
    var matchedSearch = [];
    for (var i=0; i<searchableRows.length; i++) {
      if (searchableRows[i].indexOf(searchString) > -1) {
        matchedSearch[i]=1;
      }
    }
    // you'll use matchedSearch[] later to control which rows are visible.
    

    Run the search less often

    Instead of running the search on every user keystroke, you can debounce the input to guarantee at least n milliseconds between individual searches. Some frameworks have debounce functionality built in, but it's pretty simple to roll your own. The canonical drop-in example is probably this from David Walsh, which I cannot improve on:

    // Returns a function, that, as long as it continues to be invoked, will not
    // be triggered. The function will be called after it stops being called for
    // N milliseconds. If `immediate` is passed, trigger the function on the
    // leading edge, instead of the trailing.
    function debounce(func, wait, immediate) {
        var timeout;
        return function() {
            var context = this, args = arguments;
            var later = function() {
                timeout = null;
                if (!immediate) func.apply(context, args);
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) func.apply(context, args);
        };
    };
    

    Render the results more quickly

    One big DOM operation is less expensive than lots of little DOM operations. And the less you have to change the DOM to get the results you want, the better.

    So the simple approach of incrementally hiding or removing DOM rows during the search -- that's out for sure. The two approaches I can think of to handle this are very different, and honestly I'm not sure which would have better performance; which is better may come down to other factors such as how complex the HTML needs to be and whether it has js bindings you need to preserve, how much of it you're comfortable generating within javascript, etc.

    So strategy 1: generate the results html as one big string and then drop that into the DOM to replace the original HTML in a single operation:

    //assuming you have a filled matchedSearch array from above:
    var searchResults = "";
    for (var i=0; i<searchableRows.length; i++) {
      if (matchedSearch[i]) {
        searchResults = searchResults + '<label>...'+searchableRows[i]+'</label'>;
      }
    }
    document.getElementById('ResultsLocation').innerHTML(searchResults);
    

    Or strategy 2 is to take the opposite approach: render the full list once, and minimize how much you change it after the fact for each search. Again, this would be after you've finished generating your matchedSearch array:

    var allLabels = $('.label'); // I'm being lazy and depending on jQuery in this example
    for (var i=0; i<allLabels.length; i++) {
      if (matchedSearch[i]) {
        allLabels[i].removeClass('hidden');
      } else {
        allLabels[i].addClass('hidden');
      }
    }
    

    (There are other possible optimizations in how you display this -- I note you're using .fadeOut() currently; off the top of my head I'm not certain if that's slower than using a CSS class-based animation, but it'd be worth checking. With this many rows you might consider omitting unnecessary visual flourishes anyway.)