How to build a fast, simple list filter with jQuery

Javascript, 5 April 2010, 80 comments

Sometimes you want users of your web-app to quickly filter a list down. For example, a web-app I’m currently working on features a page listing two dozen countries, and I want users to find the country they are looking for as fast as possible. This article shows you how little code is needed to add this in a fast, progressively-enhanced way using jQuery.

Demo

If you’re the impatient type, I’ve set up a simple demo: A simple list filter with jQuery.

The HTML

The basic HTML you need for this is simple: just a header and a simple unordered list. They both have id’s so we can easily and uniquely find them:

<h1 id="header">List of countries</h1>

<ul id="list">
  <li><a href="/australia">Australia</a>&lt/li>
  <li><a href="/austria">Austria</a>&lt/li>
  <li><a href="/belgium">Belgium</a>&lt/li>
  …
<ul>

With the HTML ready, here is what the filter function needs to do:

  1. Add an input to the header
  2. Set up events to check when someone is typing in the input
  3. Check the value of the input, and compare it to the countries in the list
  4. Hide the non-matching ones, while showing the matching ones

Step 1: add an input to the header

We’ll start with a simple function, we’ll call it listFilter:

function listFilter(header, list) {
  …
}

As you see, the function takes two arguments. We’ll use jQuery to select the aforementioned header and list and give them as arguments to our function. Next, we need to create a form with an input in it, and append it to the header:

function listFilter(header, list) {
  // create and add the filter form to the header
  var form = $("<form>").attr({"class":"filterform","action":"#"}),
      input = $("<input>").attr({"class":"filterinput","type":"text"});

  $(form).append(input).appendTo(header);
  …
}

Using $(“<form>”) (with brackets) we can create new elements, and by inserting the input into the form using append(), and then using the appendTo() function to add the form (with the input in it) to the header, we easily add it into the page. For easy of use I’ve given both the form and the input classes, so you can style them easily.

That’s step one done, but it doesn’t do anything yet, for that we need to…

Step 2: set up events

To check for text in the input, we can use the change event. This fires every time the value of the input has changed, but only when you exit the input. However, this doesn’t work with a letter-for-letter filter. For that, we need to watch the keyup event a well. We use keyup instead of keydown because it fires after you pressed the key, and so the value of the input then includes the just-typed letter. With keydown, the event gets fired before the letter is added, so JavaScript can’t pick it up in the value of the input.

While using two events, there is no need to do double work. When we receive a keyup event, we just fire a change event:

function listFilter(header, list) {
  …
  $(input).change( function () {
    …
  }).keyup( function () {
    // fire the above change event after every letter
    $(this).change();
  });
  …
}

We’re now all set to start with the actual checking!

Step 3: compare the values

jQuery has a way to check for matched elements easily: the :contains() selector. Using this, you can very easily select all element containing the text between the parenthesis:

function listFilter(header, list) {
  …
  $(input).change( function () {
    var filter = $(this).val(); // get the value of the input, which we filter on
    $(list).find("a:contains(" + filter + ")").parent().slideDown();
  });
  …
}

The code above works like this: in the list, which is the <ul> element. it finds all the shows all the <a> elements that contain the value in the input. It then selects the parents of those <a> elements, the <li>‘s. Those li’s then get shown. However, since we filter the list, we need to also hide all the li’s that do not contain that value. For this, jQuery offers the :not() selector, and we can just wrap that around the :contains() selector, like so:

function listFilter(header, list) {
  …
  $(input).change( function () {
    var filter = $(this).val(); // get the value of the input, which we filter on
    $(list).find("a:not(:contains(" + filter + "))").parent().slideUp();
    $(list).find("a:contains(" + filter + ")").parent().slideDown();
  });
  …
}

There, now with each keystroke, we check the list for matching elements, and hide the ones that don’t match. Easy-peasy.

But wait. What happens when you clear the input? Then $(this).val(); will be empty, and nothing matches with empty. So all list items will be hidden, the exact opposite of what we want. This can be remedied by just checking if $(this).val(); has any value, and if not, just showing everything:

function listFilter(header, list) {
  …
  $(input).change( function () {
    var filter = $(this).val();
    if (filter) {
      $(list).find("a:not(:contains(" + filter + "))").parent().slideUp();
      $(list).find("a:contains(" + filter + ")").parent().slideDown();
    } else {
      $(list).find("li").slideDown();
    }
  });
  …
}

Typing in letters now filters down the list, and removing them all shows the entire, now unfiltered, list again. Is that all we need? Well, It would be, except that :contains() is case-sensitive. so if you filter with “austria”, you won’t find Austria!

Building a case-insensitive :contains()

jQuery allows you to add your own selector expressions, and we can use that to build our own contains filter that’s a bit more sensible:

jQuery.expr[':'].Contains = function(a,i,m){
    return (a.textContent || a.innerText || "").toUpperCase().indexOf(m[3].toUpperCase())>=0;
};

This adds a :Contains() (note the uppercase) option to your selectors that does the same as :contains(), but it first converts all text to uppercase, and then compares them. So once we add this to your JavaScript, all we have to do in our listFilter function is replace :contains() with Contains() and we have a fast, simple, progressively-enhanced way to filter down a large list!

Demo

If you want to browse through the entire, documented code, and see it in action, check out the example page: A simple list filter with jQuery.

Please let me know in the comments what you would use this for, and please let me know your remarks and questions!