WordPress does an excellent job highlighting current standard posts, pages or taxonomies when you include them in a navigation menu. But when you create a custom post or custom taxonomy everything goes wrong and the navigation stops highlighting the current page. Fortunately there is a workaround, you can manually specify which menu element highlights when you are showing custom content.
How This Will Work
The solution is simple. We've written a few lines of code that create a settings page were you will specify the menu elements to highlight for every custom content type. The next step is to override the default WordPress navigation Walker class to generate a highlight class when needed. Simple and effective.
Step 1 Creating and Using a Custom Include File
Create a new file called navigation.php and include it from the functions.php file.
include_once ( get_template_directory() . '/navigation.php' );
Now we're ready to start with the real code.
Step 2 Creating the Settings Page
First, register a new settings group to generate a new wp-admin settings page. In your empty navigation.php file insert the following code.
add_action( 'admin_init', 'ns_register_navigation_settings' ); function ns_register_navigation_settings() { register_setting( 'ns_navigation', 'ns_navigation_predefined_values' ); }
Then generate a new menu element to access our new settings page in wp-admin.
add_action('admin_menu', 'ns_navigation_options'); function ns_navigation_options() { add_submenu_page( 'themes.php', 'Predefined Menus', 'Predefined Menus', 'edit_theme_options', 'menu-defaults', 'menu_defaults_page' ); }
The menu_defaults_page() function prints the settings page inside WordPress Admin. Before printing the form inputs get_option('ms_navigation_predefined_values') requests the values stored in the database and stores them in $ns_navigation_predefined_values as an array.
In this case there's nothing stored yet so the values are empty. Using settings_field() is required for printing related and required hidden fields and for security handling too. The rest of the code prints the input elements using the values in $ns_navigation_predefined_values.
The settings page is now available but empty. We need to populate it with all the available custom posts and taxonomies that have been generated and the available menu elements to match those values. Insert the following code.
function menu_defaults_page() { ?> <div class="wrap"> <div class="icon32" id="icon-options-general"><br></div> <h2><?php _e('Predefined menus for custom posts and taxonomies'); ?></h2> <form method="post" action="options.php"> <?php $ns_navigation_predefined_values = get_option('ns_navigation_predefined_values'); settings_fields( 'ns_navigation' ); ?> <h3 class="title"><?php _e('Pages'); ?></h3> <table class="form-table" cellpadding="0" cellspacing="0"> <?php foreach (ns_get_post_types() as $k => $v) { ?> <tr valign="top"> <th scope="row"><?php echo $v ?></th> <td> <?php $current_dropdown_value = get_option('ns_navigation_predefined_values'); wp_dropdown_pages( array( 'name' => 'ns_navigation_predefined_values[' . $k . ']', 'echo' => 1, 'show_option_none' => __( '— Select —' ), 'option_none_value' => '0', 'selected' => $current_dropdown_value[$k] ) ); ?> </td> </tr> <?php } ?> </table> <?php if (ns_get_taxonomies()): ?> <br /><hr /> <h3 class="title"><?php _e('Categories') ?></h3> <table class="form-table" cellpadding="0" cellspacing="0"> <?php foreach (ns_get_taxonomies() as $k => $v) { ?> <tr valign="top"> <th scope="row"><?php echo $v ?></th> <td> <?php $current_dropdown_value = get_option('ns_navigation_predefined_values'); wp_dropdown_pages( array( 'name' => 'ns_navigation_predefined_values[' . $k . ']', 'echo' => 1, 'show_option_none' => __( '— Select —' ), 'option_none_value' => '0', 'selected' => $current_dropdown_value[$k] ) ); ?> </td> </tr> <?php } ?> </table> <?php endif; ?> <p class="submit"> <input type="submit" class="button-primary" value="<?php _e('Update'); ?>" /> </p> </form> </div> <?php }
The settings page is now created but we still need to define the functions called in the code above. Insert the following code.
function ns_get_post_types() { $post_types = get_post_types(array('public' => true, '_builtin' => false), 'objects'); foreach ( $post_types as $k => $v ) { $ns_registered_post_types->$k = $v->labels->name; } return $ns_registered_post_types; } function ns_get_taxonomies() { $taxonomies_types = get_taxonomies(array('public' => true, '_builtin' => false), 'objects'); foreach ( $taxonomies_types as $k => $v ) { $ns_registered_taxonomies_types->$k = $v->labels->name; } return $ns_registered_taxonomies_types; }
The function ns_get_post_types retrieves all the available post types and outputs only those that are custom. The function ns_get_taxonomies does the same, but for taxonomies of course.
Step 3 Making It Work in the WordPress Theme
We have the settings page declared and a few custom posts and taxonomies declared. The next step is to make it work in the theme we're using. For testing purposes we're working with WordPress' Twenty Eleven theme but this code should work with any theme.
Let's modify the WordPress Menu Walker class to override the default output. We're reading our settings and using the values to add a new current_page_item ns_current_page_item class in the respective page when we're displaying the custom post loop or related single page.
class NS_Walker_Nav_Menu extends Walker_Nav_Menu { function start_el(&$output, $item, $depth, $args) { global $wp_query; $indent = ( $depth ) ? str_repeat( "\t", $depth ) : ''; if ( isset( $args->current_nav_element ) ) { $current_nav_element = $args->current_nav_element; } $class_names = $value = ''; $classes = empty( $item->classes ) ? array() : (array) $item->classes; $classes[] = 'menu-item-' . $item->ID; $classes[] = 'page-gui-' . $item->object_id; $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args ) ); $class_names = ' class="' . esc_attr( $class_names ); if ($current_nav_element == $item->object_id) { $class_names.= ' current_page_item ns_current_page_item'; } $class_names.= '"'; $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args ); $id = strlen( $id ) ? ' id="' . esc_attr( $id ) . '"' : ''; $output .= $indent . '<li' . $id . $value . $class_names .'>'; $attributes = ! empty( $item->attr_title ) ? ' title="' . esc_attr( $item->attr_title ) .'"' : ''; $attributes .= ! empty( $item->target ) ? ' target="' . esc_attr( $item->target ) .'"' : ''; $attributes .= ! empty( $item->xfn ) ? ' rel="' . esc_attr( $item->xfn ) .'"' : ''; $attributes .= ! empty( $item->url ) ? ' href="' . esc_attr( $item->url ) .'"' : ''; $item_output = $args->before; $item_output .= '<a'. $attributes .'>'; $item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after; $item_output .= '</a>'; $item_output .= $args->after; $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args ); } }
The new NS_Walker_Nav_Menu Class reads the navigation values stored in an array before printing. In this case using an if()
control structure to evaluate if the current navigation element matches with the previous stored value for the page WordPress is currently printing. If the condition is true then the classes "current_page_item" and "ns_current_page_item" are added to the existing classes stored in the $class_names variable.
Then we need to use one more custom function. When we call it, this function will print the menu in the theme.
function ns_wp_nav_menu($args) { global $post; $ns_walker = new NS_Walker_Nav_Menu(); $args['walker'] = $ns_walker; $ns_navigation_predefined_values = get_option('ns_navigation_predefined_values'); $custom_post_type = get_post_type($post); $available_post_types = (array) ns_get_post_types(); $taxonomy_type = get_queried_object(); $taxonomy_type = $taxonomy_type->taxonomy; $available_taxonomy_types = (array) ns_get_taxonomies(); if (is_singular($custom_post_type) && array_key_exists($custom_post_type, $available_post_types)) { $args['current_nav_element'] = (function_exists('icl_object_id')) ? icl_object_id($ns_navigation_predefined_values[$custom_post_type], 'page') : $ns_navigation_predefined_values[$custom_post_type]; } elseif (is_tax($taxonomy_type) && array_key_exists($taxonomy_type, $available_taxonomy_types)) { $args['current_nav_element'] = (function_exists('icl_object_id')) ? icl_object_id($ns_navigation_predefined_values[$taxonomy_type], 'page') : $ns_navigation_predefined_values[$taxonomy_type]; } else { unset($args['current_nav_element']); } wp_nav_menu($args); }
The ns_wp_nav_menu() is created to simplify the use of the built-in wp_nav_menu(). The first step is to force the function to load the new Walker class using $ns_walker = new NS_Walker_Nav_Menu() and adding to the parameters array using $args['walker'] = $ns_walker;.
Instead of always passing the required parameters to the function this is included for default. In this specific case the function reads the current post and even reads the translated page if the WPML plugin is enabled on your WordPress website.
First evaluate if the page is in single view using is_singular() and get from the database the corresponding stored value. The second possible choice to evaluate is if the current page is a taxonomy query using is_tax(). If not, then there is nothing to select and the code releases the current navigation element using unset($args['current_nav_element'])
Step 4 Printing the Navigation Menu in a WordPress Theme
Open the header.php file in your Twenty Eleven theme, find the wp_nav_menu() function, approximately on Line 118, and replace with ns_wp_nav_menu keeping the same parameters and nothing else because the new function handles the rest of the required parameters by default. The new code should look like this:
<?php ns_wp_nav_menu( array( 'container_class' => '', 'theme_location' => 'primary' ); ?>
This function uses the same arguments as the standard wp_nav_menu function so feel free to tweak as much as you want or need to.
Open style.css too and replace the code on line 617 with:
#access .current-menu-item > a, #access .current-menu- ancestor > a, #access .current_page_item > a, #access .current_page_ancestor > a, #access .ns_current_page_item > a { font-weight: bold; }
Step 5 Get the Most From Your Enhanced Navigation System
You have custom posts, custom taxonomies and you have created new pages with templates to show these custom loops. You probably have created a new menu in your wp-admin and added those pages too. Open the predefined menus settings page located under Appearance and set the selected pages for every custom post and taxonomy you have created.
When you display the custom post or the single page related to this custom post the navigation will highlight the matched menu element.
Conclusion
There are many ways to achieve this same result but after a few published projects using this approach I found this is the best and most user-friendly.
Anyway this is only the beginning of all the possibilities you can achieve when you understand this code and start making modifications for your personal needs.
I encourage you to keep researching about the navigation Walker class. There are a lot of possibilities hidden in there, you can bet on it.
Comments