Setting Active Navigation Elements When WordPress Doesn’t

Setting Active Navigation Elements When WordPress Doesn’t

Tutorial Details
  • Program: WordPress
  • Version: Latest
  • Difficulty: Beginner - Intermediate
  • Estimated Completion Time: ~30 mins

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' => __( '&mdash; Select &mdash;' ), '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' => __( '&mdash; Select &mdash;' ), '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.

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://www.deluxeblogtips.com Rilwis

    I haven’t met this situation but I think this is a nice idea! Why don’t we create this functionality for all custom post types and custom taxonomies? Or even better, for custom links as well?

  • http://www.webmentor.cr/ Marco Berrocal

    I have and deployed the site with this scenario. I had to remove the current elements altogether from the design. Wish you had wrote this sooner :)

    Oh well.

  • http://mesopinions.ca Francois

    I think this tutorial is missing an excerpt, the whole tutorial is shown on the main page.

    • http://wp.envato.com/ Japh Thomson
      Staff

      Hi Francois, thanks for the heads up! My bad, but all fixed now :)

  • Pingback: Setting Active Navigation Elements When WordPress Doesn’t | Shadowtek | Hosting and Design Solutions

  • http://weblusive.com Weblusive

    Really nice tutorial! Will come very handy in WP theme development process. Thanks for this, was extremely helpful.

  • http://www.windkr89.nl Erik

    How cool is this! I had several projects where I could use this. Almost every client thinks this is as simple as selecting a checkbox in the settings of the website, or something like that. Only wish you had written this earlier ;)

  • PLHaywood

    Looks absolutely lovely!

    I’m trying to implement this and seem to be coming up against a wall. The navigation.php file is functioning properly up until attempting to print the navigation menu. I could definitely be wrong, but that most of it is working properly since it creates a “Predefined Menus” section in wp-admin but breaks the site when calling the ns_wp_nav_menu in the header.

    When trying to print the menu, I receive these errors:

    Notice: Undefined property: stdClass::$taxonomy in /home/sites/sbaldwin.co.uk/public_html/client/cg/wp-content/themes/concrete-geometries-0.3.2/navigation.php on line 181

    Fatal error: Call to undefined function emptyempty() in /home/sites/sbaldwin.co.uk/public_html/client/cg/wp-content/themes/concrete-geometries-0.3.2/navigation.php on line 133

    I’m pretty confused at this point, any advice would be wonderful to have.

    At any rate, thanks for providing the code, I’m basically self taught and have learned quite a lot from looking it over.

  • PLHaywood

    Should have looked a teensy bit harder before I commented, of course “emptyempty(” is not a function. Once I changed it to “empty(” things seem to work just great.

    Might want to change it in the tutorial though!

  • Ivano

    Hi, this is exactly like what I was looking for! Would it be possible to implement it on the Thematic framework or it is specific to Twenty Eleven? There is no direct reference to wp_nav_menu() in Thematic’s library/header-extensions.php.

    Thanks!

  • http://www.fatcatstrategies.com jmob

    This may not be appropriate to post here….but how much would you charge to do this to a theme I’m working on?

    I’ve followed all the steps, and got 80% there, but I can’t seem to get the final tweaks to my theme to work without major failure.

    Thanks!

  • Andy

    Got this working but…

    I’m using a bought Themeforest portfolio template.

    The portfolio items go in as posts. The portfolio gallery is displayed by using short code on a ‘page’ item and I have used Predefined menus to highlight ‘Portfolio’ in the main navigation.

    But under Settings > Reading if I set a static posts page it overrides my Predefined menus settings and the ‘Portfolio’ page is highlighted as the ‘News’ page and not the ‘Portfolio’ page.

    I need to set the posts page as a static page so that all news posts are shown under the highlighted ‘News’ section.

    Is there a way of setting a static news page without overriding the Predefined menus settings?

    Many thanks.

  • http://www.studio-doggus.nl/ Bram

    Nice tutorial! Thanks

  • Elzette Roelofse

    Wow, this is seriously amazing. Works great with the Bones dev theme!