5
$j('.select-all-these:not(.except-these):nth-child(3n)');

I'm trying to select every third item that doesn't have a particular class. This is my jQuery selector, but it doesn't work - it seems that the :nth-child selector ignores the :not selector. Am I doing it wrong?

As an example, this is how it should work:

.select-all-these.except-these
.select-all-these.except-these
.select-all-these.except-these
.select-all-these
.select-all-these.except-these
.select-all-these
.select-all-these <-- THIS ONE IS SELECTED
.select-all-these.except-these

Thanks! :)

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Luke Carbis
  • 345
  • 2
  • 12

5 Answers5

6

How about using the method to filter the results instead?

$('.select-all-these:nth-child(3n)').not('.except-these');

Here's a fiddle to demonstrate: http://jsfiddle.net/ntNgC/

kinakuta
  • 9,029
  • 1
  • 39
  • 48
  • Thanks - but this has the same problem - it will select every third item, and then if that item has '.except-these' it will ignore it. The logic I need is different - select every third item that is not '.except-these' - does that make sense? – Luke Carbis May 09 '12 at 05:43
  • Not exactly. Select every third item that does not match the selector '.except-these' sounds like the same thing as ignoring the '.except-these'. Maybe it would be more clear if you included some sample markup then state what it is you want selected and what you don't want selected from that markup. Ah - I see you included that - I'll take a look at it now. – kinakuta May 09 '12 at 05:59
  • Ok, the problem with what you're trying to do is that filters applied are using the elements status as they currently exist in the DOM rather than their status in the selected set generated by the previous filter. So when you want to select every 3rd item of the result of filtering out other elements, you're only going to get elements that are the 3rd item of the set as a whole. Does that make sense? To make the kind of selection you want, you'll need to use a function to define the subset you want to filter on. – kinakuta May 09 '12 at 06:17
  • This jsfiddle demonstrates what I'm talking about: http://jsfiddle.net/ntNgC/2/ Notice that only 1 of the li's are red, because only one of the items that don't have the '.test_class' class is actually a 3n child of the set. – kinakuta May 09 '12 at 06:21
6

The only way I could see to make this work was to use two filter() calls:

$('.select').filter(
    function(){
        return !$(this).hasClass('dontselect');
    }).filter(
        function(i){
            return (i+1)%3 == 0; // unless you want a zero-based count, regular CSS is one-based
        }).css('color','red');

JS Fiddle demo.

You could, though, use a single filter() call, with an external variable:

var count = 0;
$('.select').filter(
    function(){
        console.log(!$(this).hasClass('dontselect'));
        if (!$(this).hasClass('dontselect')){
            count++;
            return count%3 == 0;
        }
    }).css('color','red');

JS Fiddle demo.

JS Perf reports that the single filter is, unsurprisingly, a little faster, but only very, very, very marginally.

References:

David Thomas
  • 249,100
  • 51
  • 377
  • 410
  • I had a similar issue but instead wanted to do every other element. So this is a little bit cleaner way to do it if you are only looking for every other element instead of third element. `$('.select:not(.dontselect)').filter(:even).css('color','red');` – c.dunlap Aug 28 '14 at 01:17
3

UPDATE: i don't think this is possible with nth-child or another selector of jQuery. so consider using a more verbose solution:

var count = 0;
$('.select-all-these').each(function() {
    if(!$(this).hasClass('except-these')) {
        count++;
    }
    if(count === 3) {
        $(this).text('every 3rd element');
        count = 0
    }
});​

http://jsfiddle.net/TJdFS/2/ (alternative version: http://jsfiddle.net/TJdFS/)

:nth-child counts all matching elements ignoring any additional filters like :not.

see jquery doc:

The :nth-child(n) pseudo-class is easily confused with :eq(n), even though the two can result in dramatically different matched elements. With :nth-child(n), all children are counted, regardless of what they are, and the specified element is selected only if it matches the selector attached to the pseudo-class. With :eq(n) only the selector attached to the pseudo-class is counted, not limited to children of any other element, and the (n+1)th one (n is 0-based) is selected.

Example:

<div class="select-all-these">1</div>
<div class="select-all-these except-these">2</div>
<div class="select-all-these except-these">3</div>
<div class="select-all-these">4</div>
<div class="select-all-these except-these">5</div>
<div class="select-all-these">6</div>

JS:

$('.select-all-these:not(.except-these):nth-child(6)').text('nth-child counts all elements (1 based!)');
$('.select-all-these:not(.except-these):eq(1)').text('eq counts only matching elements (0 based!)');

Result:

1
2
3
eq counts only matching elements. (0 based!)
5
nth-child counts all elements (1 based!)

http://jsfiddle.net/nFtkE/2/

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
Felix Ebert
  • 1,103
  • 11
  • 30
  • Thanks for the answer but eq doesn't have an 'n' - so I can't select every third element, only specific elements. – Luke Carbis May 09 '12 at 05:24
  • oh sorry, i totally overlooked the word 'every' .. ;-) my fault. – Felix Ebert May 09 '12 at 05:34
  • Thanks so much for your help - the $.each solution worked - but I decided to go with filter instead, as I think it might be a bit faster with lots of elements. – Luke Carbis May 09 '12 at 06:39
3

Best easy way =)

$('table tr:not(.class)').eq(1);

Good luck =)

fdrv
  • 852
  • 1
  • 11
  • 21
1

Nth-child can be counterintuitive when working with a filtered selection of a group.

Use .each() to get around its limitations:

var count = 0;
$('.select-all-these:not(.except-these)').each(function(){
    if ( count++ % 2 == 0 ) $(this).css('color','red')
})
Stephen Saucier
  • 1,925
  • 17
  • 20