Styling every nth element: CSS3, jQuery, and grid view templates
Themers frequently have a need to style every nth element (second, third, fourth, etc.). Sometimes, this is as straightforward as odd/even coloring on table rows; these are styling touches that can be chalked up to progressive enhancement. In the case that inspired this post, I had a layout with a three-card grid of view items, and every third item in a row needed the right margin removed so it wouldn't break to the second line. Unlike adding table striping, this is structural styling, and when it doesn't take effect because of browser restrictions (no CSS3 support, JavaScript disabled, etc.), it produces a very bad-looking page.
Views provides odd/even classes (views-row-odd, views-row-even) and number classes (views-row-1, views-row-2, views-row-3, views-row-4, and so on). Theoretically, you could apply a class to views-row-3, views-row-6, views-row-9, etc., but that would produce some truly terrible CSS and isn't worth considering when there are more elegant options.
Of those more elegant options, the three main ones are the CSS3 :nth-child pseudo-selector; the jQuery :nth-child selector; and adding a class to every third element by creating a view template. This post will touch on the advantages and disadvantages of each, along with a sample use case. While I'm writing about the third child, you can customize these for any number.
Spoiler Alert: I dug into all three options, mostly for people who have nth-child styling that's just progressive enhancement. However, if you've got a grid to deal with, I strongly recommend the grid view template approach for maximum browser compatibility.
Client-side solutions
Client-side solutions get around nth-child formatting by making changes at the browser level. If your users' browsers support them, you're golden. If they don't, they'll see your fallback. As such, these are suitable when your fallback is good enough for a decent chunk of your audience to see it.
CSS3 pseudo-selector
To select every third element with the CSS3 pseudo-selector :nth-child, you can do something like this:
.container-div .child-div:nth-child(3n+3) {
margin-right: 0;
}
For example, if I have a view with the class blog-posts, and it contains divs with the typical class views-row, my code would look like this:
.blog-posts .views-row:nth-child(3n+3) {
margin-right: 0;
}
Let's say you want the fifth element instead. Change the bit in parentheses: (5n+5). For more detail on the equation and what you can do with it, see CSS Tricks' How nth-child Works post.
Pros: This is quick, simple, and doesn't have the flicker that jQuery can on page load.
Cons: This has no browser support in IE8 and earlier. That means that while this is fine for progressive enhancement effects, it's no good for essential styling. In my case, removing the margin from the third element prevents a three-column grid from getting turned into a two-column grid with a weird gap on the right. Since I don't want people using IE8 to see the original problem, this isn't the right solution.
jQuery :nth-child
Although the CSS pseudo-element above doesn't have the appropriate level of browser support, there's also a similar jQuery version that you can stick in document.ready. The jQuery version of the example above would look like this:
$('.blog-posts .views-row:nth-child(3n)').addClass('third');
Note: If you're new to using jQuery in Drupal, don't forget to add the jQuery wrapper:
(function ($) {
$(document).ready(function(event){
$('.blog-posts .views-row:nth-child(3n)').addClass('third');
}); // End document ready
}) (jQuery); // End custom jQuery wrapper
This would add a third class to every third element. You could then write some CSS to target this new class:
.blog-posts .third {
margin-right: 0;
}
Pros: Browser support, assuming that JavaScript is turned on.
Cons: Can produce a flicker when the page loads where the undesirable effect is shown first and quickly replaced with the desired effect. In my case, this would be a broken grid followed by a corrected grid. Again, not great, since the difference is so noticeable. Also, it doesn't work if the user has JavaScript off.
Note: If you just want to style every other element, take advantage of jQuery's odd/even support. Here's an example where it's applied to table rows for zebra striping. This produces an "odd" class and an "even" class on each row.
$("tbody > tr:odd").addClass("odd");
$("tbody > tr:not(.odd)").addClass("even");Server-side solutions
Grid view templates
Client-side solutions like CSS3 and jQuery are easy to apply, but less powerful than server-side solutions like templates. Rather than adding the class on the fly, templates add the class before the page is rendered, so they're friendly even to people whose browsers don't support CSS3 or have JavaScript disabled. As long as they don't have CSS itself disabled, you'll be fine.
To add the classes we need, we could make a template for each individual view (more on that below); since grids are common on my site, I prefer to style a view format I can then turn on whenever I need it. Fortunately, Views provides a default grid format with a template called views-view-grid.tpl.php. It looks like this:
<?php if (!empty($title)) : ?>
<h3><?php print $title; ?></h3>
<?php endif; ?>
<table class="<?php print $class; ?>"<?php print $attributes; ?>>
<tbody>
<?php foreach ($rows as $row_number => $columns): ?>
<tr <?php if ($row_classes[$row_number]) { print 'class="' . $row_classes[$row_number] .'"'; } ?>>
<?php foreach ($columns as $column_number => $item): ?>
<td <?php if ($column_classes[$row_number][$column_number]) { print 'class="' . $column_classes[$row_number][$column_number] .'"'; } ?>>
<?php print $item; ?>
</td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
As you probably noticed, this is table-based, and table-based layouts are relics of an ancient time. But it does some very handy things, like adding a class for each column and row. In my case, it provides a nice col-3 class that I can use to style the third item in each row.
Modifying this template to be div-based rather than table-based was easy enough. I copied it to my theme, and adjusted it as follows:
- I removed the opening and closing <tbody> tags.
- I replaced the opening and closing <tr> tags with <div> tags.
- I replaced the opening and closing <td> tags with <div> tags.
- Because I was floating the elements inside the rows, I added a "clearfix" class to each row.
Here's the result:
<?php if (!empty($title)) : ?>
<h3><?php print $title; ?></h3>
<?php endif; ?>
<div class="<?php print $class; ?>"<?php print $attributes; ?>>
<?php foreach ($rows as $row_number => $columns): ?>
<div <?php if ($row_classes[$row_number]) { print 'class="' . $row_classes[$row_number] .' clearfix"'; } ?>>
<?php foreach ($columns as $column_number => $item): ?>
<div <?php if ($column_classes[$row_number][$column_number]) { print 'class="views-row ' . $column_classes[$row_number][$column_number] .'"'; } ?>>
<?php print $item; ?>
</div>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
And here's some sample output. The contents of anything in brackets -- view classes, content, etc. -- would be specific to your view.
<div class="[view classes]">
<div class="view-content">
<div class="views-view-grid cols-3">
<div class="row-1 clearfix">
<div class="views-row col-1">[content]</div>
<div class="views-row col-2">[content]</div>
<div class="views-row col-3">[content]</div>
</div>
<div class="row-2 clearfix">
<div class="views-row col-1">[content]</div>
<div class="views-row col-2">[content]</div>
<div class="views-row col-3">[content]</div>
</div>
<div class="row-3 clearfix">
<div class="views-row col-1">[content]</div>
<div class="views-row col-2">[content]</div>
<div class="views-row col-3">[content]</div>
</div>
</div>
</div>
</div>
I can now target the third item in every row with .col-3, solving my original problem nicely:
.blog-posts .col-3 {
margin-right: 0;
}
If I want to use this same grid format on another view, I can easily apply it by using the grid format, as shown below:
If I have 4 columns rather than 3, I can change it under the grid settings. This is the "Settings" link next to "Grid" in the screenshot above, and it will open the following dialogue:
Pros: Browser support everywhere, provided your CSS is supported; good control over the markup; easy to apply through the views UI once you've made the template.
Cons: Not suitable if you're styling every nth element in a view but aren't using a grid layout.
Honorable mentions (not tested)
I came across an interesting post on adding classes to every nth node in an array. I didn't try this out personally, but here's the link if you'd like to give it a go: How to add classes to every X number of node in a array (http://drupal.stackexchange.com/).
If the grid format doesn't work for your purposes, you might want to take a look at this post on Stack Exchange; again, I haven't tried this out myself, and I'm adding the link only to reduce the amount of Google-fu you have to do today.