How to build a simple search filter with jQuery

There are occasions when developing a web application where you’d like to give users the ability to search or filter the information presented.  For example, a web application may feature a page listing your DVD collection, and you want the users to find the movie they’re looking for quickly and easily.  This article shows you how little code is need to achieve this functionality using jQuery.


jQuery

First we need to download the jQuery library file and reference it in our HTML page, alternatively you can reference copies of jQuery hosted on existing networks.

Either way, we need to reference it in our HTML:

<script src="http://code.jquery.com/jquery-1.4.2.min.js"></script>

HTML

The basic HTML you’ll need for this is simple.  Just a header and an unordered list.  Both elements have id’s so we can easily and uniquely identify them:

<h1 id="header">DVD Collection</h1>

<ul id="list">
  <li><a href="#">Batman</a></li>
  <li><a href="#">Ice Age</a></li>
  <li><a href="#">Star Trek (2009)</a></li>
  <li><a href="#">Tremors</a></li>
  …
<ul>

With the HTML ready, here is the functionality we need to implement:

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

Step 1: Add an input field to the header

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

function listFilter(header, list) {
  …
}

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

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

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

Using $(“form”) we can create new elements, and by inserting the input field into the form using append(). Use the appendTo() function to add the form and input field to the header tag, and we’ve easily added the form to the page.  The classes have also been defined so they can be styled easily in CSS.

Step 2: Set up events

To check for text entered into the input field, we can use the Change event.  This is triggered every time the value of the input has changed, but only when you exit the input field.

However, this doesn’t work with a letter-for-letter filter.  For that, we need to watch the Keyup event as well.  We use Keyup instead of Keydown because it is triggered after you release the key, and so the value of the input field includes the just-typed letter.  With Keydown, the event is triggered before the letter is added-as your pressing the key-and therefore JavaScript cannot ascertain the value.

As we’ll be using two events, Change and Keyup, there is no need to double the work, we simply call the Change event when we receive a Keyup event:

Function ListFilter(Header, List) {
  …
  $(Input).Change( Function () {
    …
  }).Keyup( Function () {
    // call the above change event after every letter typed
    $(This).Change();
  });
  …
}

Step 3: Compare the values

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

function listFilter(header, list) {
  …
  $(input).change( function () {
    var filter = $(this).val();
    // get the value of the input field so we can filter the results

    $(list).find("a:contains(" + filter + ")").parent().slideDown();
  });
  …
}

The code above works like this: within the list, which is the <ul> element, it finds and shows all the <a> elements that contain the value in the input field.  It then selects the parents of those <a> elements, the <li>’s.  Those <li>’s are then displayed.

Step 4: Hide the non-matching elements

However, since we filter the list, we need to also hide all the <li>’s that do not contain the value in the input field.  For this jQuery offers the :not() selector, and we can wrap that around the :contains() selector like this:

function listFilter(header, list) {
  …
  $(input).change( function () {
    var filter = $(this).val();
    // get the value of the input field so we can filter the results

    $(list).find("a:not(:contains(" + filter + "))").parent().slideUp();
    $(list).find("a:contains(" + filter + ")").parent().slideDown();
  });
  …
}

Now, with each keystroke, we check the list for matching elements and hide the ones that don’t match.

So far so good, but what happens when you clear the value in the input field?  Then $(this).val(); will be empty, and nothing matches with empty, so all the list items will be hidden.  Luckily this can be remedied quite easily by checking if $(this).val(); has any value, and if it doesn’t then show 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 the input field now filters down the list, and clearing the field shows the entire-non filtered-list.

You would’ve thought that’s all we need, however :contains() is case-sensitive, so if we filter with the word “madagascar”, we won’t find Madagascar.

jQuery allows you to add your own selector expressions, and we can use that to build our own case-insensitive contains filter:

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 our selectors that has the same functionality as :contains(), but it first converts the text value to uppercase and then does the comparison.  Once this is added to the JavaScript, all we need to do is replace the :contains() with :Contains() within our listFilter function.

Demo

I’ve created two simple examples, the first ones uses the unordered list as described in this tutorial, and the second has the same functionality but uses the <div> elements instead.

Simple jQuery filter – unordered list

Simple jQuery filter – div tags

Complete Code

And finally the entire code used in this tutorial:

<script src="http://code.jquery.com/jquery-1.4.2.min.js"></script> 
<script> 
	(function ($) {
	  jQuery.expr[':'].Contains = function(a,i,m){
		  return (a.textContent || a.innerText || "").toUpperCase().indexOf(m[3].toUpperCase())>=0;
	  };
	 
	  function listFilter(header, list) {
		var form = $("<form>").attr({"class":"filterform","action":"#"}),
			input = $("<input>").attr({"class":"filterinput","type":"text"});
		$(form).append(input).appendTo(header);
	 
		$(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();
			}
			return false;
		  })
		.keyup( function () {
			$(this).change();
		});
	  }
	
	  $(function () {
		listFilter($("#header"), $("#list"));
	  });
	}(jQuery));
</script> 
 
</head> 
<body> 
<div id="wrap"> 
    <h1 id="header">DVD Collection</h1> 
    <ul id="list"> 
        <li><a href="#">Batman</a></li> 
        <li><a href="#">Ice Age</a></li> 
        <li><a href="#">Star Trek (2009)</a></li> 
        <li><a href="#">Tremors</a></li> 
    </ul> 
</div> 

37 comments

  1. Wonderful tutorial with step by step clear explanation. In this tutorial you have given list statically…
    Now I have added list with checkbox dynamically from XML….My complete coding is below….

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

    function listFilter(header, list) {
    var form = $(“”).attr({“class”:”filterform”,”action”:”#”}),
    input = $(“”).attr({“class”:”filterinput”,”type”:”text”});
    $(form).append(input).appendTo(header);

    $(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();
    }
    return false;
    })
    .keyup( function () {
    $(this).change();
    });
    }

    $(function () {
    listFilter($(“#header”), $(“#list”));
    });
    }(jQuery));

    function show()
    {

    if (window.XMLHttpRequest)
    {
    xmlhttp=new XMLHttpRequest();
    }
    else
    {
    xmlhttp=new ActiveXObject(“Microsoft.XMLHTTP”);
    }
    xmlhttp.open(“GET”,”test.xml”,false);
    xmlhttp.send();
    xmlDoc=xmlhttp.responseXML;
    var x=xmlDoc.getElementsByTagName(“value”);

    for (i=0;i<x.length;i++)
    {
    var name=x[i].getElementsByTagName("name")[0].childNodes[0].nodeValue;
    var list = document.createElement( "li" );
    var link = document.createElement( "a" );
    var cb = document.createElement( "input" );
    cb.type = "checkbox";
    cb.id = "c1";
    cb.value = name;
    cb.checked = false;
    var text = document.createTextNode( name );
    document.getElementById( 'list' ).appendChild( list );
    document.getElementById( 'list' ).appendChild( cb );
    document.getElementById( 'list' ).appendChild( link );
    document.getElementById( 'list' ).appendChild( text );

    }
    }

    DVD Collection

    In this coding, the list is displayed from XML properly…But i search in search filter, all the names(list items) are slides up….display empty…..What i did wrong here….please help me….

  2. Very Nice! I’m trying to adapt it to retrieve employees from our server (I don’t know what I’m doing…)

    list: ko.observableArray([]),

    OData.read(_serviceURL + “/EmployeeEnt?”,
    function (data) {
    viewModel.empList.removeAll();
    $.each(data.results, function (index, item) {
    viewModel.empList.push(ko.mapping.fromJS(item));
    });
    });

    (function ($) {
    jQuery.expr[':'].Contains = function (a, i, m) {…….

  3. Great script mate, would this work with a database,

    thinking that if the information from the database is loaded up into the page it would work fairly brillantly. aiming for something like the facebook friend search. clueless on the serverside coding though

    Cheers for the awesome script

    1. Yep, don’t see why it wouldn’t work with a database. As long as the query from the db is rendered on the web page, JQuery can then be used to filter the results. Having said that though you’d probably want to filter the content before making a call to the db, especially if you have a lot of records to show. AJAX would be suited better for this.

  4. Hello, I´m not well oriented in jQuery FW. I´d like to ask, if I have more fields in one row (name,address,etc.), how can I make it work using SELECT form, where I choose area for filtration (for example address)? Is it possible to choose .class according which I can filtrate?

    Thank You!

  5. Hi, very useful, thank you.

    I wanted to ask if you can add highlight to the filtered text? so it will highlight and filter at the same time.

    Thanks.

  6. Finally, an easy to understand, relevant and to the point article.
    I never comment on articles, usually because I don’t hang around long enough to finish them—my attentional wanes. But this article actually made sense is useful.

    Thanks!

  7. Would there be a way with this to replace the search bar with check boxes/dropdowns that could be used to filter the search results?

  8. Thank you so much for such a great article. I am on a journey of expanding this even further, for a list of images with anchor links with title attributes. Would it be too much trouble to add a small section to this blog on detecting if the title value of anchors (which are wrapped around images), have the filter value or not? As appose to just an $(“a”) selector? Thank you so much and sorry for the trouble!

  9. thnks for your code. it working fine bt i want to check with first letter of my list. for ex if i have mumbai,goa,ambarnath,etc… for m it should display mumbai only not mumbai and ambarnath. I tried a lot bt modifying code not working.

    1. I would suggest to use a different JQuery selector instead of Contains, perhaps the StartsWith attribute

  10. Try using:

    “`$(list).find(‘a:not([text^=' + filter + '])’).parent().slideUp();
    $(list).find(‘a:[text^=' + filter + ']‘).parent().slideDown();“`

  11. Congratulations for your nice site and content!!!
    This is a very nice, simple and working example that works and can be easily implemented on any site..I use it in my codeigniter apps for easy menu filtering ..
    Thanks a lot!!!

  12. I based a company directory on this. Thank you. However, I cannot figure out how to join the text field filtering with my dropdown list of department names filter.

    So if I type “Sarah” in the input, then choose “Human Resources” from the dropdown, I’d like to see only those Sarah’s in the Human Resources Department.

    Independently, my dropdown and input work, but they won’t collaborate.

    Here’s my dropdown script:

    $(document).on(‘change’, ‘#departments’, function(e) {
    var selDept = (this.options[e.target.selectedIndex].text);
    if (selDept) {
    $(list).find(“.person:not(:contains(” + selDept + “))”).closest(“li.person”).attr({“style”:”display:none”});
    $(list).find(“.person:contains(” + selDept + “)”).closest(“li.person”).removeAttr(“style”);
    }
    });

    Any insight and help greatly appreciated.
    Thx
    CT

    1. I got it, we can directly here in the attr.

      var form = $(“”).attr({“class”:”filterform”,”action”:”#”}),
      input = $(“”).attr({“class”:”filterinput”,”type”:”text”,”placeholder”:”search here..”});
      $(form).append(input).appendTo(header);

  13. Nice tutorial.. bt i m using yii2 framework … i have checkbox list which is generated dynamically by the query… now i want to add a search to it… can you plz help me in it?… thanx in advance…

  14. Hi,
    This works great and I would love to add this to a project I am working on. However, Something is messed up and the search box continues to be appended to the header. How do I fix this so that I don’t continue to have the input box added to the page? How would I just put the input box on the page and then refer to that box rather than appending i? Here is the code:

    $.expr[':'].Contains = function(array, index, matchcase){
    return (array.textContent || array.innerText || “”).toUpperCase().indexOf(matchcase[3].toUpperCase())>=0;
    };
    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”,”placeholder”:”search for charts/guidelines”});
    $(form).append(input).appendTo(header);

    // To check for text in the input -> use .change event fires every time the value of the input changes
    // watch the keyup event for letter-for-letter filter – use keyup instead of keydown bc it fires after you press the key which will include the just-typed keyup
    // with keydown, the event gets fired before the letter is added so javascript won’t pick it up in the value of the input

    $(input).change(function(){
    // compare and check values
    var filter = $(this).val(); // get the value of the input which we filter
    if(filter){
    $(list).find(“a:not(:Contains(” + filter + “))”).parent().slideUp(); //hide li’s that don’t contain the value
    $(list).find(“a:Contains(” + filter + “)”).parent().slideDown();
    } else { //check if $(this).val(); has any value, and if not, show everything:
    $(list).find(“li”).slideDown();
    }
    return false;

    }).keyup(function(){
    // fire the above change event after every letter
    $(this).change();
    });
    }

  15. Hi I love yr tutorials so nice, pls how can I insert this to my webpage ,ie which of the scripts goes to the HTML header, and which one goes to the body am new to this . thank u

Leave a Reply to Jeff Frick Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>