• Skip to primary navigation
  • Skip to footer navigation
  • Skip to main content
  • Skip to primary sidebar
  • Skip to footer

WP Fangirl

WordPress Consultant Sallie Goetsch

  • speakerdeck icon
  • Home
  • About
  • Why WordPress?
  • How I Work
  • Portfolio
  • Services
  • Blog
  • Contact

Roll Your Own Filterable Directory with Genesis, ACF, and Ultimate WP Query Search Filter

October 13, 2015 by Sallie Goetsch 3 Comments

yoga teacher directory created with Genesis, ACF, and Ultimate WP Query Search Filter

When I build the Radiant Body Yoga site for Kia Miller, I created the directory of RBY-certified teachers using the Our Team plugin from WooThemes and FacetWP. It was fairly easy to add additional fields and not difficult to code the FacetWP query; tinkering with the CSS for the grid display probably took longer than setting up the query.

A couple of WordPress upgrades (and no Our Team upgrades) later, it wasn’t working properly. Kia’s assistant complained to me of not being able to save featured images, and when I went to check, I found she was right–something had ganged seriously agley.

I’d used other team plugins on occasion, but none was all that well-suited to what Kia wanted: to display the photo, name and location of each teacher, with a link to the person’s site, sorted by last name, and then be able to filter the list (well, grid) by city, state, and country. For some reason, most team and staff plugins don’t seem to use first name and last name as separate fields, making it a bit difficult to sort the display by last name. You’d think a feature like that might be obvious for a directory, but I guess not.

Also, the pagination on the FacetWP query wasn’t working. That by itself I’m pretty sure I could have solved, but I ran into further issues with FacetWP on this page later on, and decided to look for an alternative, which I found. (FacetWP is an amazing plugin and I’m pretty sure the issues were mainly my lack of development experience.)

So here’s what I did to get a working, filterable directory.

  1. Created a functionality plugin to register a “yoga teacher” post type.
  2. Set up a field group and applied it to the post type.
  3. Created a custom page_teacher_directory template to display the teacher entries alphabetically, with custom fields.
  4. Added CSS for a responsive grid display of teacher entries.
  5. Installed the Ultimate WP Query Search Filter plugin and set up a custom field search.
  6. Customized the display of the search box on the teacher directory page.
  7. Customized the Ajax search results to display the same fields and classes as the original query.

Now that you have the overview of what was involved, let me break it down.

Register yoga_teacher post type

I like to keep my custom post types in plugins. Right now I’m putting them in the regular plugin directory rather than in mu-plugins because I want to be able to turn them on and off easily when testing for problems. I suppose that once everything was working I could move the functionality plugin to mu-plugins, but so far I haven’t done that.

I learned on a recent project that sometimes you run out of room in the admin menu for custom post types, so in addition to registering the post types (and taxonomies, if I need them), I also added some code to rearrange the menu. And just to be fancy, I added the custom post types to the dashboard “At a Glance” metabox, too.

Here’s the code I used to register the yoga_teacher post type:

<?php
/*
Plugin Name: RBY Custom Post Type Plugin
Plugin URI: https://wpfangirl.com/
Description: Yoga Teacher Post Type for RBY 
Author: Sallie Goetsch
Version: 1.0
Author URI: https://wpfangirl.com
License: GPL2
*/

/* CREATE CUSTOM POST TYPE: Yoga Teachers
 ---------------------------------------------- */
 
add_action( 'init', 'create_yoga_teacher_post_type' );
register_activation_hook( __FILE__, 'activate_yoga_teacher_type' );

function activate_yoga_teacher_type() {
    create_yoga_teacher_post_type();
    flush_rewrite_rules();
}
function create_yoga_teacher_post_type() {
  
   $labels = array(
    'name' => __( 'Yoga Teachers' ),
    'singular_name' => __( 'Yoga Teacher' ),
    'all_items' => __('All Yoga Teachers'),
    'add_new' => _x('Add new Yoga Teacher', 'Teachers'),
    'add_new_item' => __('Add new Yoga Teacher'),
    'edit_item' => __('Edit Yoga Teacher'),
    'new_item' => __('New Yoga Teacher'),
    'view_item' => __('View Yoga Teacher'),
    'search_items' => __('Search in Yoga Teachers'),
    'not_found' =>  __('No Yoga Teachers found'),
    'not_found_in_trash' => __('No Yoga Teachers found in trash'), 
    'parent_item_colon' => ''
    );
  
    $args = array(
    'capability_type' => 'post',
    'hierarchical' => false,
    'labels' => $labels,
    'menu_icon' => 'dashicons-groups',
    'public' => true,
    'show_ui' => true,
    'has_archive' => true,
    'rewrite' => array('slug' => 'yoga-teachers'),
    'taxonomies' => array( 'years-episode' ),
    'supports'  => array( 'title', 'editor', 'author', 'thumbnail' , 'excerpt', 'custom-fields', 'revisions', 'genesis-cpt-archives-settings' )       
    );
  
  register_post_type( 'yoga_teacher', $args);
}

// REARRANGE ADMIN MENU ORDER

add_filter('custom_menu_order', 'my_custom_menu_order');
add_filter('menu_order', 'my_custom_menu_order');

function my_custom_menu_order($menu_ord) {
    if (!$menu_ord) return true;
    return array(
        'index.php', // the dashboard link
        'edit.php?post_type=tribe_events',
        'edit.php?post_type=product',
        'edit.php?post_type=testimonial',
        'edit.php?post_type=yoga_teacher',
        'edit.php?post_type=page', 
        'admin.php?page=nestedpages',
        'edit.php', // posts
        'edit-comments.php',
        'admin.php?page=gf_edit_forms'
    );
}

// Add Custom Post Type to WP-ADMIN Right Now Widget
// Ref Link: http://wpsnipp.com/index.php/functions-php/include-custom-post-types-in-right-now-admin-dashboard-widget/
// http://wordpress.org/support/topic/dashboard-at-a-glance-custom-post-types
// http://halfelf.org/2012/my-custom-posttypes-live-in-mu/
function vm_right_now_content_table_end() {
    $args = array(
        'public' => true ,
        '_builtin' => false
    );
    $output = 'object';
    $operator = 'and';
    $post_types = get_post_types( $args , $output , $operator );
    foreach( $post_types as $post_type ) {
        $num_posts = wp_count_posts( $post_type->name );
        $num = number_format_i18n( $num_posts->publish );
        $text = _n( $post_type->labels->name, $post_type->labels->name , intval( $num_posts->publish ) );
        if ( current_user_can( 'edit_posts' ) ) {
            $cpt_name = $post_type->name;
        }
        echo '<li class="post-count"><tr><a href="edit.php?post_type='.$cpt_name.'"><td class="first b b-' . $post_type->name . '"></td>' . $num . '&nbsp;<td class="t ' . $post_type->name . '">' . $text . '</td></a></tr></li>';
    }
    // $taxonomies = get_taxonomies( $args , $output , $operator );
    // foreach( $taxonomies as $taxonomy ) {
    //     $num_terms  = wp_count_terms( $taxonomy->name );
    //     $num = number_format_i18n( $num_terms );
    //     $text = _n( $taxonomy->labels->name, $taxonomy->labels->name , intval( $num_terms ));
    //     if ( current_user_can( 'manage_categories' ) ) {
    //         $cpt_tax = $taxonomy->name;
    //     }
    //     echo '<li class="post-count"><tr><a href="edit-tags.php?taxonomy='.$cpt_tax.'"><td class="first b b-' . $taxonomy->name . '"></td>' . $num . '&nbsp;<td class="t ' . $taxonomy->name . '">' . $text . '</td></a></tr></li>';
    // }
}
add_action( 'dashboard_glance_items' , 'vm_right_now_content_table_end' );
?>

Note that the last bit of code displays ALL custom post types in the “At a Glance” section, not just the yoga_teacher type. For more control over the display, try the Glance That plugin.

Set up yoga teacher field group

I’m going to assume for the sake of this tutorial that you know how to use Advanced Custom Fields. If not, there are plenty of other tutorials on how to set up field groups.

I created fields for Teacher First Name, Teacher Last Name, Nickname or Alternate Name, Teacher URL, City, State, and Country, and set them to display only on the yoga_teacher post type and to hide the normal post edit screen.

ACF field group for yoga teacher fields

That resulted in a yoga teacher edit screen like this:

Edit screen for yoga teacher post type

Create a custom page_teacher_directory template

In this case, I didn’t even want to show the single entries, because all the content was there on the directory page. And I also wanted the directory to show up in the normal page hierarchy, because of the way the sidebar menus were being generated and because site structure and page hierarchy are becoming more important to SEO.

This one took me a little fiddling, mainly because I’m not all that good at nested conditionals but didn’t want to display the optional fields if they were empty, but here’s what I came up with:

<?php
/**
 * Template Name: Teacher Directory Page
 * @author WP Fangirl
 * @package Enviable
 * @subpackage Customizations
 */

// Add teacher directory loop
add_action( 'genesis_loop', 'rby_teacher_directory_loop' );
function rby_teacher_directory_loop() {
	echo '<div class="teacher-directory-wrap">';
	$query = new WP_Query( array(
		'post_type' => 'yoga_teacher',
		'post_status' => 'publish',
		'meta_key' => 'teacher_last_name',
		'orderby'=> 'meta_value',
		'order' => 'ASC',
		'paged' => get_query_var( 'paged' ),
		'posts_per_page' => 18,
	 ) );
	if ( $query->have_posts() ) :
		while ( $query->have_posts() ) : $query->the_post(); global $post;
		echo '<div class="team-members">';
			// Show Featured Image
				if ( has_post_thumbnail() ) {
				echo '<figure itemprop="image">';
				the_post_thumbnail('thumbnail', array('class' => 'teacher-avatar') );
				echo '</figure>';
				}
			echo '<div class="teacher-name">'; // open teacher-name div
			// Opening H3 tag around teacher name	
				echo '<h3 itemprop="name" class="member">';
			// Link to Teacher URL
				if ( get_field('teacher_url') ) {
					echo '<a href="'. get_field('teacher_url') .'">';
				}
			// Display Teacher Name	
				echo the_field('teacher_first_name');
				echo ' ';
				echo the_field('teacher_last_name') ;
			// Add closing tag to link
				if (get_field('teacher_url')) {
					echo '</a>';
				}
			echo '</h3>'; //Closing H3 tag around teacher name
			// Display Teacher Nickname/Alternate Name
				if (get_field('teacher_nickname')) :
					echo '<h6 class="member">('. get_field('teacher_nickname') .')</h6>';
				endif;
			echo '</div>'; // close teacher-name div
			echo '<div class="teacher-location">'; // Begin location list
			//  Display teacher city if field has a value
				echo '<li class="teacher-city">'. get_field('teacher_city') . '</li>';
			// Display teacher state or region if field has a value
				if (get_field('teacher_state')) :
					echo '<li class="teacher-state">'. get_field('teacher_state') . '</li>';
				endif;
			// Display teacher country if field has a value
				echo '<li class="teacher-country">'. get_field('teacher_country') . '</li>';
			echo '</div>'; // End location list
		echo '</div>'; // Close team-member div
		endwhile;
		genesis_posts_nav();
	endif;
	wp_reset_query();
	echo '</div>'; //close teacher directory wrap
}

genesis();

I applied the template to the existing teacher directory page and added a couple of lines of introductory text.

Add CSS for a responsive grid display of teacher entries

Actually, I had set all this CSS up when I created the original directory, but you’ll need it if you want to duplicate what I did.

/*---------------Teacher Directory Styles ---------------- */
li.teacher-city::after,
li.teacher-state::after {
  font-family: Arial, Helvetica, sans-serif;
  content: '\2c\00a0';
}


div.team-members {
  width:250px;
  height:300px;
  float: left;
  margin-right: 5px;
  justify-content: center;
}

div.teacher-location {
	display:flex;
	justify-content:center;
}

.teacher-city, .teacher-state, .teacher-country {
  color: #333;
}

.teacher-country {
  clear: right;
}

h3.member, h3.member a {
  font-family: 'Open Sans Condensed', Lato, Helvetica, sans-serif;
  font-weight:bold;
  font-size: 24px;
text-align:center;
  margin-bottom:4px;
}

h3.member a, h6.member a {
  color: #e96d5d;
}
h6.member {
  font-family: 'Open Sans Condensed', Lato, Helvetica, sans-serif;
  text-align:center;
  margin-top:4px;
  margin-bottom:4px;
}

.team-members figure {
  max-width:250px;
  justify-content: center;
}

img.teacher-avatar {
  clear:both;
margin: 0 auto;
}

The flex display on the div.team-member saved me writing a few media queries and helped center everything. (Please, anyone who sees anything I can improve, let me know.)

That gives me a directory page with three teachers across in desktop view, since the page has a sidebar and the main content area is 850 px wide.

yoga teacher directory created with Genesis, ACF, and Ultimate WP Query Search Filter

The teachers stack nicely as the page shrinks, showing two across on medium tablet screens and one across on phone screens.

teacher directory on a tablet screen
768px

Install the Ultimate WP Query Search Filter plugin and set up a custom field search

For some reason, when I added the facetwp-template class to the page, however, I got an error message saying “FacetWP is unable to auto-detect the main query.” I had followed the directions for using FacetWP with existing template files, but it wasn’t working. If I took out the facetwp-template class, I could see the directory, but the “Narrow Search” widget in the sidebar wouldn’t display anything in the dropdowns. (Yes, I re-indexed.) This might have been due to something besides FacetWP, but after wrestling with it unsuccessfully, I decided to look for another option.

A little Google Fu uncovered the Ultimate WP Query Search Filter plugin (UWPQSF for short). Setting up the search was the easy part, because the search builder already works with ACF fields (and other custom fields). I set the Ajax search template to display the results in the same div as the original directory (.teacher-directory-wrap), then set up the meta fields for city, state, and country. I decided to display them as dropdowns to keep the search form compact.

Building an Ultimate WP Query Search Filter search

To show the search box on the page, I just pasted the shortcode (in this case [[ULWPQSF id=6364]]) into the content metabox of the page edit screen.

Customize the search box display

After I’d added the search box to the page, I needed a little custom CSS to match the box to the theme and display the field drop-downs in a row rather than a column.

/* Ultimate WP Search Query Styles 
-------------------------------------------------- */
#uwpqsf_id{
  border:1px solid #efefef;
  padding:8px;
}
.uform_title {
	color: #e96d5d;
	font-family: 'Open Sans Condensed', 'Lato', Arial, Helvetica, sans-serif;
	font-weight: 600;
	font-size:22px;
}

.uwpqsf_class {
  float:left;
  width: 31.623931623931625%;
  margin-bottom:5px;
  margin-left:8px;
  clear:none;
}

.uwpqsf_class::last-child {
  float:right;
  margin-left:0;
}
.uwpqsf_submit {
  width:100%;
  clear:both;
  text-align:center;
  margin:15px 0;
}
@media only screen and (max-width: 768px) {
  .uwpqsf_class {
    float:none;
    width: 100%;
    margin-left: 0;
    clear:both;
  }
}

That gives us a search box that looks like this on larger screens…

Ultimate WP Query Search Filters search box for yoga teacher directory

…and like this on smaller screens:

mobile view of Ultimate WP Query Search Filters search box for yoga teacher directory

So far, so good. But the search results showed up in a column with all the wrong formatting and none of the custom fields, which leads me to the final step of this process.

Customize the Ajax search results to display the same fields and classes as the original query

The author of Ultimate WP Query Search Filter created one and only one hook in his plugin, but fortunately, it was the one I needed and he had some sample code on his website. After pasting it into my functions.php file (I wondered about putting it into the page template but wasn’t sure it would work there), I modified the query to be exactly the same as the one for the page template itself. This meant I didn’t have to write any new CSS for the results display. The code looked like this:

// Customize Ajax Search Results for Ultimate WP Query Search

add_filter('uwpqsf_result_tempt', 'customize_output', '', 4);
function customize_output($results , $arg, $id, $getdata ){
	 // The Query
            $apiclass = new uwpqsfprocess();
             $query = new WP_Query( $arg );
		ob_start();	$result = '';
			// The Loop

		if ( $query->have_posts() ) {
			while ( $query->have_posts() ) {
				$query->the_post();global $post;
echo '<div class="team-members">';
			// Show Featured Image
				if ( has_post_thumbnail() ) {
				echo '<figure itemprop="image">';
				the_post_thumbnail('thumbnail', array('class' => 'teacher-avatar') );
				echo '</figure>';
				}
			echo '<div class="teacher-name">'; // open teacher-name div
			// Opening H3 tag around teacher name	
				echo '<h3 itemprop="name" class="member">';
			// Link to Teacher URL
				if ( get_field('teacher_url') ) {
					echo '<a href="'. get_field('teacher_url') .'">';
				}
			// Display Teacher Name	
				echo the_field('teacher_first_name');
				echo ' ';
				echo the_field('teacher_last_name') ;
			// Add closing tag to link
				if (get_field('teacher_url')) {
					echo '</a>';
				}
			echo '</h3>'; //Closing H3 tag around teacher name
			// Display Teacher Nickname/Alternate Name
				if (get_field('teacher_nickname')) :
					echo '<h6 class="member">('. get_field('teacher_nickname') .')</h6>';
				endif;
			echo '</div>'; // close teacher-name div
			echo '<div class="teacher-location">'; // Begin location list
			//  Display teacher city if field has a value
				echo '<li class="teacher-city">'. get_field('teacher_city') . '</li>';
			// Display teacher state or region if field has a value
				if (get_field('teacher_state')) :
					echo '<li class="teacher-state">'. get_field('teacher_state') . '</li>';
				endif;
			// Display teacher country if field has a value
				echo '<li class="teacher-country">'. get_field('teacher_country') . '</li>';
			echo '</div>'; // End location list
		echo '</div>'; // Close team-member div
			}
                        echo  $apiclass->ajax_pagination($arg['paged'],$query->max_num_pages, 4, $id, $getdata);
		 } else {
					 echo  'no post found';
				}
				/* Restore original Post Data */
				wp_reset_postdata();

		$results = ob_get_clean();		
			return $results;
}

And the search results (in this case filtered for all yoga teachers in California) look like this:

yoga teacher directory filtered to show only teachers in Californi

The whole thing took me more hours than I’d like to put together, but the working version is not really that complicated, and I will likely re-use this code as the basis of other directories in the future. ACF makes it easy to set up different fields for different types of directories, which can then serve the client’s needs better than a generic plugin. (Though for a typical “meet the team” page the plugins are just fine.)

If you can show me how to do this better, by all means do so!

Related Items

  • screenshot of the new menu order function in SublimeText
    Help! My Custom Post Type Disappeared from the Admin Menu!
  • It’s WordPress Plugin Developer Donation Day!
  • screenshot: Shimmer Theme demo page
    Adding a CSS Background to the Shimmer Child Theme for Genesis

Share this post:

Share on Twitter Share on Facebook Share on Pinterest Share on LinkedIn Share on Email

Filed Under: Using WordPress Tagged With: Tutorial, ACF, Genesis Theme Framework

Reader Interactions

Comments

  1. Estefani Rangel says

    December 28, 2016 at 11:56 pm

    Thanks for your tip! It helped me a lot in a recent project.

    Reply
  2. Sophia says

    May 12, 2019 at 10:47 pm

    this was an awesome help. Thank you

    Reply
    • Sallie Goetsch says

      May 13, 2019 at 8:19 am

      I just had to rebuild it with Search and Filter Pro, because UWPQSF hasn’t been updated and had stopped working. I hope to have time to write about that.

      Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Primary Sidebar

What I Write About

  • Book Reviews
  • Content Strategy
  • Design
  • Hosting and Servers
  • Most Valuable Plugins
  • There's a Plugin for That
  • Using WordPress
  • Widgets
  • WordPress Consulting
  • WordPress Events

Series

  • Interviews (5)
  • Checking Up on Your Website (4)
  • Client from Hell (5)
  • WordCamps (17)
  • WP-Tonic Roundtable (30)
  • Modern Tribe Tutorials (13)

Follow Sallie on Twitter

    Sorry, no Tweets were found.

RSS Latest News from the East Bay WordPress Meetup

  • Does It Work? Using The New CSS Layout with Rachel Andrew
    Things change rapidly in the WordPress world. The content in this post is more than a year old and may no longer represent best practices.Description Over the past two years, […] The post Does It Work? Using The New CSS Layout with Rachel Andrew appeared first on East Bay WordPress Meetup.
  • Speaker Training
    Get the workbook and slides for the October 2019 speaker training, plus background and pro tips. The post Speaker Training appeared first on East Bay WordPress Meetup.
  • SEO Audit Template & Resources
    Our November speaker, John Locke, graciously provided a template for an SEO audit report. You can download it as a Microsoft Word or PDF document. The post SEO Audit Template & Resources appeared first on East Bay WordPress Meetup.

Footer

Contact Info

2063 Main St #133 · Oakley, CA 94561

+1 (510) 969-9947

author-izer

sallie [at] wpfangirl [dot] com

Location

Map of East Contra Costa County

I live in Oakley, CA and run a WordPress Meetup in Oakland, CA. Don't confuse them!

Subscribe for New Posts

  • Since I blog on an unpredictable schedule, you might want to subscribe by email. I'll also send out occasional announcements about events.

  • Privacy Policy: I will never sell or rent your contact information.

  • This field is for validation purposes and should be left unchanged.
  • Contact
  • Colophon
  • Comment Policy
  • Privacy Policy
  • Five for the Future

Copyright © 2023 · Utility Pro on Genesis Framework · WordPress · Log in

MENU
  • Home
  • About
  • Why WordPress?
  • How I Work
  • Portfolio
  • Services
  • Blog
  • Contact