Thursday, January 22, 2009

Beware jquery's :nth-child selector

The past day or two I've been using selectors like this to manipulate tabular data:
  • $("table#summary tr:nth-child("+k+")")
I was using the selectors to pump a table of audit data into a summary table, and the whole process was 5-6 seconds for under 50 rows. There were lots of ":contains" statements in there, so naturally I attacked those first, caching the results in .data() fields. Yet most of the lag time remained.
So I cranked on the Firebug profiler and profiled the function that does the heavy lifting. Calls to nth-child were taking up 4700ms out of about 5000ms total!
After staring at it for a minute, I recalled that :nth-child looks for all elements that are the nth child of their parent -- that's what I want, right? -- but it does not assume that they are direct descendants of the preceding element in the selection chain.
"Matches all elements that are the nth-child of their parent ... While :eq(index) matches only a single element, this matches more than one ..."
This is certainly what you need in some cases, but if your jquery consists of a set of siblings and you just want to grab a particular element by index, it's far quicker to do one of the following:
  • $("table#summary tr:eq("+k+")")
  • $("table#summary tr").eq(k)
Once I made that change, Firebug's Profiler timing went from 5000ms+ down to the 300-400ms range. Lesson learned.




Monday, January 19, 2009

Searching tables with jquery

Suppose you have an html table like the one below (name, favorite color) and you want to grab all the favorite colors.

NameFavorite Color
Joeblue
Sallygreen
Mikeyellow
Ziggyorange

Here are some jquery plugin functions that let you do it:

/** find the index of this node among its siblings **/
jQuery.fn.childn = function() { return 1 + jQuery(this).parent().find("> *").index(this); }

/** given a table row, find the cell value under the supplied heading **/
jQuery.fn.column = function(heading) {
    var idx = jQuery(this).parents("table:first").find("th:contains("+heading+")").childn();
    return jQuery(this).find("> *:nth-child("+idx+")");
}

Example:

$("table tbody tr").column("Favorite Color");

Update: use :eq instead of :nth-child! See my follow-up post for details.