3
\$\begingroup\$

Scenario

Currently filtering through a list of ~3100 rows of data presented to the end-user in the form of an index (think, list of car models).

This is done in such a way that every item is available in a long list to the end-user however they have the possibility to filter down their results into something more relevant to them.


I have gone through three variations of my filtering functionality which has greatly improved, however the fact that my jQuery skills lack experience, was wondering whether or not my implementation can be improved in the following ways:

  1. Readability
  2. Performance

var lis = $('#some-list .some-entry');
var list = $.makeArray(lis.map(function(k, v) {
    return $(v).text().toLowerCase();
}));

var filterDelay = (function() {
    var timer = 0;
    return function(callback, ms) {
        clearTimeout(timer);
        timer = setTimeout(callback, ms);
    };
})();

$('#filter').keyup(function() {
    var userInput = $(this).val();

    filterDelay(function() {
        var count = 0;
        lis.each(function(index, value) {
            if (list[index].indexOf(userInput) >= 0) {
                count++;
                $(value).show();
            } else {
                $(value).hide();
            }
        });

        if (count == 1) {
            $("#filter-results").text(count + " Result");
        } else {
            $("#filter-results").text(count + " Results");
        }
    }, 250);    
});

The reason for the callback functionality is so that jQuery/JS does not start filtering through ~3100 rows of data every time the user presses a key. Instead, we want to wait for 250ms and then fall back to our filtering functionality afterwards.

This greatly helps the user-experience and is overall less stressing on the browser.


Question

My previous examples used toggle(bool) rather than show() or hide() to add or remove a row from the list (when filtered). Are there any inherent differences in terms of performance between the two?

Additionally, I am looking to implement a loading animation when:

  1. The user is typing
  2. The filter has starting filtering

And the removing it when the filtering is complete. Thus, would it make sense to toggle the loading animation, or add/remove it from the DOM respectively?

Toggle it off would mean that the animation is still occurring in the background regardless whether or not any filtering is being made. Adding it or removing to the DOM however will have its own performance drop.

What would one recommend? I don't want to make it seem like it's for micro-optimization purposes but simply looking to implement my filter functionality in a neater way for possible future re-use.


Update(s)

  • Added load animations by combining Bootstrap and jQuery like so:

    $('#filter').keyup(function() {
        //...
        $('#filter-icon-load').removeClass('hidden');
        //...
        $('#filter-icon-load').addClass('hidden');
        //...       
    });
    
\$\endgroup\$
1
\$\begingroup\$

The root of the problem is you have ~3100 items in the page. This could be simplified by using pagination. If this is a traditional page, page the output. If this is a webapp, you may have all ~3100 in memory, but don't render it all. Besides, there's no point rendering ~3100 items unfiltered anyways. Who scrolls through ~3100 items?

Granted that you don't have any other option but to render it all and filter on the spot, the next problem with your code is that it generates a lot of jQuery objects. Every time you call the $() function, a jQuery object is generated.

// Here, the $(v)
var list = $.makeArray(lis.map(function(k, v) {
    return $(v).text().toLowerCase();
}));

// Here, the $(value)
lis.each(function(index, value) {
    if (list[index].indexOf(userInput) >= 0) {
        count++;
        $(value).show();
    } else {
        $(value).hide();
    }
});

One way to avoid this is to collectively do the show and hide instead of doing it per item. In the following code, it gathers all elements to show and to hide, wraps both arrays in jQuery and calls show and hide.

var toShow = [];
var toHide = [];
lis.each(function(index, value){
  if (list[index].indexOf(userInput) >= 0) {
    toShow.push(this);
  } else {
    toHide.push(this);
  }
});

$(toShow).show();
$(toHide).hide();

Notice that I only called $() twice, and .show() and .hide() once instead of per item. We just avoided creating ~3100 jQuery objects!

Another way to improve this code is to use native operations whenever possible. It's not that they're faster by themselves, but you reduce reliance to jQuery, avoiding creation of jQuery objects when ordinary arrays would have sufficed.

var list = $.makeArray(lis.map(function(k, v) {
  return $(v).text().toLowerCase();
}));

This code above could have been simplified into

var list = lis.get().map(function(element){
  return (element.innerText || element.textContent).toLowerCase();
});

In the "improved version", notice that 1) it uses a native array instead of jQuery in the first place, allowing the use of the native array.map and 2) it doesn't call $() in the callback at all. We just avoided ~3100 jQuery objects again!

Now back to your toggling code, I think I see an update you did before my answer by adding/removing classes. That's a good move because 1) styling should be done in CSS and 2) show and hide uses inline styles. They are hard to override from a stylesheet. The only way you could is to use !important which you should generally avoid.

As for your question about removal vs hiding, I'd say go for hiding. As far as I know, removals are slow. This is even worse when there are stuff attached to the elements in question. For instance, removing an element with jQuery handlers attached. jQuery has to clean them up, remove references to data and handlers etc. If removals aren't done properly, you could have a lot of lingering things in memory.

\$\endgroup\$
  • \$\begingroup\$ Lovely response, Joseph. Thank you very much. Had in mind the idea to push to a respective array however I was not aware you can hide() or show() directly on an array and thought, "Why re-iterate through every item again anyway". Will be testing the suggestions out; regarding mixing jQuery and JS, I always thought it would be a better idea to stick to one or the other(?) so it's great to know it's not the case :-) \$\endgroup\$ – Juxhin Apr 14 '16 at 7:36
  • \$\begingroup\$ @Juxhin Re: the array of elements, it's documented here: api.jquery.com/jquery/#jQuery-elementArray . \$\endgroup\$ – Joseph Apr 14 '16 at 7:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.