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

Posted September 25, 2009 at 10:07 AM by Ben Nadel

Tags: Javascript / DHTML

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

Oct 29, 2009 at 6:29 PM // reply »
1 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?


Oct 30, 2009 at 7:52 AM // reply »
11,243 Comments

@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.


Nov 16, 2009 at 1:05 AM // reply »
2 Comments

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


Nov 16, 2009 at 7:55 AM // reply »
11,243 Comments

@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.


Nov 16, 2009 at 8:53 PM // reply »
2 Comments

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?


Nov 17, 2009 at 2:19 PM // reply »
11,243 Comments

@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.


Sep 11, 2010 at 10:49 AM // reply »
1 Comments

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?


Sep 12, 2010 at 6:58 AM // reply »
11,243 Comments

@Jean,

jQuery doesn't have access to your file system.


Oct 1, 2011 at 10:30 AM // reply »
1 Comments

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!


ned
Oct 14, 2011 at 10:09 PM // reply »
1 Comments

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


Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 23, 2013 at 3:55 AM
Javascript Array Methods: Unshift(), Shift(), Push(), And Pop()
very interesting and helpful too. ... read »
May 22, 2013 at 5:35 PM
Script Tags, jQuery, And Html(), Text() And Contents()
This is still an issue 2 years later. jQuery is supposed to remediate these cross browser issues, no? I have been unable to find any statement from the jQuery team calling this behavior "by de ... read »
May 22, 2013 at 12:44 PM
Ask Ben: Query Loop Inside CFScript Tags
In cf10, if you call a function that has: local.result = {}; local.result.msg = ""; local.svc = new query(); local.svc.setSQL("SELECT * FROM..."); local.obj = local.svc.exe ... read »
May 22, 2013 at 12:29 PM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben: What version of Java are you using? Also, did you test users.id to see what Java reports as the data type? I wonder if it's not a Java primitive data type, but getting returned as something ... read »
May 22, 2013 at 11:47 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Dana, Awesome - so it looks like this bug was fixed in ColdFusion 10. Thanks so much for double-checking that. ... read »
May 22, 2013 at 11:37 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
When I c&p and run on cf10, I get: Selected User IDs: 1,4 User 1 selected: YES - YES User 2 selected: NO - NO User 3 selected: NO - NO User 4 selected: YES - YES User 5 selected: NO - ... read »
May 22, 2013 at 11:27 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Tom, Good thought, but no dice. Both of these still exhibit the same behavior: users.id[ users.currentRow ] users[ "id" ][ users.currentRow ] It's just something whacky happening with ... read »
May 22, 2013 at 11:07 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
Could your problem be that "users.id" is actually an ARRAY, not a single value? Perhaps try it again with "users.id[1]" (I only have CF8 here at work). ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools