Creating a WordPress Categories Page using Nested Loops

Do you find yourself with a bunch of Posts organized into Categories and are thinking, Wouldn’t it be nice to make a page where people can get a taste for what’s in each Category? I found myself in exactly this situation with Seth Anderson’s photography site.

First create a new file in your theme directory and call it page-categories.php:

<?php
/*
Template Name: Categories Page
*/
 
get_header(); ?>
 
	<h1><?php the_title(); ?></h1>
 
<?php get_footer(); ?>

Then go into WordPress and create a new Page. Call it Categories. Under Page Attributes there’s a drop down for Templates; select Categories Page. Save. You should now be able to pull up the page at /categories.

Back in the template file, add a line that gets the list of categories and store them in a variable. These parameters here are set to show the fullest categories first:

<?php $categories = get_categories( array ('orderby' => 'count', 'order' => 'desc' ) ); ?>

Loop through the categories

This is the most important part. We’re going to loop through each of those Categories. Each time we do this we’re doing a query for posts in that Category. These parameters here are set to give us five random posts.

<?php foreach ($categories as $category) : ?>
 
	<?php query_posts( array ( 'category_name' => $category->slug, 'showposts' => '5', 'orderby' => 'rand' ) ); ?>
 
<?php endforeach; ?>

Loop through the posts

This is the second most important part. Inside that foreach, do a loop that prints out our list of posts:

<h2><?php single_cat_title(); ?></h2>
 
<?php if ( have_posts() ): ?>
 
	<ul class="thumbs">
 
		<?php while ( have_posts() ) : ?>
 
			<?php the_post(); ?>
 
			<li><a href="<?php the_permalink(); ?>"><?php the_post_thumbnail( array(151,151)  ); ?></a></li>
 
		<?php endwhile; ?> 
 
	</ul>
 
	<p class="more"><a href="/category/<?php echo $category->slug; ?>">More &raquo;</a></p>
 
<?php endif; ?>

The post_thumbnail bit is specific to my case of working on a photo site. You might use something else there, like the post title.

Classing the first thumbnail (Optional)

In CSS you can now use the pseudo-element li:first-child to give the first item in the list special styles, but old IE6-era habits die hard. Right before this line:

<?php while ( have_posts() ) : ?>

Initiate a counter, like so:

<?php $i = 0; ?>

And then, break up this line:

<li><a href="<?php the_permalink(); ?>"><?php the_post_thumbnail( array(151,151)  ); ?></a></li>

So that on the first pass though it prints a class, then iterates the counter so that it won’t do it again:

	<li><a 
		<?php if ( $i == 0 ) : ?>
			class="first" 
		<?php endif; ?>
		<?php $i++; ?>  
	href="<?php the_permalink(); ?>"><?php the_post_thumbnail( array(151,151)  ); ?></a></li>

This puts a class of “first” on the first item. (The :first pseudo class is obviously easier!)

Eliminating Duplicate Posts

It’s all there and working, but I’m not satisfied with how posts that are filed under multiple categories can show up repeatedly on the page. Luckily, Ben Gillbanks has posted a way to hide duplicate content across multiple loops. Insert the following into your functions.php:

$bmIgnorePosts = array();
 
/**
 * add a post id to the ignore list for future query_posts
 */
function bm_ignorePost ($id) {
	if (!is_page()) {
		global $bmIgnorePosts;
		$bmIgnorePosts[] = $id;
	}
}
 
/**
 * reset the ignore list
 */
function bm_ignorePostReset () {
	global $bmIgnorePosts;
	$bmIgnorePosts = array();
}
 
/**
 * remove the posts from query_posts
 */
function bm_postStrip ($where) {
	global $bmIgnorePosts, $wpdb;
	if (count($bmIgnorePosts) > 0) {
		$where .= ' AND ' . $wpdb->posts . '.ID NOT IN(' . implode (',', $bmIgnorePosts) . ') ';
	}
	return $where;
}
 
add_filter ('posts_where', 'bm_postStrip');

And then, back in the template file, right after this line:

<?php while ( have_posts() ) : ?>

Insert this line:

<?php bm_ignorePost($post->ID); ?>

And that’s it!

The Complete File

Here’s the whole thing:

<?php
/*
Template Name: Categories Page
*/
 
get_header(); ?>
 
	<h1><?php the_title(); ?></h1>
 
	<?php $categories = get_categories( array ('orderby' => 'count', 'order' => 'desc' ) ); ?>
 
	<?php foreach ($categories as $category) : ?>
 
		<?php query_posts( array ( 'category_name' => $category->slug, 'showposts' => '5', 'orderby' => 'rand' ) ); ?>
 
		<h2><?php single_cat_title(); ?></h2>
 
		<?php if ( have_posts() ): ?>
 
			<ul class="thumbs">
 
				<?php $i = 0; ?>
 
				<?php while ( have_posts() ) : ?>
 
					<?php bm_ignorePost($post->ID); ?>
 
					<?php the_post(); ?>
 
					<li><a 
						<?php if ( $i == 0 ) : ?>
							class="first" 
						<?php endif; ?>
						<?php $i++; ?>  
					href="<?php the_permalink(); ?>"><?php the_post_thumbnail( array(151,151)  ); ?></a></li>
 
			   	<?php endwhile; ?> 
 
			</ul>
 
			<p class="more"><a href="/category/<?php echo $category->slug; ?>">More &raquo;</a></p>
 
		<?php endif; ?>
 
	<?php endforeach; ?>
 
<?php get_footer(); ?>

Posted October 2011

  • Thanks for this! Can this be done for individual categories? Like if you had more than one? 

  • guest

    Thank you, this was SUPER helpful!

  • ZacEckstein

    So glad I found this! Thank you.

  • PhotoVide

    Hello, great tutorial.

    How customize css?

This website is hosted by Media Temple. Thanks, guys!