1

I have a grid of items where after certain items there is to be a spacer. I've tried to use :nth-of-type to apply styling to the first column of items that I don't want applied to the right side. However, the CSS seems to get confused when I have the separators.

https://jsfiddle.net/yquw291h/2/

As you can see the counting goes off after each extra div, but surely the CSS should only be taking into account each .box?

Is there a way I can make it only affect each item? I want only the first column to have certain styles, I'm happy to use a JS solution too.

HTML

<div>
    <div class="box">1</div>
    <div class="box">2</div>
    <div class="box">3</div>
    <div class="box">4</div>
    <div class="splitter">splitter</div>
    <div class="box">1</div>
    <div class="box">2</div>
    <div class="box">3</div>
    <div class="splitter">splitter</div>
    <div class="box">1</div>
    <div class="box">2</div>
    <div class="box">3</div>
    <div class="box">4</div>
    <div class="box">5</div>
    <div class="box">6</div>
    <div class="box">7</div>
    <div class="box">8</div>
</div>

CSS

.box {
    width: 48%;
    float: left;
    height: 30px;
    background: #ccc;
}

.box:nth-of-type(odd){
    margin-right: 4%;
    background-color: red;
}

.splitter {
    width: 100%;
    float: left;
}
TylerH
  • 20,799
  • 66
  • 75
  • 101
Sam Willis
  • 4,001
  • 7
  • 40
  • 59
  • I have nominated this question for reopening since this is asking for a JavaScript/jQuery solution and the target question was strictly CSS-only (and the answers are different; in CSS you can't, but with JS you can). – TylerH Apr 26 '16 at 14:23

3 Answers3

8

This is a misunderstanding of :nth-of-type(). It's looking for the type of element (a div), that is also (odd), and only applying the styles to those that are also of class="box".

Your 5th div is a splitter, which is still counted even though it doesn't have class="box". It just doesn't have the styles applied because your selector specifies .box nth-of-type. Your next odd one is <div class="box">2</div> in between the splitters.

In case you haven't caught on yet, it's counting all the divs in your container. Just because you have an even number as text inside the .box divs, doesn't make them evenly numbered entries in your "list" of divs.

Since you said in a comment that you can't edit the markup, here is a jQuery implementation (thanks to BoltClock for help):

var container = document.getElementById("search-results");
var descendants = container.getElementsByTagName("div");
var x, i = 0;
for (x = 0; x < descendants.length; x++) {
    var nth = $(descendants).eq(x);
    if (nth.hasClass("splitter")) {
     i = 0;
     continue;
    }
    if (nth.hasClass("box")) {
        if (i % 2 == 0) {
            nth.addClass("odd");
        }
        i++;
    }
};
.box {
    width: 48%;
    float: left;
    height: 30px;
    background: #ccc;
}

.odd {
    margin-right: 4%;
    background-color: red;
}

.splitter {
    width: 100%;
    float: left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="search-results">
    <div class="box">1</div>
    <div class="box">2</div>
    <div class="box">3</div>
    <div class="box">4</div>
    <div class="splitter">splitter</div>
    <div class="box">1</div>
    <div class="box">2</div>
    <div class="box">3</div>
    <div class="splitter">splitter</div>
    <div class="box">1</div>
    <div class="box">2</div>
    <div class="box">3</div>
    <div class="box">4</div>
    <div class="box">5</div>
    <div class="box">6</div>
    <div class="box">7</div>
    <div class="box">8</div>
</div>

You can modify your markup as Tim's answer suggests to achieve what you're looking for without using JavaScript. Or you can modify it by using a different set of elements to divide. But this would only work if you had an even number of divs in each section between a divider: (I've commented the numbering status for each element, with regard to the selector you're using).

.box {
    width: 48%;
    float: left;
    height: 30px;
    background: #ccc;
}

.box:nth-of-type(odd){
    margin-right: 4%;
    background-color: red;
}

.splitter {
    width: 100%;
    float: left;
}
<div>
    <div class="box">1</div> <!-- odd -->
    <div class="box">2</div> <!-- even -->
    <div class="box">3</div> <!-- odd -->
    <div class="box">4</div> <!-- even -->
    <hr class="splitter"/> <!-- N/A -->
    <div class="box">1</div> <!-- odd -->
    <div class="box">2</div> <!-- even -->
    <div class="box">3</div> <!-- odd -->
    <div class="box">3</div> <!-- even -->
    <hr class="splitter"/> <!-- N/A -->
    <div class="box">1</div> <!-- even -->
    <div class="box">2</div> <!-- odd -->
    <div class="box">3</div> <!-- even -->
    <div class="box">4</div> <!-- odd -->
    <div class="box">5</div> <!-- even -->
    <div class="box">6</div> <!-- odd -->
    <div class="box">7</div> <!-- even -->
    <div class="box">8</div> <!-- odd -->
</div>

At this point the question needs to be asked, why do you need columns? If you have some kind of tabular data, you should use <table>s. If you just want two side-by-side columns, why not put one group of divs in a "left" container and the others in a "right" container? That way you can style them en masse via .left div {} and .right div {} selectors.

TylerH
  • 20,799
  • 66
  • 75
  • 101
  • 1
    @Tim You misunderstand. I've said it doesn't count meaning it doesn't *qualify* for the selector. This is in line with the rest of my answer. I'll edit it to make it clearer. – TylerH Mar 30 '16 at 16:45
  • You could add a snippet with different type of element for the separators (like `hr`) and style it according to OP's separator. Just to supply an alternative solution. :) – Tim Mar 30 '16 at 16:48
  • 1
    @Tim True, but for now I'm headed to lunch :-P – TylerH Mar 30 '16 at 16:49
  • Unfortunately, The structure is built on the fly as a search results page so adding different structure to the page isn't as easy as it would seem. – Sam Willis Mar 30 '16 at 22:48
  • @SamWillis Can you add a classname or ID to the container div? – TylerH Mar 31 '16 at 12:50
  • Yes I would be able to add to the container. It also already does have an ID of `search-results` – Sam Willis Mar 31 '16 at 13:57
  • @SamWillis I've updated my answer to include a jQuery answer that does exactly what you want without modifying the markup/structure. – TylerH Mar 31 '16 at 19:10
5

Actually it works like it should. :nth-of-type translates to the number of this type (div) of the parent element. Therefor you can easily fix your problem by wrapping the boxes in a simple parent element, between separators:

.box {
    width: 48%;
    float: left;
    height: 30px;
    background: #ccc;
}

.box:nth-of-type(odd){
    margin-right: 4%;
    background-color: red;
}

.splitter {
    width: 100%;
    float: left;
}
<div>
  <div>
    <div class="box">1</div>
    <div class="box">2</div>
    <div class="box">3</div>
    <div class="box">4</div>
  </div>
    <div class="splitter">splitter</div>
  <div>
    <div class="box">1</div>
    <div class="box">2</div>
    <div class="box">3</div>
  </div>
    <div class="splitter">splitter</div>
  <div>
    <div class="box">1</div>
    <div class="box">2</div>
    <div class="box">3</div>
    <div class="box">4</div>
    <div class="box">5</div>
    <div class="box">6</div>
    <div class="box">7</div>
    <div class="box">8</div>
  </div>
</div>
Tim
  • 5,521
  • 8
  • 36
  • 69
  • Unfortunately, The structure is built on the fly as a search results page so adding different structure to the page isn't as easy as it would seem. – Sam Willis Mar 30 '16 at 22:48
2

I believe this is a misunderstanding of how the :nth-of-type selector works. The .box selector doesn't restrict the :nth-of-type selector. It acts as an additional sub filter. In this case all odd divs are selected and then of those odd divs, ones with the box class are styled.

In your example, the odd divs in this case are:

<div>
    <div class="box">1</div> <!-- Odd -->
    <div class="box">2</div>
    <div class="box">3</div> <!-- Odd -->
    <div class="box">4</div>
    <div class="splitter">splitter</div> <!-- Odd -->
    <div class="box">1</div>
    <div class="box">2</div> <!-- Odd -->
    <div class="box">3</div>
    <div class="splitter">splitter</div> <!-- Odd -->
    <div class="box">1</div>
    <div class="box">2</div> <!-- Odd -->
    <div class="box">3</div>
    <div class="box">4</div> <!-- Odd -->
    <div class="box">5</div>
    <div class="box">6</div> <!-- Odd -->
    <div class="box">7</div>
    <div class="box">8</div> <!-- Odd -->
</div>

However, of those odd divs listed above, only these:

<div>
    <div class="box">1</div> <!-- Odd w/ class box -->
    <div class="box">2</div>
    <div class="box">3</div> <!-- Odd w/ class box -->
    <div class="box">4</div>
    <div class="splitter">splitter</div>
    <div class="box">1</div>
    <div class="box">2</div> <!-- Odd w/ class box -->
    <div class="box">3</div>
    <div class="splitter">splitter</div>
    <div class="box">1</div>
    <div class="box">2</div> <!-- Odd w/ class box-->
    <div class="box">3</div>
    <div class="box">4</div> <!-- Odd w/ class box -->
    <div class="box">5</div>
    <div class="box">6</div> <!-- Odd w/ class box -->
    <div class="box">7</div>
    <div class="box">8</div> <!-- Odd w/ class box-->
</div>

Match the additional filter requirements being that they must also contain the class box.

Example:

Below is an example of how you can accomplish this using several :nth-child selectors to create css ranges. This is helpful if you're not able to alter your HTML structure as some of the other answers suggest:

.box {
  width: 48%;
  float: left;
  height: 30px;
  background: #ccc;
}

.box:nth-child(n+1):nth-child(odd):nth-child(-n+4),
.box:nth-child(n+6):nth-child(even):nth-child(-n+8),
.box:nth-child(n+10):nth-child(even):nth-child(-n+17),
.box:nth-child(n+19):nth-child(odd):nth-child(-n+21){
  margin-right: 4%;
  background-color: red;
}

.splitter {
  width: 100%;
  float: left;
}
<div>
  <div class="box">1</div>
  <div class="box">2</div>
  <div class="box">3</div>
  <div class="box">4</div>
  <div class="splitter">splitter</div>
  <div class="box">1</div>
  <div class="box">2</div>
  <div class="box">3</div>
  <div class="splitter">splitter</div>
  <div class="box">1</div>
  <div class="box">2</div>
  <div class="box">3</div>
  <div class="box">4</div>
  <div class="box">5</div>
  <div class="box">6</div>
  <div class="box">7</div>
  <div class="box">8</div>
  <div class="splitter">splitter</div>
  <div class="box">1</div>
  <div class="box">2</div>
  <div class="box">3</div>
</div>

Note: The above markup is slightly more extensive than that provided in your question. It's based off of your full jsfiddle markup instead of the shortened version contained in your question.

Here is your fiddle updated to contain the changes shown above.

War10ck
  • 12,387
  • 7
  • 41
  • 54
  • What about browser support? – Tim Mar 31 '16 at 13:03
  • @Tim [caniuse.com](http://caniuse.com/#feat=css-sel3). The CSS3 `:nth-child` selector has support in all major browsers and even partial support in IE 8. – War10ck Mar 31 '16 at 13:24
  • And do you know that also covers the `n+x` part? Personally, I doubt that. – Tim Mar 31 '16 at 13:53
  • 1
    @Tim Yes, all except for IE 8 which was noted above. Here's two additional resources showing browser support [`:nth-child`](https://css-tricks.com/almanac/selectors/n/nth-child/#browser-support) and [How `nth-child` works](https://css-tricks.com/how-nth-child-works/#article-header-id-0) if you still have doubts... – War10ck Mar 31 '16 at 14:01
  • Smart solution then! – Tim Mar 31 '16 at 14:03
  • 1
    @Tim Thanks! Yours too. I personally like the idea of fixing the HTML structure to support the desired layout but if its locked in as the OP says, it's good to have a backup. – War10ck Mar 31 '16 at 14:04
  • This would definitely work (albeit a little verbosely) if the structure were static, but it's built on the fly (per OP's comment on my answer above). – TylerH Mar 31 '16 at 19:12