How to build a fast, simple list filter with jQuery
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:
- Add an input to the header
- Set up events to check when someone is typing in the input
- Check the value of the input, and compare it to the countries in the list
- 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!
80 comments
This would be interesting as hell in a select drop down list.
There’s a minor error in your examples. In the two examples that show the listFilter() function slideDown() is used instead of slideUp() for the not-condition.
Ryan953: Thanks, I’ve fixed that.
In Step 3: Compare the values, the first code example is missing quotation marks around the a:contains.
Nice jquery widget :) Very useful. Here is another way to go about it:
(function ($) {
// custom css expression for a case-insensitive contains()
jQuery.expr[':'].Contains = function(a,i,m){
return (a.textContent || a.innerText || “”).toUpperCase().indexOf(m[3].toUpperCase())>=0;
};
$(function () {
$(”).appendTo(‘#header’);
$(‘input.filterinput’).keyup( function () {
var $val = $(this).val();
$(‘#list a:not(:Contains(“‘ + $val + ‘”))’).parent().slideUp();
$(‘#list “a:Contains(“‘ + $val + ‘”)’).parent().slideDown();
});
});
}(jQuery));
oops, it deleted the HTML
(function ($) {
// custom css expression for a case-insensitive contains()
jQuery.expr[':'].Contains = function(a,i,m){
return (a.textContent || a.innerText || “”).toUpperCase().indexOf(m[3].toUpperCase())>=0;
};
$(function () {
$(‘<form class=”filterform” action=”#”><input class=”filterinput”></form>’).appendTo(‘#header’);
$(‘input.filterinput’).keyup( function () {
var $val = $(this).val();
$(‘#list a:not(:Contains(“‘ + $val + ‘”))’).parent().slideUp();
$(‘#list “a:Contains(“‘ + $val + ‘”)’).parent().slideDown();
});
});
}(jQuery));
Shoot, don’t you always think of something else just after you post!
Your original code bound the event handler to the change event and then had the keyup handler trigger the change event. you could also just bind to both events at the same time:
.bind(‘keyup change’, function () {
On a side note: Even though it looks like an efficient filter (except for loading jQuery for all this), you should cache the list and it’s values instead of walking the DOM each time you press a key for speed performance perposes.
Also: where’s the edit button? Geez, my previous post is full of typo’s ;)
Crosspost of a comment I left on hacker news:
There are a couple of optimizations. You can cache the list, by instead of doing this:
You can do this:
You can get rid of most of the $(), since input, list, etc are jQuery objects already (I left them in because it makes it easier to read and doesn’t have too big an impact)
Lastly, you could save all countries in an array, give all list items unique id’s corresponding to the array, match on the array and hide/show the id’s that way. You forgo all the DOM interaction until the last moment.
That is an awesome Script. Keep up the good work. :)
Interesting. This reminds me a lot of Digital Inferno’s LiveFilter
http://www.digitalinferno.net/blog/jquery-plugin-livefilter-1-1/
That one also handles tables, but yours has pretty animation. Perhaps the two can be combined…
What a fabulous and easy control! Thanks.
[...] How to build a fast, simple list filter with jQuery • Javascript • Kilian Valkhof (tags: jquery list javascript tutorial webdev ajax) [...]
This is great. Thats for sharing.
Hmm, it seems that you ripped your case insensitive :Contains verbatim directly from my plugin. I’m not pissed but it would be nice to get a little shout out instead of you taking credit for it.
[...] How to build a fast, simple list filter with jQuery [...]
How to build a fast, simple list filter with jQuery…
This article shows you how little code is needed to add this in a fast, progressively-enhanced way using jQuery….
Thanks for this awesome tutorial, I’ve implemented it on my site that lists tons of brands in an ul
[...] List Filter – Mit jQuery Aufzählungsliste durchsuchen [...]
It is very useful for me.
Thank you.
is there a way to exclude some entries from the filtering ?
maybe excluding a class from of a item ??
[...] and send them gifts/facebook-punch them..or whatever. Therefore, I am trying to implement a simple Jquery filter using this piece of code that manipulates with the [...]
Man you rock!!! I didn’t know about jquery awesome ‘contains’ selector until I read this article.
[...] ordinare e filtarere gli elementi di una lista html. Nelle mie ricerche mi sono imbattuto in questo blog, ma la soluzione proposta aveva diversi bug e [...]
thanks for the blog entry… helped a lot
Do not work with “(” .
But how do you avoid problems when pressing enter inside the input field?
Oh.. Thank you..
Finally I found it ..
In IE8, I get an error when I type a parentheses.
Looks like it is a bug in jQuery for parsing the parntheses. A work-around is to not run the filter when a parentheses is typed in the input:
if(filter && !filter.match(/(\(|\))/)){
//hide stuff here
}
This is very helpful piece of code…clean and simple…Thanks a ton…
Hi, Very nice code, very understandable !
made it integrated withing my already-made search box within 3 minutes.
Thanks !
Hi, thanks for this code—
I’ve implemented this as a search within a video library of about 650 videos. It works but when I type in the search value it is super slow. If I type in “money” for example it only lets me type in the first couple of letters, then it pauses for a while to search the list. Not very usable this way.
What I would really like is a way to type the full keyword, then click a search button to initiate the search…
I’m not a javascript person, however. Can the script be easily modified to function this way? Help, anyone?
Nice;
Do you have a real example using an actual SELECT and INPUT TEXT instead? or perhaps, I am in the wrong blog!? I go here looking for a functionality to search a huge SELECT with some auto completion type of behavior.
Thanks!
Just found your site last night and am enthused to give the filter script a whirl. In our case, we have two choice lists, A and B. If user makes a choice in A, we want it to filter B accordingly. We’ll report back. Thank you so much.
Also, I really like your progressive presentation style.
This is awesome.. Thanks much
This helped me soooooooo much, I was about to try do this in a very very complicated way but yours is so simple, so THANK YOU!!
[...] you shouldn't need to change this). I had a very quick look for a jQuery list filter, and found this tutorial – that might be a good place to start? Reply With Quote [...]
Hey there,
Was just wondering if was possible to have multiple textboxes for filtering out results?
Dude!! you have given ten fold of knowledge here!!! i got open up to jquery ,someting i dint know about, and this $() in javascript which am still processing.
One question, how can one load a string to the input and get it to filter… i tried this
i put an id to the text field like this,
input = $(“”).attr({“class”:”filterinput”,”type”:”text”,”id”:”mytext”});
i added this
function text(){
document.getElementById(‘mytext’).value=’aus’;
}
then put a button
so when you click the button it loads the text to the textfield and filters.
PROBLEM:
This script gets the Value “aus” to the text field but it doesnt filter!
i tried loads of combination of script but it doesnt work.
am missing something but i dont know what… PLEASE HELP!!!
the button code dint appear in the above code
input type=”button” value=”Add New Text” onClick=”addtext();”
Thanks in advance
[...] သဘောအကျဆုံးကတော့ Kilian Valkhof ရဲ့ Article ပဲဖြစ်ပါတယ်။ [...]
How about adding button clear the text field to reset the list?
[...] 041. How to build a fast, simple list filter with jQuery [...]
When using this with ~1000 items it become painfully slow. Is there any way I could fix this, or am I simply asking too much of the browser?
This list doesnt work on my website… it changes every time from:
function listFilter(header, list) { // header is any element, list is an unordered list
// create and add the filter form to the header
var form = $(“”).attr({“class”:”filterform”,”action”:”#”}),
input = $(“”).attr({“class”:”filterinput”,”type”:”text”});
$(form).append(input).appendTo(header);
to this:
function listFilter(header, list) { // header is any element, list is an unordered list
// create and add the filter form to the header
var form = $(“This form is inoperational! “).attr({“class”:”filterform”,”action”:”#”}),
input = $(“”).attr({“class”:”filterinput”,”type”:”text”});
$(form).append(input).appendTo(header);
can anybody help me out please ?!
thanks man!
[...] of this from scratch using the jQuery documentation, but a big shout-out goes to Kilian Valkhof’s article, which helped me organize a lot of what I [...]
Very handy little script I love it!
Too good! Thank you!
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?
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.
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/
Learned alot from this really interesting post. Thanks
Sweetness, thanks!
Is there a way to evaluate when 0 results match the filter text, in order to display a custom message?
Sweetness, thanks!
Is there a way to evaluate when 0 results match the filter text, in order to display a custom message?
Awesome! Is there a way to even filter thrue hidden . For example for a long menu collapsible menu?
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?
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
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.
[...] 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 [...]
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!
[...] 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 [...]
I get “TypeError: m is undefined”
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?
Ugh, third edit. Jquery 1.8.0 fails, but 1.8.2 works. Great script!
[...] /* IE9 + IE10pp4 */ http://rafael.adm.br/css_browser_selector/ css hack http://kilianvalkhof.com/2010/javascript/how-to-build-a-fast-simple-list-filter-with-jquery/ http://web.koesbong.com/2011/01/24/sortable-and-editable-to-do-list-using-html5s-localstorage/ [...]
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?
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.
Hi, this is great. Is it possible to filter multiple lis’ like #list1, #list2, #list3? Thanks heaps
Any chance to modify a bit, to allow hidden text search? eg: http://jquerymobile.com/test/docs/lists/lists-search-filtertext.html
Ya, that is very nice. When I found the similar one here
DOC: http://antguider.blogspot.com/2012/07/jquery-list-filter.html
DEMO: http://antguider.co.nf/demos/ListFilter/index.html
Your code appears to have been cross-posted with no attribution that I can find here:
http://papermashup.com/jquery-list-filtering/
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);
}
This is helpful, I have a huge list of elements, that I needed to filter. The Jquery is easy to follow. Great work.
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/
Thanks for the post . This is so cool…..
it’s working good for the number search also . Thanks again…