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 );
Mike says
Good detail, thanks Bill. I’m actually trying to remove the grid loop. I’ve figured that out for the home page, but my ‘page 2’ of posts shows up in grid format. Strangely my domain.com/page/2 shows the grid, however my domain.com/blog/page/2/ shows up like my homepage (eg. 5 posts, 600×200 featured image, 145 text content). As the guru, any idea how to configure to remove grids on page 2? Thanks!
Dorian Speed says
Hi, Bill – finally had a chance to test this, on the site here:
http://mwilby.convolare.com/
I have tried both code snippets, and I’m using the plugin. I’m working with the basic Genesis sample theme and I’ve deactivated all other plugins.
When I try this one:
https://gist.github.com/billerickson/4947529
I get “Fatal error: Call to undefined function be_grid_loop_pagination() in /home/convola/public_html/mwilby/wp-content/themes/rocketman/functions.php on line 22”
When I try this one:
https://www.billerickson.net/code/genesis-grid-change-length-of-content-in-grid/
it doesn’t seem to affect the length of the excerpts, and when you view an individual post, there’s no content.
FWIW, I have the same issue on this site:
patgohn.convolare.com
I wasn’t sure if the Genesis theme settings should be set to “show content” or “show excerpt” but I’ve tried both configurations and have the same result.
I’d appreciate it if you have a chance to take a look.
Bill Erickson says
Best place to look is in the plugin itself.
The plugin is built as a class and the functions are methods. So be_grid_loop_pagination() does not exist as a function, and your code is rightly telling you that. That code only exists if you are NOT using the plugin, but using the series of functions listed above.
You were correct, there was an error in the second code. I needed to move the remove_action() inside the function so it only removed the content if we were providing new content. Here’s the updated code: https://gist.github.com/billerickson/4179753
If that second code doesn’t work for you, view the source of your page and see what classes are actually being applied to your teasers. I’m assuming you’re adding a class of ‘teaser’. If you’re adding something else, like ‘one-half’, then do that. You just need to find a class that is on all teasers but not on the features.
Dorian Speed says
Got it working! (have to give credit to Brandon Kraft for the assist). Here’s what was happening.
It’s applying the class “one-half teaser,” not “one-half” and “teaser” as separate classes. So I’m guessing if I’d said I wanted three columns instead of two, I would need to change it to “one-third teaser.”
I also changed it from get_the_excerpt to get_the_content. Since I hadn’t changed the default excerpt length of 55 words, it wouldn’t allow me to create an excerpt that was longer than 55 words. But this seems to be working. I haven’t gotten all of the styling sorted out and it may be that I need to switch back to using the excerpt and just modify the default length of the excerpt in order for other things to work right, but for now – this solution is working. Thanks again for your help.
I’m still figuring out GitHub, so I’m not sure this link will work, but here’s the code I am using: https://gist.github.com/DMSpeed/5207985 I am also going to post this on the StudioPress forum where someone’s asked this same question.
Raghu says
Bill – I’m trying to insert Adsense after 2 Features in home page. Is there an example that I can follow?
Terence says
Bill,
I am a website designer ~ not a developer ~ so any help you can give me would be much appreciated.
Here’s a loop that displays the title and an excerpt with a link for each post in the section ~ https://gist.github.com/SocialStrategy/5270605
How do I replace the loop in Genesis with a loop that loads post URLs rather than categories?
The goal is to not use a blog but end up with a structure like this http://domain.com/page/comment/
Thanks for reading this far.
Ternece.
Terence says
Sorry Bill, that should have read http://domain.tld/page/post/comments/ ~ my bad!
XDude says
Thanks!
Gemma Wild says
Hi Bill
Thank you very much for posting this tutorial, works brilliantly and is a life saver for someone like me who isn’t so fluent in this kind of coding.
Would you be able to help me just a little bit further? I am displaying the 3 grid loop on the homepage and all archive, category and tag pages, etc, and am also using the Genesis – Featured Posts widget to display 1 featured post (title and thumbnail) in the sidebar throughout the site. Of course, on the pages that display the grid loop, the widget copies the behaviour and displays a small image and the title in 1/3 of a column. On normal pages without the grid loop, it displays the featured post at 100% width of the widget, as intended.
You have provided a way of excluding the grid if its on a singular post so I was wondering if there is a way I can ask it to ignore the grid behaviour if it’s contained within a widget?
Gemma
Bill Erickson says
I’ll need to do some testing to see why this happens in the Featured Posts widget. My guess is the Featured Posts widget is doing something wrong.
Are you using the widget built into Genesis, or are you installing the “Genesis Featured Widget Amplified” plugin and using its widget?
The code above and in my plugin is limited to the main query on the page (that’s the $query->is_main_query() part). Any secondary queries, like those in widgets, should not be affected. The Featured Posts widget must be telling WordPress that it is the main query.
Gemma Wild says
I’ve double checked to be sure and yes I am using the built in Genesis Featured Posts widget and not a plugin.
To be clear, I am using the ‘Even easier sitewide’ code given above which doesn’t appear to have the ‘$query->is_main_query’ code you’ve mentioned in your reply. Should it? =)
My widget settings are, although you may not need them:
All Categories
Number of Posts to Show: 1
Show Featured Image
Thumbnail 150px by 150px (though it displays 54px on pages containing the grid loop)
Show Post Title
Nigel Parry says
Using Grid Loop, which is an amazing plugin, I am noticing that the right border on thumbnails is missing: http://66.147.244.62/~grapefr3/topic/the-golden-gallery
Presumably this is a CSS issue? I know Grid Loop does its own thing with images. Any tips?
Bill Erickson says
The issue is that the image you’re using is wider than the column it is in. The column is 201px wide. The image is 202px wide (200px + 1px border on left and right = 202px).
If you make the image 199px wide, it will be just the right size.
Nigel Parry says
Thanks Bill, that had confused me for a while. Your solution worked fantastically with a quick image size add to functions.php, Regenerate Thumbnails, and reselecting the new 199 x 199 pixel size in the Grid Loop settings.
I also noticed that the Grid Loop, when turned on, appears to override thumbnail image sizes in the Primary Sidebar (the site’s Blog teaser, an instance Genesis Featured Widget Amplified).
When I had a full width Feature image and Teasers selected in Grid, the blog widget image in the Primary Sidebar was the full size of the Sidebar width. When I opted in Grid Loop for no Features but just Teasers (2-across), then the image in the Primary Sidebar became about 50% of the width. Is there some crossover of CSS going on here between the main page and the sidebar images? Should the Grid Loop be affecting sidebars?
Nigel Parry says
PS: Sorry, the link to the blog teaser is here: http://66.147.244.62/~grapefr3/
Bill Erickson says
This is because the Genesis Featured Widget Amplified plugin is coded improperly (it’s using query_posts). This means that when the widget runs, WordPress thinks that’s the primary content of the page and applies all the grid code to it as well.
If possible, just don’t use this widget.
Nigel Parry says
Thanks for your responses Bill. Yeah, it’s a kind of strange behaving widget sometimes. I’ve been using it kind of as a legacy option. Is there any alternative?
Bill Erickson says
It depends what you’re using it for. If you’re using it to query for posts, I recommend my Display Posts Shortcode plugin along with Shortcodes in Sidebar Widgets which will make the shortcode work in the sidebar.
Mike says
The Display Posts Shortcode plugin is brilliant! It handled the issue with the grid loop and the featured widget amplified. I was having the same issue with featured posts in the widget being formatted in grid columns. Spent three hours today trying to sort it out and finally got smart and came here. Thanks Bill!
Nigel Parry says
I couldn’t agree more Mike. Thanks Bill, that plugin is just unbelievably richly featured. 🙂
Qing says
Hi, Bill
I bought and installed the “Expose child theme”. I want the exact layout structure of its home page, which might be “grid loop” (sorry, I am new for the website design). I imported its xml file, and it doesn’t work; I installed your grid loop plugin, it don’t know how to set it up. I am waiting for your kindly help. THANK YOU.
Bill Erickson says
You’ll need to speak with StudioPress support. I don’t provide support for my plugins or commercial themes.