Traversal vs. Collection Filtering In jQuery

Posted February 17, 2010 at 9:50 AM by Ben Nadel

Tags: Javascript / DHTML

When it comes to jQuery selectors, I tend to think in two different modes: collection filtering and traversal filtering. By that, I mean that I see filtering as happening at two different and distinct times in the selection process. With collection filtering, jQuery filters nodes only once it has compiled them into a collection. All filtering done at that point is done in the context of the collection. With transversal filtering, on the other hand, jQuery filters nodes as it gathers them during DOM traversal. All filtering done at that point is done before a final collection has been established. This is my philosophical interpretation of what's happening - I don't truly know how this filtering is done at the technical level. That said, I have found this type of thinking to be very useful.

 
 
 
 
 
 
 
 
 
 

To demonstrate what I'm talking about, let's take a quick look at some HTML:

  • <table>
  • <tr>
  • <td>
  • <span>
  • <em>Hello</em>
  • </span>
  • </td>
  • <td>
  • <span>
  • <em>World</em>
  • </span>
  • </td>
  • </tr>
  • <tr>
  • <td>
  • <span>
  • <em>Hello</em>
  • </span>
  • </td>
  • <td>
  • <span>
  • <em>World</em>
  • </span>
  • </td>
  • </tr>
  • </table>

Given this HTML, imagine that I want to bold the EM element contained within the first SPAN of each table row (TR). Because I use the jQuery pseudo selector, ":first", so often, whenever I see the keyword "first", I tend to want to apply this filtering mechanism. As such, let's see what happens when I apply the ":first" selector to our SPAN element:

  • $( "tr span:first em" )
  • .css( "font-weight", "bold" )
  • ;

While this might sound like a good approach, when we run this selector, we get the following output:

Hello World
Hello World

As you can see, only the first EM in the table was bolded - not the first EM within each row. The mistake that we made here was using the wrong type of selector. The pseudo selector, ":first", is a collection filter; meaning, it filters nodes in the context of a collection, not in the context of DOM traversal. As such, jQuery collected all of the SPAN elements of each row into a single collection and then filtered them down to a single node.

What we need to do is use a traversal filter - one that filters in the context of the DOM. Unfortunately, jQuery does not provide any cousin-style traversal filtering (ie. "sibling" filtering across different parents). As such, we'll need to make our selector a bit more detailed. In this case, we'll need to add a TD selector that has, attached to it, some traversal filtering:

  • $( "tr td:first-child span em" )
  • .css( "font-weight", "bold" )
  • ;

As you can see, we've added the ":first-child" selector to the TD to contextualize the SPAN element. The pseudo selector, ":first-child", is a traversal filter that is applied in the context of the DOM. As such, the TD element is only filtered in the context of its parent TR, not in the context of the entire TD collection. In doing so, we have slightly altered the intent of our selector - making it dependent on the TD, not on the SPAN - but, we do end up with the desire output:

Hello World
Hello World

As you can see, the appropriate EM elements have been bolded. Only, it's no longer the EM contained within the first SPAN, it's the EM contained within the first TD. Like I said, we've had to slightly alter the intent of our selector; but, at least this demonstrates the difference in capabilities between collection and traversal filtering.

In most cases, it's not necessary to think too deeply about your jQuery selector choices; typically, they're so small that the difference between collection and transversal filtering becomes insignificant. But, in cases where your selectors get longer, the difference between these two modes of filtering becomes more relevant.



Reader Comments

Feb 17, 2010 at 12:32 PM // reply »
170 Comments

@Ben:

The problem I have with this entry is your two different selectors have completely different meanings.

In the first example you're not grouping by table cell at all, but the second one you are. Also, there's a difference between the behavior in :first and :first-child.

The :first filter grabs the first item in the collection of elements. The :first-child filter grabs the every match of the first one found.

I think this entry would be more clear if you used the following markup:

<div>
<div>
<span>
Hello
</span>
<span>
World
</span>
</div>
<div>
<span>
Hello
</span>
<span>
World
</span>
</div>
</div>

And then used selectors that are grabbing the same elements:

$( "div span:first em" )
.css( "font-weight", "bold" )
;

$( "div span:first-child em" )
.css( "background-color", "#ffcccc" )
;

(You can view the code here: http://jsbin.com/ozeke)

The main thing is understanding how the filters actually operate.


Feb 17, 2010 at 12:37 PM // reply »
11,238 Comments

@Dan,

Ha ha, Dan, sometimes I think we just completely miss each other on communication :) Yes, the two different selectors are completely different. I am not saying they do the same thing at all; in fact, I'm saying that they work at two different times and I even state that the intent of the second selector is not the same as the first:

In doing so, we have slightly altered the intent of our selector - making it dependent on the TD, not on the SPAN

I will try to make my language more clear in future posts.


Feb 17, 2010 at 1:57 PM // reply »
7 Comments

Excellent explanation about these two concepts of jQuery. Something more that I will need to keep in mind.


Feb 17, 2010 at 9:32 PM // reply »
4 Comments

hmm, I'm sure you're correct and all, but to me :first makes sense as only ever returning a single item, you can't have more than one 'first' thing in a collection, so my first instinct on looking at the example was something more like...

$('tr').each(function(){
$(this).find('span:first em').css("font-weight","bold");
});

...makes more sense to me in solving the problem as originally described, for each 'tr' we find the first spans em and bold it.

Probably not what you were trying to show though :)


Feb 17, 2010 at 9:56 PM // reply »
11,238 Comments

@Adam,

That will definitely work. I was only trying to demonstrate different ways to think about filtering; approaching this way is certainly a good way as well.


Jon
Feb 19, 2010 at 5:20 AM // reply »
1 Comments

Whilst we're skinning this cat:

$('tr').each(function(){
$('em',$(this)).first().css("fontweight","bold");
});


Feb 19, 2010 at 9:46 AM // reply »
170 Comments

@Jon:

Instead of:
$('em',$(this)).first().css("fontweight","bold");

You're better off using:
$('em', this).first().css("fontweight","bold");

- or -

$(this).find('em').first().css("fontweight","bold");

There's some overhead in calling $(), so if you can minimize the usage, the better off you'll be.

I personally like the find() method--I think it's more readable.


Feb 22, 2010 at 8:43 PM // reply »
11,238 Comments

@Dan,

I agree re: find(). It's very left-to-right readable.


Nov 24, 2011 at 10:53 AM // reply »
1 Comments

I really like this 'philosophical' interpretation of the way different jQuery selectors are working.

With jQuery it's very easy to come up with a dozen different ways of achieving the same thing (as several of the comments demonstrate). The difficulty is finding the 'best' way to get the job done, i.e. what is the most efficient set of selectors to use to get to the element(s) you want?

Given the title of this article I was hoping to find some form of testing and results demonstrating which method would be the quickest in a given example.

I'm specifically interested in the performance difference between a contextualised $() selector statement vs. a chained filter method. e.g. $('.someClass', '#myEl') vs. $('#myEl').filter('.someClass')

Any ideas?


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 21, 2013 at 11:51 AM
Ask Ben: Parsing Very Large XML Documents In ColdFusion
Looking at my first ever XML document that I have to parse and put into MS SQL 2000 with CF8. I get it to list the desired Field name, many times over, and have a long list of this field name displa ... read »
May 21, 2013 at 9:25 AM
Turning Off and On Identity Column in SQL Server
you are awesome..i am lucky to get this blog between such a garbage one....Thanks, Prashant ... read »
May 20, 2013 at 4:38 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Dana, Your confusion is well founded, since this is a very confusing features. In fact, it ONLY works if you use array notation. Meaning, that this: arrayToList( query[ "columnName" ] ) ... read »
May 20, 2013 at 4:34 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
I was thinking chicken and the egg, I wouldn't have expected it to work in the valuelist going in I guess. Maybe I just need a beer, long day :) ... read »
May 20, 2013 at 4:29 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Dana, That's if you're trying to reference a specific row. In this case, we're trying to reference the entire query column as one cohesive value. So, you are correct that if you wanted to output a ... read »
May 20, 2013 at 4:24 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
I thought when you used array notation to reference queries you always had to have the row or it would throw a similar error as well? ... read »
May 20, 2013 at 11:45 AM
Using jQuery's Animate() Step Callback Function To Create Custom Animations
This is really useful. I found out that you don't actually have to use a dummy css property (surprisingly). To animate a property in a linear-gradient for instance I did this this.css('someLinearGra ... read »
May 20, 2013 at 10:51 AM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Josh, Oh snap! You're totally right! I'm not sure I've ever tried that. I did know that you can call a number of other array-methods on ColdFusion query columns: http://www.bennadel.com/blog/167 ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools