Create a Custom WordPress Menu Walker, Extending Walker_Nav_Menu Class

If your website has a large navigation menu, and you would like to display part of this menu elsewhere on the website, this article will explain how to do. We could create this menu on each page manually, but why do this when we can get WordPress to set the correct states for each item. This article will take you through creating a custom WordPress Walker_Nav_Menu, which will be passed as an argument when outputting the navigation menu wp_nav_menu.

Extending the Walker_Nav_Menu class

Lets create a new class in the functions.php file within your theme directory. TIP – when creating functions or classes it is best to prefix these decelerations, you will notice in my examples they are prefixed with JC. This helps to reduce the chance of conflicting functions and scripts from other plugins which are installed on your WordPress Website.

class JC_Walker_Nav_Menu extends Walker_Nav_Menu {
}

Since we are extending the core WordPress Walker_Nav_Menu class, we can override all class functions, the class we are extending can be viewed within your wordpress folder /wp-admin/includes/nav-menu.php. In this example I will remove the sub menus, and produce one flat list of all the menu items, this can be done by overriding start_lvl and end_lvl functions as shown below, if you want to keep the multilevel lists, just comment out or remove these functions.

class JC_Walker_Nav_Menu extends Walker_Nav_Menu {
 
	function start_lvl( &$output, $depth = 0, $args = array() ) {}
 
	function end_lvl( &$output, $depth = 0, $args = array() ) {}
}

To expand on the previous code we need to pass an integer to our Walker class, to do this we create a __construct() function, and pass the integer argument to a class wide variable, and add it to an array of selected menu items, called $menu_items. No we have the menu item we wish to show along with all its children we can now override start_el and end_el, checking to see if the current menu item is part of the chosen menu, otherwise don’t output.

class JC_Walker_Nav_Menu extends Walker_Nav_Menu {
 
	var $menu_id = 0;
	var $menu_items = array();
 
	function __construct($id = 0){
		$this->menu_id = $id;
		$this->menu_items[] = $id;
	}
 
	function start_el( &$output, $item, $depth, $args ) {
		if( !empty($this->menu_items) && !in_array($item->ID, $this->menu_items) && !in_array($item->menu_item_parent, $this->menu_items)){
			return false;
		}
 
		if(!in_array($item->ID, $this->menu_items)){
			$this->menu_items[] = $item->ID;
		}
 
		parent::start_el($output, $item, $depth, $args);
	}
 
	function end_el( &$output, $item, $depth = 0, $args = array() ) {
		if( !empty($this->menu_items) && !in_array($item->ID, $this->menu_items) && !in_array($item->menu_item_parent, $this->menu_items)){
    			return false;
    		}
    		parent::end_el($output, $item, $depth, $args);
	}
 
	function start_lvl( &$output, $depth = 0, $args = array() ) {}
 
	function end_lvl( &$output, $depth = 0, $args = array() ) {}
}

Using a Custom WordPress Walker

Being an extension of WordPress’s Walker_Nav_Menu class we can output our custom menu with the following snippet, changing menu to the menu you wish to use and 8 to be the id of the menu item you wish to display and all its children.

<?php  wp_nav_menu( array('menu' => 'header-menu', 'walker' => new JC_Walker_Nav_Menu(8)) ); ?>

Creating a Custom Menu Widget

The following section of code, creates a WordPress widget which you can visually choose a section of a menu to be displayed, you will notice when generating the widget options panel i created another custom Walker_Nav_Menu to output all items as select menus.

<?php
class JC_Adv_Menu_Widget extends WP_Widget {
 
	public function __construct() {
		parent::__construct(
	 		'jc_menu_widget', // Base ID
			'Advanced Menu Widget', // Name
			array( 'description' => __( 'Custom options to output menus in your theme')) // Args
		);
	}
 
	public function widget( $args, $instance ) {
		extract( $args );
 
		$title = apply_filters( 'widget_title', $instance['title'] );
 
		echo $before_widget;
 
		if ( ! empty( $title ) )
			echo $before_title . $title . $after_title;
 
		wp_nav_menu( array('menu' => $instance['menu'], 'walker' => new JC_Walker_Nav_Menu($instance['menu_item'])) );
 
		echo $after_widget;
	}
 
 	public function form( $instance ) {
 
		$title = isset($instance['title']) ? $instance['title'] : '';
		$menu = isset($instance['menu']) ? $instance['menu'] : '';
		$menu_item = isset($instance['menu_item']) ? $instance['menu_item'] : '';
		$menus = get_terms('nav_menu');
 
		?>
		<p>
		<label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label>
		<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />
		</p>
 
		<p>
		<input class="widefat" id="<?php echo $this->get_field_id( 'menu' ); ?>" name="<?php echo $this->get_field_name( 'menu' ); ?>" type="hidden" value="<?php echo $menu; ?>" />
		</p>
 
		<p>
			<label for="<?php echo $this->get_field_id( 'menu_item' ); ?>"><?php _e( 'Select Menu Part:' ); ?></label>
			<select class="widefat" id="<?php echo $this->get_field_id( 'menu_item' ); ?>" name="<?php echo $this->get_field_name( 'menu_item' ); ?>" >
				<?php foreach($menus as $m){
					wp_nav_menu(array(
					  'menu' => $m->slug, // your theme location here
					  'container' => false,
					  'walker'         => new Walker_Nav_Menu_Dropdown($menu_item),
					  'items_wrap'     => '<optgroup id="'.$m->slug.'" label="'.$m->name.'">%3$s</optgroup>',
					));
				} ?>
			</select>
		</p>
 
		<script type="text/javascript">
		jQuery(document).ready(function($) {
			$('select#<?php echo $this->get_field_id( 'menu_item' ); ?>').change(function(){
				var label=$('select#<?php echo $this->get_field_id( 'menu_item' ); ?> :selected').parent().attr('id');
			    $('#<?php echo $this->get_field_id( 'menu' ); ?>').val(label);
			});
		});
		</script>
 
		<?php
	}
 
	public function update( $new_instance, $old_instance ) {
		$instance = array();
 
		$instance['title'] = strip_tags( $new_instance['title'] );
		$instance['menu'] = strip_tags( $new_instance['menu'] );
		$instance['menu_item'] = strip_tags( $new_instance['menu_item'] );
 
		return $instance;
	}
 
}
 
add_action( 'widgets_init', create_function( '', 'register_widget( "JC_Adv_Menu_Widget" );' ) );
 
 
class Walker_Nav_Menu_Dropdown extends Walker_Nav_Menu{
 
	var $item_id = 0;
 
	function __construct($id = 0){
		$this->item_id = $id;
	}
 
	public function start_lvl(&$output, $depth){}
 
	public function end_lvl(&$output, $depth){}
 
	public function start_el(&$output, $item, $depth, $args){
 
		$item->title = str_repeat("&nbsp;", $depth * 4) . $item->title;
 
		parent::start_el($output, $item, $depth, $args);
		if($item->ID == $this->item_id)
			$output = str_replace('<li', '<option value="'.$item->ID.'" selected="selected"', $output);
		else
			$output = str_replace('<li', '<option value="'.$item->ID.'"', $output);
	}
 
	public function end_el(&$output, $item, $depth){
		$output .= "</option>n";
	}
}
?>

Check out my JC Submenu plugin to dynamically populate your wordpress menus with custom post types, taxonomies, and pages.



Liked this article? help spread the word.