I received the following email from a developer yesterday.
As a newcomer to the web world and genesis included, I’m attempting to learn from others and looking at great designs that inspire me. This brings me to my question. Would you be willing to share your experience or even go as far as sharing your code for your custom designed portfolio page here?
After writing out my response, I figured others might be interested as well. Here’s the code, followed by a description of what each function does below.
First, the functions.php file:
| <?php | |
| /** | |
| * Functions | |
| * | |
| * @package BE_Genesis_Child | |
| * @since 1.0.0 | |
| * @link https://github.com/billerickson/BE-Genesis-Child | |
| * @author Bill Erickson <[email protected]> | |
| * @copyright Copyright (c) 2011, Bill Erickson | |
| * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License | |
| * | |
| */ | |
| /** | |
| * Portfolio Query | |
| * | |
| * @param object $query | |
| * @return null | |
| */ | |
| function be_portfolio_query( $query ) { | |
| if( $query->is_main_query() && !is_admin() && ( is_post_type_archive( 'projects' ) || is_tax( 'type' ) ) ) { | |
| $query->set( 'posts_per_page', 18 ); | |
| $query->set( 'orderby', 'menu_order' ); | |
| $query->set( 'order', 'ASC' ); | |
| $query->set( 'post_type', 'projects' ); | |
| } | |
| } | |
| add_action( 'pre_get_posts', 'be_portfolio_query' ); |
be_portfolio_query()customizes the query on the portfolio pages. This must go in functions.php since it needs to run before main query, which happens before the template is determined. More information on customizing the query.
I specify the post type so the projects show up on the taxonomy archive pages AND so that the archive-projects.php file is used for the taxonomy archive pages. I could’ve also done this second part using the template_include filter.
| <?php | |
| /** | |
| * Portfolio Archive | |
| * | |
| * @package BE_Genesis_Child | |
| * @since 1.0.0 | |
| * @link https://github.com/billerickson/BE-Genesis-Child | |
| * @author Bill Erickson <[email protected]> | |
| * @copyright Copyright (c) 2011, Bill Erickson | |
| * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License | |
| * | |
| */ | |
| /** | |
| * Portfolio Body Class | |
| * | |
| * @param array $classes | |
| * @return array | |
| */ | |
| function be_portfolio_body_class( $classes ) { | |
| $classes[] = 'portfolio-section'; | |
| return $classes; | |
| } | |
| add_filter( 'body_class', 'be_portfolio_body_class' ); | |
| /** | |
| * Collect Testimonials | |
| * | |
| */ | |
| function be_collect_testimonials() { | |
| if( have_posts() ): while( have_posts() ): the_post(); | |
| global $post, $be_testimonials; | |
| $testimonial = get_post_meta( $post->ID, 'be_project_testimonial', true ); | |
| if( $testimonial ) { | |
| $testimonial = '<div class="clearfix"></div><div class="testimonial">' . wpautop( $testimonial ); | |
| $name = get_post_meta( $post->ID, 'be_project_testimonial_name', true ); | |
| if( !empty( $name ) ) | |
| $testimonial .= '<p class="name">-- ' . $name . '</p>'; | |
| $testimonial .= '</div><div class="clearfix"></div>'; | |
| $be_testimonials[] = $testimonial; | |
| } | |
| endwhile; endif; | |
| } | |
| add_action( 'genesis_before_loop', 'be_collect_testimonials' ); | |
| /** | |
| * Sort Projects | |
| * | |
| */ | |
| function be_sort_projects() { | |
| echo '<div class="sort-projects">'; | |
| echo '<h3><span>Sort by project type:</span></h3>'; | |
| echo '<ul>'; | |
| // All Projects | |
| $class = is_post_type_archive( 'projects' ) ? 'all-projects active' : 'all-projects'; | |
| echo '<li class="' . $class . '"><a href="' . get_post_type_archive_link( 'projects' ) . '">All<br />Projects</a></li>'; | |
| // Project Types | |
| $types = get_terms( 'type', array( 'orderby' => 'menu_order' ) ); | |
| foreach ( $types as $type ) { | |
| $class = is_tax( 'type', $type->slug ) ? $type->slug . ' active' : $type->slug; | |
| echo '<li class="' . $class . '"><a href="' . get_term_link( $type, 'type' ) . '">' . $type->name . '</a></li>'; | |
| } | |
| echo '</ul></div>'; | |
| } | |
| add_action( 'genesis_before_loop', 'be_sort_projects' ); | |
| // No Post Info | |
| remove_action( 'genesis_before_post_title', 'genesis_post_info' ); | |
| /** | |
| * Project Post Classes | |
| * | |
| * @param array $classes | |
| * @return array | |
| */ | |
| function be_project_post_classes( $classes ) { | |
| global $wp_query; | |
| $classes[] = 'one-third'; | |
| if( 0 == $wp_query->current_post || 0 == $wp_query->current_post % 3 ) | |
| $classes[] = 'first'; | |
| return $classes; | |
| } | |
| add_filter( 'post_class', 'be_project_post_classes' ); | |
| /** | |
| * Outer Wrap | |
| * | |
| */ | |
| function be_outer_wrap() { | |
| echo '<div class="outer-wrap">'; | |
| } | |
| add_action( 'genesis_before_loop', 'be_outer_wrap', 80 ); | |
| /** | |
| * Outer Wrap Close | |
| * | |
| */ | |
| function be_outer_wrap_close() { | |
| echo '</div>'; | |
| } | |
| add_action( 'genesis_after_loop', 'be_outer_wrap_close', 1 ); | |
| /** | |
| * Project Archive Content | |
| * | |
| */ | |
| function be_project_archive_content() { | |
| global $post; | |
| echo '<div class="image"><a href="' . get_permalink() . '">' . get_the_post_thumbnail( $post->ID, 'be_project_thumbnail' ) . '</a></div>'; | |
| $terms_output = ''; | |
| $terms = get_the_terms( $post->ID, 'type' ); | |
| foreach( $terms as $term ) | |
| $terms_output .= '<a class="icon-type ' . $term->slug . '" href="' . get_term_link( $term, 'type' ) . '">' . $term->name . '</a> '; | |
| if( !empty( $terms_output ) ) | |
| echo wpautop( $terms_output ); | |
| } | |
| add_action( 'genesis_post_content', 'be_project_archive_content' ); | |
| /** | |
| * Project Divider | |
| * | |
| */ | |
| function be_project_divider() { | |
| global $wp_query; | |
| $count = $wp_query->current_post + 1; | |
| if( 0 == $count % 3 && $count !== 6 && $count !== $wp_query->post_count ) | |
| echo '<div class="divider"></div>'; | |
| } | |
| add_action( 'genesis_after_post', 'be_project_divider' ); | |
| /** | |
| * Project Quote | |
| * | |
| */ | |
| function be_project_quote() { | |
| global $wp_query, $be_testimonials; | |
| if( !( ( 5 < $wp_query->found_posts && 5 == $wp_query->current_post ) || ( $wp_query->current_post == ( $wp_query->found_posts - 1 ) && 5 > $wp_query->found_posts ) ) ) | |
| return; | |
| if( isset( $be_testimonials ) && !empty( $be_testimonials ) ) { | |
| $testimonial = $be_testimonials[0]; | |
| if( is_tax( 'type', 'mobile-design' ) ) | |
| $testimonial = $be_testimonials[2]; | |
| if( is_tax( 'type', 'design-to-website' ) ) | |
| $testimonial = $be_testimonials[1]; | |
| echo $testimonial; | |
| } else { | |
| echo '<div class="divider"></div>'; | |
| } | |
| } | |
| add_action( 'genesis_after_post', 'be_project_quote' ); | |
| genesis(); |
be_portfolio_body_class()adds a consistent body class to all the pages using this template, for styling (if I didn’t use this, I’d have to write styles with body.post-type-archive-projects and body.tax-type )be_collect_testimonials(), I wanted to display a testimonial between certain projects, and wanted that testimonial to be from one of the projects featured on this page. So before the projects are listed, I loop through them all and pull out all the testimonials. I can then use this in a function lower down in the pagebe_sort_projects()adds the links before the project listing that lets you go from the main portfolio (all projects) to one of the taxonomy archivesbe_project_post_classes()– This looks at the loop counter and adds column classes to the posts, breaking it into multiple columns. See this tutorial for more information.be_outer_wrap()andbe_outer_wrap_close()add a wrapping div around all the posts and testimonials so they can be cleared with CSSbe_project_archive_content()is the content of each project. It has the image and then displays the taxonomy terms (Project Type) below itbe_project_divider()adds a dividing line after every 3 posts, except after the 6th one (where a testimonial is the divider)be_project_quote()adds a testimonial after the 6th project (it’s #5 since it starts counting at 0) OR after the last project if there are less than 6. I also have it choosing different testimonials for different taxonomies so the same testimonial isn’t used on every page
Dorian Speed says
Wow, this is terrific! I’ve been putting off dealing with my own portfolio and clearly the procrastination was worth it. Thanks for posting this!
Anne Elliott says
Thank you!!!
Henk says
So there I am, just redone my portfolio, all snug and happy and then Bill comes with one of his great posts again 🙂 should I restart?
Thanks for taking the time to share, your are my top Genesis guru ! although when I look at your beard on the twitter avatar, you seem to work at looking like the first WordPress prophet…
Just noticed before posting this.. is the “Notify me of new posts by email” custom code as well ?
Bill Erickson says
I think that’s coming from Jetpack. All I know is I didn’t code that 🙂
Tim Osborn says
Very generous, thanks for taking the time, Bill!
Aaron Portbury says
Thanks so much Bill – I honestly didn’t expect this detailed of a response when I sent you that email. So generous of you 🙂
Now it’s time for me to work my way through the code and see what I come up with. Let the fun begin!
Thanks again Bill.
Mark says
Interesting read! you have inspired me to build a portfolio page just like yours =)
Leah says
Hello,
I cannot style the “active” taxonomy with CSS on my portfolio page. I have structured my CSS in the same way with the .sort-works li.(tax-slug).active a, but I cannot get the menu item to style differently when the current page is active.
Here are my functions:
https://gist.github.com/anonymous/5298060
css snippet:
.sort-works li.available-works.active a,
.sort-works li.available-works a:hover {
background: #000000;
}
Bill Erickson says
I need to see a link to your site to be able to troubleshoot
Richard Buff says
I’m working on a similar portfolio style setup as this and I’ve looked over this code line by line and I love it. Very practical and effective.
One question. Why is it necessary to have the wpautop in “echo wpautop( $terms_output );”
I understand why you use wpautop on the testimonials, which would have line breaks in their content, but why does the terms output need it?
Bill Erickson says
Because I wanted the links (inline elements) wrapped in a paragraph (block level element)
Richard Buff says
So is that an undocumented feature of wpautop? Adding a wrapping paragraph around whatever you feed it? To me at least ,the Codex seems to imply that it only replaces double line breaks with a p tag, like it would in your testimonial content. I don’t see anything to suggest that I can feed it my own HTML (which has no double line breaks in it) and it will output my HTML wrapped in a paragraph tag.
Is there a reason, other than perhaps the interest of cleaner code or simplicity, that you chose to use wpautop and not to simply echo an opening p tag, echo the $terms_output, and then echo a closing p tag?
Bill Erickson says
I guess it’s an undocumented feature. If you pass it content, it will wrap that content in paragraph tags unless it matches certain elements that shouldn’t be wrapped (table, ol, ul, li…). If two line breaks are found, it ends the first paragraph and starts a new one there.
I just think wpautop looks cleaner than opening and closing a paragraph tag.
Richard Buff says
After thinking my last comment over and reading your response, it makes a lot more sense now. Conceptually I think I was approaching it wrong. Thinking of it as wrapping whatever content you give it in a paragraph tag (with exceptions) and then replacing any double line break with a close and open paragraph tag makes it “click” better in my head. Thanks Bill.
Richard Buff says
Hi Bill,
Since here you’re storing testimonials in a meta box for each project post type, I was wondering if:
1) Your actual testimonials page, https://www.billerickson.net/wordpress-consulting/testimonials/, is built dynamically or if you just edit it directly in the page editor.
2) For the testimonials that appear on your service pages, if you setup metaboxes for them or just manually inserted them.
3) And one final unrelated question: Is your “Scheduling projects for” date value manually edited or do you store things like that in the options table, since it looks like it appears in two different variations?
Bill Erickson says
1. Testimonials page is built dynamically by querying my projects and pulling out the testimonials.
2. Testimonials on service pages are dynamic as well. I query for a project in that service (there’s a Service taxonomy for projects).
3. Yes, my availability date is stored in the options table as a UNIX timestamp, so I can then display it however I like.
Richard Buff says
Awesome thanks Bill!
sam dreadthug says
Awesome, I felt like if i had written an email to you than that would look same as that you pointed at beginning.
I was struggling to create my portfolio and cames across to this post. Thanks Google and thanks Bill.