WordPress Menu Custom Walker Class

All we need is an easy explanation of the problem, so here it is.

We are using a jQuery UI accordian to render a menu in the sidebar. We’ve already written the markup and javascript and I am just trying to get the wp_nav_menu to output the same markup we’ve already written. I am creating a custom Walker Class extending Walker_Nav_Menu.

Here is the markup we are going for

<h2><a href="#" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener">Parent Item</a></h2>
<div>
    <ul>
    <li><a href="">Child Item 1</a></li>
    <li><a href="">Child Item 2</a></li>
    <li><a href="">Child Item 3</a></li>
    <li><a href="">Child Item 4</a></li>
    <li><a href="">Child Item 5</a></li>
    </ul>
</div>
<h2><a href="#" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener">Parent Item 2</a></h2>
<div>
    <ul>
    <li><a href="">Child Item 1</a></li>
    <li><a href="">Child Item 2</a></li>
    <li><a href="">Child Item 3</a></li>
    <li><a href="">Child Item 4</a></li>
    <li><a href="">Child Item 5</a></li>
    </ul>
</div>

Unfortunately I cannot get the custom Walker Class to output the markup how I’d like. Any examples or tutorials on the Walker Class would be great. Still haven’t found exactly what I’m looking for on SE or Google yet.

Thanks

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

The easiest way is to extend the Walker_Nav_Menu class rather than the Walker_Class, (as the parent / ID fields are set and often you want to maintain some of the mark-up etc).

The main methods are:

  • start_el / end_el – responsible for displaying an element in a list
  • start_lvl / end_lvl – responsible for displaying a sub menu

Also, there are walk and display_element which largely just control the mechanics of the class. For most purposes the above four functions are those that are needed to be altered in an extending class.

That said, the structure you are after isn’t really nested, so you aren’t really using the Walker Class to its full extent here. However this custom walker class will get you most of the way there:

class SH_Accordian_Walker extends Walker_Nav_Menu {  

    //Start 'sub menu'
    function start_lvl(&$output, $depth=0, $args=array()) {  

        if($depth > 0)
           return parent::end_lvl(&$output, $depth);

        $output .= '<div><ul>';
    }  

    //End 'sub menu'
    function end_lvl(&$output, $depth=0, $args=array()) {  
        if($depth > 0)
           return parent::end_lvl(&$output, $depth);

        $output .= '</ul></div>';
    }  

    // Start element
    function start_el(&$output, $item, $depth=0, $args=array()) {  
        if( 0 == $depth ){
             $output.= '<h2><a href="'.esc_attr($item->url).'" rel="nofollow noreferrer noopener">'.apply_filters( 'the_title', $item->title, $item->ID ).'</a></h2>';
             return; 
        }

        // level 2+
        parent::start_el(&$output, $item, $depth, $args);  
    }  

    // Don't need to add any output - sub menus aren't nested
    function end_el(&$output, $item, $depth=0, $args=array()) {
       if($depth > 0)
         parent::end_el(&$output, $item, $depth);
    }  

} 

This is just a very simply class to demonstrate what you need to do. Since your structure isn’t nested – this might not look right if we go down further than 2 levels (i.e. to grand-children).

Usage:

wp_nav_menu( array( 
       'theme_location' => 'primary',
       'walker'=> new SH_Accordian_Walker, 
       'depth'=>2,
       'items_wrap'=> '%3$s'
     ) );

(I’ve recently written this tutorial on the Walker Class: http://wp.tutsplus.com/tutorials/creative-coding/understanding-the-walker-class/ that you might find helpful)

Method 2

Wish I’d seen your answer before I went down the rabbit hole. However I’ve been able to replicate the markup provided by our designer using an extension of the Walker class and it’s working great with the jQuery UI Accordion.

    class sidebar_nav_walker extends Walker_Nav_Menu {
    function start_el(&$output, $item, $depth, $args) {
      global $wp_query;
      if (0 == $depth) {
        // parent item
        $output .= '<h2>';

        $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        ) .'" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener"' : '';

        $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 );
        $output .= '</h2>';

      } else {
        // child items
        $class_names = $value = '';
        $classes = empty( $item->classes ) ? array() : (array) $item->classes;
        $classes[] = 'menu-item-' . $item->ID;

        $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args ) );
        $class_names = ' class="' . esc_attr( $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        ) .'" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener"' : '';

        $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 );
      }

    }

  function start_lvl(&$output, $depth, $args=array()) {  
      $output .= "\n<div><ul>\n";  
  }

  // Displays end of a level. E.g '</ul>'  
  // @see Walker::end_lvl()  
  function end_lvl(&$output, $depth, $args=array()) { 
      $output .= "</ul></div>\n";  
  }

    /**
     * @see Walker::end_el()
     * @since 3.0.0
     *
     * @param string $output Passed by reference. Used to append additional content.
     * @param object $item Page data object. Not used.
     * @param int $depth Depth of page. Not Used.
     */
    function end_el(&$output, $item, $depth) {

  }
}

That got me pretty far. But I still had an issue with wp_nav_menu wrapping the entire thing in a UL. This broke the jQuery Accordion script. So I needed to remove it and after experimentation ended up rendering the menu with the custom Walker as follows…

  <nav id="nav">
    <?php
      $menu = wp_nav_menu(
        array(
          'container' =>  false,
          'echo'  =>  false,
          'before'  =>  '',
          'after' =>  '',
          'link_before' =>  '',
          'link_after'  =>  '',
          'walker'  =>  new sidebar_nav_walker()
        )
      );
      echo preg_replace( array( '#^<ul[^>]*>#', '#</ul>$#' ), '', $menu );
    ?>
  </nav>

Note: I tell the menu not to echo itself in the arguments. Instead I run a regular expression on it to remove that parent level UL.

Also of note.. This will look like crap if you don’t tell your jQuery Accordion NOT to autoheight.

$(function() {
    $("#nav").accordion({
            collapsible: true,
            autoHeight: false
    });
});

The result is somewhat close to the original markup from my designer, aside from the classes WP adds in.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply