Custom Nav walker display current menu item children, or siblings on no children

I’ve been messing around / searching for hours and still can’t get this to work, so i’m finally giving in and asking for some help.

I’m trying to write a custom walker that shows only the current pages children, or if there are no children display the pages siblings.

Read More

For example, take the following menu tree:

  • 1.0
    • 1.2.0
      • 1.3.0
      • 1.3.1
      • 1.3.2
    • 1.2.1
    • 1.2.2
  • 2.0

Let’s assume that i am on the current page 1.2.0. On this page I want to display it’s children (1.3.0, 1.3.1, 1.3.2)

however, if i’m on the page 1.2.2, since it doesn’t have any children, it should display it’s current level siblings, so it should show me (1.2.0, 1.2.1, 1.2.2).

Related posts

Leave a Reply

2 comments

  1. This is the walker I used to display only children of the current menu item. Or the menu items siblings if it doesn’t have any children of its own.

    There are comments throughout the class explaining each section

    <?php
    
    class SH_Child_Only_Walker extends Walker_Nav_Menu {
    
    private $ID;
    private $depth;
    private $classes = array();
    private $child_count = 0;
    private $have_current = false;
    
    
    // Don't start the top level
    function start_lvl(&$output, $depth=0, $args=array()) {
    
        if( 0 == $depth || $this->depth != $depth )
            return;
    
        parent::start_lvl($output, $depth,$args);
    }
    
    // Don't end the top level
    function end_lvl(&$output, $depth=0, $args=array()) {
        if( 0 == $depth || $this->depth != $depth )
            return;
    
        parent::end_lvl($output, $depth,$args);
    }
    
    // Don't print top-level elements
    function start_el(&$output, $item, $depth=0, $args=array()) {
    
        $is_current = in_array('current-menu-item', $this->classes);
    
        if( 0 == $depth || ! $is_current )
            return;
    
        parent::start_el($output, $item, $depth, $args);
    }
    
    function end_el(&$output, $item, $depth=0, $args=array()) {
        if( 0 == $depth )
            return;
    
        parent::end_el($output, $item, $depth, $args);
    }
    
    // Only follow down one branch
    function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {
    
        // Check if element is in the current tree to display
        $current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' );
        $this->classes = array_intersect( $current_element_markers, $element->classes );
    
        // If element has a 'current' class, it is an ancestor of the current element
        $ancestor_of_current = !empty($this->classes);
    
        // check if the element is the actual page element we are on.
        $is_current = in_array('current-menu-item', $this->classes);
    
        // if it is the current element
        if($is_current) {
    
            // set the count / ID / and depth to use in the other functions.
            $this->child_count = ( isset($children_elements[$element->ID]) ) ? count($children_elements[$element->ID]) : 0;
            $this->ID = $element->ID;
            $this->depth = $depth;
            $this->have_current = true;
    
            if($this->child_count > 0) {
    
                // if there are children loop through them and display the kids.
                foreach( $children_elements[$element->ID] as $child ) {
                    parent::display_element( $child, $children_elements, $max_depth, $depth, $args, $output );
                }
    
            } else {
                // no children so loop through kids of parent item.
                foreach( $children_elements[$element->menu_item_parent] as $child ) {
                    parent::display_element( $child, $children_elements, $max_depth, $depth, $args, $output );
                }
    
            }
        }
    
        // if depth is zero and not in current tree go to the next element
        if ( 0 == $depth && !$ancestor_of_current)
            return;
    
        // if we aren't on the current element proceed as normal
        if(! $this->have_current )
            parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
    }
    }
    

    Attach it like you would with any other custom walker on a wp_nav_menu

    <?php
    wp_nav_menu( array(
        'menu' => 'primary-menu'
        ,'container' => 'nav'
        ,'container_class' => 'subpages'
        ,'depth' => 0
        ,'walker' => new SH_Child_Only_Walker()
     ));
    ?>
    
  2. I had a similar experience. You may want to think about moving the pages logic out of the walker. Basically, compile the current page hierarchy as an object. Then use the ‘exclude’ parameter in the wp_nav_menu function. Now the excluded pages would depend on if the current page has children. If no children show brothers; if children && those children are the end of the line show brothers and children; if children && and grandchildren exist exclude brothers and show children and grandchildren.