Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at BFusion / BFLEX 2009 (Bloomington, Indiana) with:

jQuery's Filter() Method Can Take A Callback Function For Advanced Filtering

Posted by Ben Nadel

I think one of the most awesome tips that I picked up while reading Cody Lindley's jQuery Enlightenment book was that the filter() method can take a callback function as its argument. Typically, when I use jQuery's filter() method, I simply pass in a selector that it would then use to narrow down the collection of DOM elements:

  • var items = $( "li" );
  • var visibleItems = items.filter( ":visible" );

In this tiny example, I am collecting all of the list items on the page; then, from that collection, I am creating a subsequent collection by filtering down to just the visible list items using the filter() method and the ":visible" pseudo selector.

The filter() method is awesome and has made life easy; but, knowing that the filter() method can accept a callback function for use with advanced filtering just made life some kind of exciting. Previously, if I needed to carry out advanced selecting, I might have created a custom jQuery pseudo selector; but, the filter() method just feels so much more straightforward and easy to use.

 
 
 
 
 
 
 
 
 
 

The callback function that you pass to the filter() method works, like most other jQuery callbacks, in the context of the current DOM element; meaning that within the callback function, the "this" keyword refers to the DOM element in question. When jQuery executes the callback functions, it passes in the index of the current DOM element (within the contextual collection) as the first argument:

  • $( "...." ).filter(
  • function( index ){
  • return( true | false );
  • }
  • );

Your callback function carries out the actual filtering by returning a boolean value. If you return true, the DOM element in question is included in the resultant collection; if you return false, the DOM element in question is excluded from the resultant collection.

To see this in action, I set up a small demo in which we can rate photos and then filter the list of photos based on a target rating:

  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>jQuery's Filter Method Used With A Callback</title>
  • <style type="text/css">
  •  
  • ul {
  • list-style-type: none ;
  • margin: 0px 0px 0px 0px ;
  • padding: 0px 0px 0px 0px ;
  • }
  •  
  • li {
  • border: 1px solid #999999 ;
  • float: left ;
  • height: 190px ;
  • margin: 0px 20px 5px 0px ;
  • padding: 5px 5px 5px 5px ;
  • }
  •  
  • </style>
  • <script type="text/javascript" src="../jquery-1.3.2.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready, populate the list.
  • $(function(){
  • var list = $( "ul" );
  • var listData = [];
  • var listTemplate = list.html();
  •  
  • // Create a list item for each of our photos.
  • for (var i = 1 ; i <= 6 ; i++){
  •  
  • // Create a list item.
  • listData.push(
  • listTemplate.replace( "$$id$$", i )
  • );
  •  
  • }
  •  
  • // Now that our array contains the raw HTML of our
  • // photo list, let's join that into a single chunk
  • // of HTML and inject it as the contents of our UL
  • // list.
  • list.html( listData.join( "" ) );
  • });
  •  
  •  
  • // When the DOM is ready, initialize the forms and
  • // the event listeners.
  • $(function(){
  • var targetRating = $( "form select:first" );
  • var list = $( "ul" );
  • var listItems = list.find( "> li" );
  •  
  • // I update the display based on teh selected rating.
  • var updateDisplay = function(){
  • // First, show all list items. Once all items have
  • // been shows, we will then hide the ones that do
  • // NOT match the current rating.
  • listItems.show();
  •  
  • // Now, check to see if we need to hid some of the
  • // list items. This will happen if the target
  • // rating has been selected (and is not zero).
  • if (parseInt( targetRating.val() )){
  •  
  • // Get all photos that don't match this rating.
  • // In order to do that, our filter() method is
  • // going to take a callback that checks the
  • // given photo's rating against the target
  • // rating the user selected.
  •  
  • listItems
  • .filter(function(){
  • var item = $( this );
  • var rating = item.find( "select" ).val();
  •  
  • // Return TRUE if this rating does NOT
  • // match the target rating.
  • return( rating != targetRating.val() );
  • })
  • .hide()
  • ;
  •  
  • }
  • }
  •  
  • // Now that we have our display update method defind,
  • // let's hook up each select box to trigger that
  • // method such that the display is always fresh.
  • $( "select" )
  • .change( updateDisplay )
  • .keyup( updateDisplay )
  • ;
  •  
  • // Call update display just to make sure we starte
  • // off with an accurate display.
  • updateDisplay();
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • jQuery's Filter Method Used With A Callback
  • </h1>
  •  
  • <form>
  •  
  • <p>
  • Please select rating:
  •  
  • <select>
  • <option value="0">Any</option>
  • <option value="1">1</option>
  • <option value="2">2</option>
  • <option value="3">3</option>
  • <option value="4">4</option>
  • <option value="5">5</option>
  • </select>
  • </p>
  •  
  •  
  • <h2>
  • The Photos
  • </h2>
  •  
  • <ul>
  • <!-- BEGIN: Template - will be replaced. -->
  • <li>
  • <img src="./$$id$$.jpg" width="125" /><br />
  •  
  • Rating:
  • <select>
  • <option value="1">1</option>
  • <option value="2">2</option>
  • <option value="3" selected="true">3</option>
  • <option value="4">4</option>
  • <option value="5">5</option>
  • </select>
  • </li>
  • <!-- END: Template - will be replaced. -->
  • </ul>
  •  
  • </form>
  •  
  • </body>
  • </html>

As you can see in the code above, there is one Select box at the top and one Select box associated with each photo. When any of the select boxes are changed, the display needs to be updated in order to keep the visible photos in sync with the target rating. To do so, our display method takes the list of photos and then filters them down, using the jQuery filter() method and a callback function, such that only photos with an inappropriate rating are hidden:

  • // Get all photos that don't match this rating.
  • // In order to do that, our filter() method is
  • // going to take a callback that checks the
  • // given photo's rating against the target
  • // rating the user selected.
  •  
  • listItems
  • .filter(function(){
  • var item = $( this );
  • var rating = item.find( "select" ).val();
  •  
  • // Return TRUE if this rating does NOT
  • // match the target rating.
  • return( rating != targetRating.val() );
  • })
  • .hide()
  • ;

If you ask me, this is totally awesome! This is one of those jQuery features that definitely changes the way you think about attacking display problems.




Reader Comments

Great screencast! I was wondering...what if I have multiple criteria to filter for? So let's say, I wanted to filter by rating AND location (for example) with a second drop down option...

How would I go about doing this?

Reply to this Comment

@Adam,

The .filter() method simply has to return TRUE or FALSE. How you go about doing that is up to you. As such, you can easily compare more than a single drop down box within the filter() method and use multiple values to derive the true/false value you return.

Reply to this Comment

can i use this trick for double dropdown? or filter a second dropdown based on the selected item on the first dropdown? thx.

Reply to this Comment

@Neophyte,

Absolutely - the execution of the filter callback gives you the current DOM node context and the index; within that method, you can relate that to another select box in any way you like.

Reply to this Comment

thanks. we use hibernate and spring and jstl to populate the dropdown list. what we need to do is to select the cities (second dropdown) based on the selected state on the first dropdown. how do i go about doing that?

Reply to this Comment

@the_finisher,

For something like that, you're probably going to want to bind to the change() event on the select box and then make a subsequent call to the server for data.

Unless, are you pre-populating the OPTION list with ALL the cities? Or just the ones for the select city. It really depends on what is available currently on the page.

Reply to this Comment

If possible - does Jquery have the ability to read the # of images within your folder so that it could assemble an unordered list of the images within your page?

Reply to this Comment

Ben, this is really awesome--exactly what I was looking for. Google searches often bring me to you, and I'm grateful you're putting such great examples on the web.

Thank you!

Reply to this Comment

i am not even a coder but seems ima learn jquery :)) .. i got that idea of multiply filtering
(probably will ask stupidest question or non sense question but i gotta try its 5 am :)

3 levels filtering :
1. I(niche:carpets), II(location:uk), III(method:offline sale)
2. 1(persian carpets) , 2(london-park-17th-street-100001) ,3(no need of option here)
3. custom filtering for the niche-item:
-BLUE colored persian carpet
-sexy chick drawn on persian carpet

So its like lot of filters on the line probably should do separate internal filtering so i skip this in the main steps but i wonder if this is possible and also can it use this trackback function eventually :?? so when something change/new stuff appear it gets fixed...

I am really just asking in theory i doubt all this complication has any use right now but since i brainstormed whole night.. i had to ask :)
thx in advance for your reading & excuse me for my bad english.
Ned

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.