Don’t feel like coding? I built a plugin that does this for you.
See the Genesis Grid Loop plugin.
Note: This technique can be used by all WordPress themes. I’m proposing it as a replacement for the Genesis-specific grid loop, so aspects of this post might be Genesis-specific.
A popular request is to list posts in multiple columns. I do it on my blog, and do it often on my clients’ sites.
Genesis developed a Grid Loop, which you can utilize inside your Genesis themes for this effect, and is how this blog does it. While very useful, it can be difficult to set up. I believe this is because they combined two separate functions: what content to display (your query) and how to display it.
By breaking those two functions we can use another feature of Genesis, the column classes, to build grid loops easier. You can also copy that CSS to any WordPress theme and make it work too.
For this example I’ll be using a Gallery theme I recently built. On the archive pages, it displays posts in three columns. See the screenshot above, or click here) for an example.
Step 1: Multiple columns using post_class
The post_class filter lets us customize the classes applied to each post in the loop. Since I want this to only apply to archive pages, I’m placing it in archive.php.
/**
* Archive Post Class
* @since 1.0.0
*
* Breaks the posts into three columns
* @link http://www.billerickson.net/code/grid-loop-using-post-class
*
* @param array $classes
* @return array
*/
function be_archive_post_class( $classes ) {
global $wp_query;
if( ! $wp_query->is_main_query() )
return $classes;
$classes[] = 'one-third';
if( 0 == $wp_query->current_post % 3 )
$classes[] = 'first';
return $classes;
}
add_filter( 'post_class', 'be_archive_post_class' );
The first line adds a class of ‘one-third’ to all posts. Then I grab the current post counter out of $wp_query, and if this is the first post ( 0 == $wp_query->current_post ) or if the remainder of the current post divided by 3 is zero (this tells us the current post is the first in a row), apply a class of “first” as well.
That’s it! You now have your content broken into multiple columns. If you want two columns, use ‘one-half’ and divide the current post by 2. If you want four columns, use ‘one-fourth’ and divide the current post by 4.
Step 2: Customize the Query
When I view the archive page now, it’s in three columns but it’s only displaying 10 posts. The last post sits by itself in its own row. I’m going to modify the main query to show 27 posts per page. You could use any number you want, just make sure it’s a multiple of columns. For more information on customizing the main query, see this post.
/**
* Archive Query
*
* Sets all archives to 27 per page
* @link http://www.billerickson.net/customize-the-wordpress-query/
*
* @param object $query
*/
function be_archive_query( $query ) {
if( $query->is_main_query() && $query->is_archive() ) {
$query->set( 'posts_per_page', 27 );
}
}
add_filter( 'pre_get_posts', 'be_archive_query' );
This code must go in functions.php, since the main query runs before it reaches archive.php (it checks the query to figure out what template to load).
Even easier sitewide
If you’re using this for all listings of posts (home, archive, search…), it is even easier to set up. Put the post_class filter in functions.php so it runs sitewide, and add a conditional to check if it isn’t singular:
/**
* Archive Post Class
*
* Breaks the posts into three columns
* @link http://www.billerickson.net/code/grid-loop-using-post-class
*
* @param array $classes
* @return array
*/
function be_archive_post_class( $classes ) {
// Don't run on single posts or pages
if( is_singular() )
return $classes;
$classes[] = 'one-third';
global $wp_query;
if( 0 == $wp_query->current_post || 0 == $wp_query->current_post % 3 )
$classes[] = 'first';
return $classes;
}
add_filter( 'post_class', 'be_archive_post_class' );
And instead of the function to customize the number of posts, go to Settings > Reading and tweak it there.
Advanced Example
The code snippet below (added to functions.php), modifies the blog’s homepage and archive pages to display 5 features and 6 teasers (in three columns) on the first page. On inner pages, it displays 0 features and 12 teasers (in three columns). It also updates the post image to use image sizes specifically created for features and teasers.
The first function, be_grid_loop_pagination(), is where we control the grid loop. Under the comment that says “Sections of site that should use grid loop”, you can modify that list to specify where you want the grid loop displayed. Right now it is running when is_home() or is_archive() is true. The second part, under the comment that says “Specify pagination”, is where you specify how many features and teasers to show on the homepage and subsequent pages.
The second function, be_grid_loop_query_args(), doesn’t require any customization from you. It uses the pagination information you added to the previous function to tell WordPress how many posts show up on each page.
The third function, be_grid_loop_post_classes(), applies relevant classes to each post. It’s adding a class of ‘feature’ to each feature post, and column classes to the teasers. The only thing that you’d need to change is the teasers sections if you want something other than three columns. Here’s what you’d need to change if you wanted two columns.
The fourth function, be_grid_image_sizes(), specifies the two image sizes. Change these to whatever size you’d like. If you don’t want images, you can leave out this function and the last one, be_grid_loop_image(). Also note that you need to have “Include Featured Image” checked in Genesis > Theme Settings > Content Archives.
The fifth function, be_grid_loop_image(), overrides the image size set in Genesis > Theme Settings > Content Archives with the image sizes we created in the previous function. No changes need to be made to this function.
The sixth function, be_fix_posts_nav() does as its name implies. The post navigation (Older/Newer, numerical links to posts pages…) uses $wp_query->max_num_pages to know how many pages there are, and this is based on the current page’s posts_per_page. So if you have less posts on your homepage than inner pages, the post navigation on the homepage will be off (this is noticeable if you’re using numerical links). This code changes the max_num_pages based on the grid args.
/**
* Grid Loop Pagination
* Returns false if not grid loop.
* Returns an array describing pagination if is grid loop
*
* @author Bill Erickson
* @link http://www.billerickson.net/a-better-and-easier-grid-loop/
*
* @param object $query
* @return bool is grid loop (true) or not (false)
*/
function be_grid_loop_pagination( $query = false ) {
// If no query is specified, grab the main query
global $wp_query;
if( !isset( $query ) || empty( $query ) || !is_object( $query ) )
$query = $wp_query;
// Sections of site that should use grid loop
if( ! ( $query->is_home() || $query->is_archive() ) )
return false;
// Specify pagination
return array(
'features_on_front' => 5,
'teasers_on_front' => 6,
'features_inside' => 0,
'teasers_inside' => 12,
);
}
/**
* Grid Loop Query Arguments
*
* @author Bill Erickson
* @link http://www.billerickson.net/a-better-and-easier-grid-loop/
*
* @param object $query
* @return null
*/
function be_grid_loop_query_args( $query ) {
$grid_args = be_grid_loop_pagination( $query );
if( $query->is_main_query() && !is_admin() && $grid_args ) {
// First Page
$page = $query->query_vars['paged'];
if( ! $page ) {
$query->set( 'posts_per_page', ( $grid_args['features_on_front'] + $grid_args['teasers_on_front'] ) );
// Other Pages
} else {
$query->set( 'posts_per_page', ( $grid_args['features_inside'] + $grid_args['teasers_inside'] ) );
$query->set( 'offset', ( $grid_args['features_on_front'] + $grid_args['teasers_on_front'] ) + ( $grid_args['features_inside'] + $grid_args['teasers_inside'] ) * ( $page - 2 ) );
// Offset is posts on first page + posts on internal pages * ( current page - 2 )
}
}
}
add_action( 'pre_get_posts', 'be_grid_loop_query_args' );
/**
* Grid Loop Post Classes
*
* @author Bill Erickson
* @link http://www.billerickson.net/a-better-and-easier-grid-loop/
*
* @param array $classes
* @return array $classes
*/
function be_grid_loop_post_classes( $classes ) {
global $wp_query;
// Only run on main query
if( ! $wp_query->is_main_query() )
return $classes;
// Only run on grid loop
$grid_args = be_grid_loop_pagination();
if( ! $grid_args || ! $wp_query->is_main_query() )
return $classes;
// First Page Classes
if( ! $wp_query->query_vars['paged'] ) {
// Features
if( $wp_query->current_post < $grid_args['features_on_front'] ) {
$classes[] = 'feature';
// Teasers
} else {
$classes[] = 'one-third';
if( 0 == ( $wp_query->current_post - $grid_args['features_on_front'] ) || 0 == ( $wp_query->current_post - $grid_args['features_on_front'] ) % 3 )
$classes[] = 'first';
}
// Inner Pages
} else {
// Features
if( $wp_query->current_post < $grid_args['features_inside'] ) {
$classes[] = 'feature';
// Teasers
} else {
$classes[] = 'one-third';
if( 0 == ( $wp_query->current_post - $grid_args['features_inside'] ) || 0 == ( $wp_query->current_post - $grid_args['features_inside'] ) % 3 )
$classes[] = 'first';
}
}
return $classes;
}
add_filter( 'post_class', 'be_grid_loop_post_classes' );
/**
* Grid Image Sizes
*
*/
function be_grid_image_sizes() {
add_image_size( 'be_grid', 175, 120, true );
add_image_size( 'be_feature', 570, 333, true );
}
add_action( 'genesis_setup', 'be_grid_image_sizes', 20 );
/**
* Grid Loop Featured Image
*
* @param string image size
* @return string
*/
function be_grid_loop_image( $image_size ) {
global $wp_query;
$grid_args = be_grid_loop_pagination();
if( ! $grid_args )
return $image_size;
// Feature
if( ( ! $wp_query->query_vars['paged'] && $wp_query->current_post < $grid_args['features_on_front'] ) || ( $wp_query->query_vars['paged'] && $wp_query->current_post < $grid_args['features_inside'] ) )
$image_size = 'be_feature';
if( ( ! $wp_query->query_vars['paged'] && $wp_query->current_post > ( $grid_args['features_on_front'] - 1 ) ) || ( $wp_query->query_vars['paged'] && $wp_query->current_post > ( $grid_args['features_inside'] - 1 ) ) )
$image_size = 'be_grid';
return $image_size;
}
add_filter( 'genesis_pre_get_option_image_size', 'be_grid_loop_image' );
/**
* Fix Posts Nav
*
* The posts navigation uses the current posts-per-page to
* calculate how many pages there are. If your homepage
* displays a different number than inner pages, there
* will be more pages listed on the homepage. This fixes it.
*
*/
function be_fix_posts_nav() {
if( get_query_var( 'paged' ) )
return;
global $wp_query;
$grid_args = be_grid_loop_pagination();
if( ! $grid_args )
return;
$max = ceil ( ( $wp_query->found_posts - $grid_args['features_on_front'] - $grid_args['teasers_on_front'] ) / ( $grid_args['features_inside'] + $grid_args['teasers_inside'] ) ) + 1;
$wp_query->max_num_pages = $max;
}
add_filter( 'genesis_after_endwhile', 'be_fix_posts_nav', 5 );
Jesse says
I just realized that it only does it on the first page, so it must be using the number of posts on the page to calculate the total pages for the pagination. That makes sense. So the only way to make this work correctly with Genesis’ numerical pagination is to make all pages have the same number of posts?
Bill Erickson says
Yep, I just figured that out too 🙂 Here’s some code to drop into your functions.php file to fix it: https://gist.github.com/3529963
When on the homepage, this resets $wp_query->max_num_pages to the correct one based on the grid args
Terran Birrell says
This last function (be_fix_posts_nav) doesn’t seem to quite work. It gets the total number of pages right on the first page, and the last page, but the interior pages are low by one. Removing if( get_query_var( ‘paged’ ) ) return; does the trick, so basically having this run on all pages, not just the first page. Am I missing some problem this might cause?
Otherwise this tutorial works perfectly! Thanks!
Jesse says
Awesome! It worked. Thank you so much for your help!
Gretchen Louise says
This is working great…except with Genesis Prose, it’s giving me full posts int the “teasers” instead of excerpts. (See http://ashleighbaker.net/)
Gretchen Louise says
Nevermind, I finally figured out I needed more than this, I needed to actually change the loop. Now it’s working!
Ben says
What did you change in the loop to get the posts to go from full post content to summarized. I’ve gone into Reading and chose Summarized and that didn’t change it. I added excerpts and that didn’t change it. BTW, how would I get excerpts to show instead? And how would I summarize or show only 140 words, for example, of each post instead of all of the post content?
Bill Erickson says
In Genesis > Theme Settings > Content Archives you can select if excerpts or full content are displayed.
julia says
How can I specify the pagination for only the grid loop?
I used the advanced example and specified the gridloop with:
if( ! ( $query->is_home() || $query->is_category( ‘5467’ )))
And I specified the pagination as follow:
return array(
‘features_on_front’ => 0,
‘teasers_on_front’ => 60,
‘features_inside’ => 0,
‘teasers_inside’ => 60,
The gridloop is now there for that category and shows the 60 posts but the other categories are also showing the 60 posts instead of the ‘normal’ 10.
How can I specify 60 teasers for only the gridloop?
Bill Erickson says
Hmm, it should only be affecting that specific category. Your other categories are displaying 60 posts but not in a grid? The exact same logic that determines if the grid loop is used, determines if we should change the number of posts. I don’t see how, using the code above, you could have one but not the other.
Are you sure there’s no other code in your theme or plugins that could be causing this? Did you try anything else to increase that category to 60 posts?
julia says
You are totally right. I forgot that I changed the Read settings.
Thanks alot. Great code. It works brilliantly with Genesis.
A. Tate Barber says
Hey Bill! Great tutorial.
On the front page of my blog (a blog template), I have a custom post type called “home_column” being displayed after the Genesis #header and before the latest blog posts in the Genesis #content. Here is the Gist: https://gist.github.com/3862002
I am trying to incorporate your grid loop function (advanced) into the displayed posts. Worked like a charm! However, I also trying to customize the post content. Here is the Gist: https://gist.github.com/3862034
I have input that into your grid loop function. When I do that, this manually edits the content of all of my home_column posts to “Test Front Page Feature Content”. I thought that when displaying my home_columns by using get_posts, it would operate smoothly as get_posts is supposed to be used outside of The Loop and my use of the_content filter would operate inside of The Loop (or so I thought).
Of course I am sure the solution is simple, as I am not as familiar with the WP_Query and The Loop as I would like to be, but maybe you could offer a quick solution to save me some time.
Thanks!
Bill Erickson says
‘the_content’ filter is always applied to the_content() function. the_content() is actually just get_the_content() with ‘the_content’ and a few other things ( see /wp-includes/post-template.php: https://gist.github.com/3862103 ).
I’m not sure what your end goal is, but if you must use ‘the_content’ filter, limit your modification to posts in the grid loop. See my changes here: https://gist.github.com/3862125
Your original code was checking if the grid loop was applied to the main query on the current page (returning true for the main query AND any custom queries on the current page). This checks if the current post is in the grid loop, or more specifically, if the current post has a class of ‘feature’ or ‘one-third’, which it could only get by being in the grid loop.
A. Tate Barber says
Well, mostly, I was simply experimenting with your grid loop for some learning material to work on and better my knowledge of WP + Genesis. I guess the best use of the grid loop that comes to my mind is to have the featured posts display a longer string of characters and the teaser posts to display a shorter string of characters (or using the excerpt). With your changes (by helping me understand the content loop as well at the wp_query), I think I figure out how to use some Genesis hooks to get the result I need.
Also, I was trying to play around with the jQuery isotope plugin (http://isotope.metafizzy.co/) and jQuery infinite scroll plugin (http://www.infinite-scroll.com/) to incorporate them into a “Pinterest-looking” style of blog page that is animated and responsive as well as page-less. By using the grid loop (or just being able to edit the classes), I was trying to make it so that the teasers were the only things that the plugins were effecting.
Thanks for the help!
A. Tate Barber says
Got it figured out. Your help on the_content filter really helped me (as well as a few minutes of Google searching).
Here is the solution I came up with:
https://gist.github.com/3862990
Still working on it, though.
Reid Crandall says
Great tutorial. It was exactly what I was looking for. Still learning WordPress and Genesis, it really helped. I am wondering about styling the posts. I see that you’ve got the images on your blog styled and always seem to have a the image in the thumbnail (for both feature and grid) sized and cropped very neatly, showing a nice view of the image. Are you using the thumbnail from WordPress to choose what part of the image shows in on the front page? I’m having a little trouble figuring out what my options are and how to get to them. Just editing the CSS for post-image isn’t getting me what I want exactly.
Also, is there a way to add a class to the image for each type of post so that I can style the feature and grid images differently than all of the post-images inside the single pages?
Is there a way to set the images sizes to be responsive instead of just setting the height and width in pixels?
Sorry if this is a lot of stuff. I’m full of questions and am in the “soaking up info” phase here.
Bill Erickson says
Yes, I’m uploading a large image for each post, and it’s being resized to 500×333 (used at top of single post and on features in the archive) and 212×56 (for the teasers in the archive).
I’m letting WordPress do the crop. If you don’t like how WordPress is cropping it, try using my Image Override plugin. It lets you upload alternative images to use for certain image sizes. But make sure you’ve manually cropped it because WordPress won’t crop the images you use in these overrides (that’s the whole point).
I don’t believe you can add a class to the image itself, but you’re adding classes to the whole feature and teaser, so use those: https://gist.github.com/3867171 (I’ve also made the images responsive in this code)
Reid Crandall says
Thanks Bill. Just had a chance to sort this out, and all seems to be headed in the right direction. Much appreciated.
julia says
Hi,
I tried to get a gridloop with five next to each other. So I changed one-third to one-fifth but this doesn’t work. I tried one-half which works. But one-fourth, one-fifth and one-sixth all don’t work.
Any suggestions where to look?
julia says
Sorry, wasn’t reading it closely enough. I forgot that I had to divide it too.
So in my case $wp_query->current_post % 5
julia says
Hi,
I was trying to get a sort on a custom field with posts that are in my grid loop. I added this function to the functions.php of genesis, but probably this is not the right way?
It interfers with the grid loop because now it only shows the ‘default’ number of posts instead of the defined number in the grid loop.
I added this code right after the grid loop code:
add_action(‘genesis_before_loop’, ‘custom_sort’);
function custom_sort () {
if (is_category(‘5467’)) {
global $query_string;
query_posts($query_string . “&orderby=meta_value&meta_key=sort_plek&order=desc”);
}
}
Bill Erickson says
Never use query_posts!
Modify the be_grid_loop_query_args() function to include your additional query args. If you used the code from this tutorial, it might look like this after your new args: https://gist.github.com/3894556
julia says
Thanks, works brilliantly.
Dario says
Hi Bill!
sorry for my bad english.
I’m using Education Child Theme.
How can I show only one category on the homepage?
Thank you,
Dario
Bill Erickson says
Add this to your theme’s functions.php file: https://gist.github.com/3895534
replace ‘sample-category’ with the category slug you want used.