16

It surprised me that Sizzle (the selector engine jQuery uses) comes with a built-in :nth-child() selector, but lacks an :nth-of-type() selector.

To illustrate the difference between :nth-child() and :nth-of-type() and to illustrate the problem, consider the following HTML document:

<!doctype html>
<html>
 <head>
  <meta charset="utf-8">
  <title>:nth-of-type() in Sizzle/jQuery?</title>
  <style>
   body p:nth-of-type(2n) { background: red; }
  </style>
 </head>
 <body>
  <p>The following CSS is applied to this document:</p>
  <pre>body p:nth-of-type(2n) { background: red; }</pre>
  <p>This is paragraph #1.</p>
  <p>This is paragraph #2. (Should be matched.)</p>
  <p>This is paragraph #3.</p>
  <p>This is paragraph #4. (Should be matched.)</p>
  <div>This is not a paragraph, but a <code>div</code>.</div>
  <p>This is paragraph #5.</p>
  <p>This is paragraph #6. (Should be matched.)</p>
  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
  <script>
   $(function() {
    // The following should give every second paragraph (those that had red backgrounds already after the CSS was applied) an orange background.
    // $('body p:nth-of-type(2n)').css('background', 'orange');
   });
  </script>
 </body>
</html>

Since Sizzle uses the browser-native querySelector() and querySelectorAll() methods if those are present (i.e. in browsers that already implement the Selectors API), stuff like $('body p:nth-child'); will of course work. It won’t work in older browsers though, because Sizzle has no fallback method for this selector.

Is it possible to easily add the :nth-of-type() selector to Sizzle, or to implement it in jQuery (by using the built-in :nth-child() selector, perhaps)? A custom selector with parameters would be nice.

Mathias Bynens
  • 144,855
  • 52
  • 216
  • 248
  • 2
    Not sure, but won't `$('p:even')` give you what you're looking for? You already have the selector (`p`), so you just need to filter it. – Kobi Jan 19 '10 at 13:19
  • 1
    @Kobi: It’s not that easy. The selector `p:nth-child(2n)` would match every second paragraph *in every parent element*. If there are two DIVs, both containing three paragraphs, the following paragraphs (in DOM order) would be matched by `p:nth-child(2n)`: #2, #5. See? It’s not just a matter of getting every `P` in the document and then filtering it down to every *mn*th elementh. Yes, `$('p:even')` is an alias for `$('p:nth-child(2n)')`, but not for `$('p:nth-of-type(2n)')`. Also, I’m using `2n` in this example, but of course other variations should be possible as well. – Mathias Bynens Jan 19 '10 at 13:28
  • Got it, and deleted my answer. – Kobi Jan 19 '10 at 13:32
  • `$('body p:nth-of-type(n)').css('background', 'orange');` is working in firefox here on jQuery 1.4, are you asking how to implement this strictly for older browsers, e.g. implement only the fallback version? – Nick Craver Jan 19 '10 at 22:36
  • 2
    Nick Craver, like I explained in my post, that’s because Firefox is one of the browsers with a native implementation of the Selectors API. Sizzle doesn’t know the `:nth-of-type()` selector, but Firefox’s `querySelectorAll()` does. That’s why it ‘works’ — but it’s not thanks to Sizzle. It would be good to have this in Sizzle because then it would work in **all** browsers. – Mathias Bynens Jan 20 '10 at 09:55
  • Sorry, can you elaborate on why $('body p:nth-child') does not work in older browsers? Because that's your only argument against using it, right? – littlegreen Jan 20 '10 at 16:59
  • littlegreen: `$('body p:nth-child')` works in every browser, because Sizzle has a built-in `:nth-child()` selector. So, if the browser doesn’t implement the Selectors API, Sizzle just uses that instead. The problem is Sizzle lacks an `:nth-of-type()` selector. – Mathias Bynens Jan 21 '10 at 09:56
  • 1
    Wouldn't you know it, it wasn't implemented simply because [John Resig didn't think it was worth implementing](http://ejohn.org/blog/selectors-that-people-actually-use). – BoltClock Mar 01 '12 at 07:26

3 Answers3

14
/**
 * Return true to include current element
 * Return false to exclude current element
 */
$.expr[':']['nth-of-type'] = function(elem, i, match) {
    if (match[3].indexOf("n") === -1) return i + 1 == match[3];
    var parts = match[3].split("+");
    return (i + 1 - (parts[1] || 0)) % parseInt(parts[0], 10) === 0;
};

Test case - (check in IE or rename the selector)

You can of course add even & odd too:

match[3] = match[3] == "even" ? "2n" : match[3] == "odd" ? "2n+1" : match[3];

gblazex
  • 49,155
  • 12
  • 98
  • 91
  • 1
    Cool! This is what I was looking for; a short, clever, concise, solution. As you know, `:odd` and `:even` are already supported in jQuery, just not in the `:nth-of-type(odd)` / `:nth-of-type(even)` form, but it’s nice to have ’em in there as well for the sake of completeness. – Mathias Bynens Jan 25 '11 at 08:05
  • Update: for even more selectors, see https://github.com/keithclark/JQuery-Extended-Selectors – Mathias Bynens Mar 30 '11 at 16:40
  • 1
    Nice, but i just wanted to warn others about this solution always assuming a periodicity (n). I mean, filter like `:nth-of-type(2n)` and `:nth-of-type(2)` will be the same (when the latter should only be the 2nd element, instead of all the even elements). – Mariano Desanze May 18 '11 at 21:12
4

the jQuery plugin moreSelectors has support for nth-of-type (and many other selectors). I suggest either using that, or simply implement a simple plugin that only implements the exact selectors you need. You should be able to copy-paste code from there.

Happy hacking!

Emil Stenström
  • 13,329
  • 8
  • 53
  • 75
1

I can't pretend to know how nth-of-type is implemented, but jQuery does provide a mechanism by which you can create your own custom selector.

The following question deals with custom selectors, and may provide a useful insight to you

What useful custom jQuery selectors have you written?

Community
  • 1
  • 1
James Wiseman
  • 29,946
  • 17
  • 95
  • 158