Kilian Valkhof

Front-end & user experience developer, Jedi.

How to build a fast, simple list filter with jQuery

Javascript, 5 April 2010

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></li>
  <li><a href="/austria">Austria</a></li>
  <li><a href="/belgium">Belgium</a></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!

Thanks for Reading!

I am Kilian Valkhof, a front-end and user experience developer from the Netherlands.
Contact me or ping me on twitter.

  1. Dave

    Very handy little script I love it!

  2. Dipaks

    Too good! Thank you!

  3. That is a great script, but it breaks, if the filter variable is empty:
    “Uncaught Error: Syntax error, unrecognized expression: )”

    Something I’m doing wrong?

  4. For those of you having issues with the filter’s speed with big lists all you have to do is change from using slideUp() and slideDown() to using hide() and show() and it’s dramatically faster. With big lists that’s just asking jQuery too much if you want it to nicely animate hiding the elements. It’s nearly instantaneous if you use hide and show instead. Now that I’ve discovered that I might even switch back to letting it filter on keyup, the other way it was abusive to the user. I have nearly 1000 items in my list (that’s why I needed filtering in the first place.

  5. James

    There seems to be a bug on webkit but im not sure if its your code or something else. If I display the list elements as inline, when their shown after being hidden the inspect element in my browser tells me their inline (as they should be) but they behave like block level elements. Im getting this bug on Chrome and Safari but not Firefox.

    Here’s a demo. Cheers
    http://smartpeopletalkfast.co.uk/filter/

  6. Learned alot from this really interesting post. Thanks

  7. Jason

    Sweetness, thanks!

    Is there a way to evaluate when 0 results match the filter text, in order to display a custom message?

  8. Jason

    Sweetness, thanks!

    Is there a way to evaluate when 0 results match the filter text, in order to display a custom message?

  9. Awesome! Is there a way to even filter thrue hidden . For example for a long menu collapsible menu?

  10. Alvin

    How to fix the bug if there’s space between Name and Surname?

    For example: Hello World.

    It cannot search for the word “Hello World” if i type “Hello W”

    how to fix this?

  11. Thanks , this was exactly what I was looking for. I Borrowed it, and threw in this line at the end of the jscript:

    //Made by Kilian Valkhof – Front-End developer, user Experienced designer & Jedi. Visit http://kilianvalkhof.com

    Hope its ok.

    Bye

  12. Jason

    Hey, this is exactly what I was looking for so thanks for the tutorial!
    I was having an issue with the re-population of items once the filter was reset to blank. Perhaps it is just me encountering the issue because I am using a table, rather than links in a list. I had the word “jason” and “test” in the table. I noticed that when I typed “j”, the “test” value disappeared, as expected. When I delete the “j”, the “test” value did not return. I figured out that if the first typed letter, made an entry disappear, then that value would never return, unless you type a letter that in that disappeared entry. So in this case, “j” is not in “test”, so it disappeared and didn’t come back after I cleared the filter field.

    The solution was in this line:
    $(list).find(“li”).slideDown();

    which I changed to:
    $(list).find(“li”).parent().slideDown();

    Now it works… Just wanted to post in case somebody else comes across the same issue.

  13. […] region codes and then have the ability to filter that list as you type, similar to this example: How to build a fast, simple list filter with jQuery Is there a way to use this method of filter-as-you-type but with an external text file of some […]

  14. Galen

    Thanks for this! However, when trying to implement it myself, the case insensitivity was not working. I noticed that in step 3 :contains is not capitalized, while in some cases it is. In case anyone was having this problem, to fix it, just keep your capitalization consistent!

  15. […] the nav-list – more or less what this does: http://kilianvalkhof.com/2010/javascript/how-to-build-a-fast-simple-list-filter-with-jquery/ – while it works nicely on this site, I can’t get it to filter elements in nav-list in […]

  16. mike

    I get “TypeError: m is undefined”

  17. mike

    Sorry I guess I needed jquery 1.4.2 to make this work. I am using jquery 1.8.0, but apparently your script is not compatible? or are you using the jquery core-ui?

  18. mike

    Ugh, third edit. Jquery 1.8.0 fails, but 1.8.2 works. Great script!

  19. Rikki Prince

    Thank you, this has come very much in handy!

    However, can you explain why you add the field programmatically? It seems that you could just put this in the HTML and then reference it by id in the Javascript?

  20. I add the field via Javascript, because if you add it in the HTML and javascript isn’t available, then you have a field that does nothing.

  21. uds

    Hi, this is great. Is it possible to filter multiple lis’ like #list1, #list2, #list3? Thanks heaps

  22. OL

    Any chance to modify a bit, to allow hidden text search? eg: http://jquerymobile.com/test/docs/lists/lists-search-filtertext.html

  23. JMK
  24. Michael C.

    Your code appears to have been cross-posted with no attribution that I can find here:
    http://papermashup.com/jquery-list-filtering/

  25. Leszek

    Hi,
    How to implement timer to cancel change event if somebody type fast in search input? Dont want to trigger change() on every letter.
    I tried this, but does not wokr as excepted.
    .keyup( function () {
    console.log(‘Keyup’);
    // fire the above change event after every letter
    if (!currentTimer) {
    console.log(‘timer: ‘ + currentTimer);
    currentTimer = true;
    setTimeout(function() {
    if (currentTimer) {
    currentTimer = false;
    console.log(‘timer: ‘ + currentTimer + ‘, wywolanie funkcji change()’);
    $(this).change();
    // Do ajax call here
    }
    }, 500);
    }

  26. This is helpful, I have a huge list of elements, that I needed to filter. The Jquery is easy to follow. Great work.

  27. Uds

    Hi, I tried this on my list but it word pretty wired. When I search, it filters fine, but when I remove the search term, it doesnt slide Down and the hidden lis are hidden until you refresh.

    Would you mind giving me your thoughts, I have a working example here : http://jsfiddle.net/vesa9/1/

  28. vani

    Thanks for the post . This is so cool…..

  29. vani

    it’s working good for the number search also . Thanks again…