phphtmlwordpresswp-nav-walker

Walker_Nav_Menu() for top level links only


I have a menu in the WordPress backend that looks like this:

What we do
  Solutions
    Solution 1
    Solution 2
  Services
    Service 1
Contact

Items What we do and Contact are top level links and I only want these level 1 links to show in my main header.

As such, I created a custom walker to achieve this:

<?php

class simple_header extends Walker_Nav_Menu{

  /*
  * start_lvl : Responsible for start of a new level, such as <ul>
  * end_lvl : Responsible for end of a level, such as </ul>
  * start_el : Responsible for start of inner element, such as <li>
  * end_el : Responsible for end of inner element, such as <\li>
  */

    public function start_lvl( &$output, $depth = 0, $args = array() ) {
      $indent = str_repeat("\t", $depth);
    }

    public function end_lvl( &$output, $depth = 0, $args = array() ) {
      $indent = str_repeat("\t", $depth);
    }

    public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';


        $classes = empty( $item->classes ) ? array() : (array) $item->classes;
        $classes[] = $item->ID;

        $class_names = 'header__nav-menu-li';

        if (in_array('current-menu-item', $classes)) {
          $class_names .= ' header__nav-menu-li--active';
        }

        $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';

        if ($depth === 0) {
          $output .= $indent . '<li data-test' . $class_names .'>';
        }

        $atts = array();
        $atts['title']  = ! empty( $item->attr_title ) ? $item->attr_title : '';
        $atts['target'] = ! empty( $item->target )     ? $item->target     : '';
        $atts['rel']    = ! empty( $item->xfn )        ? $item->xfn        : '';
        $atts['href']   = ! empty( $item->url )        ? $item->url        : '';

        // New
        if ($depth === 0) {
          $atts['class'] = 'header__nav-menu-anchor link-header';
        }

        if (in_array('current-menu-item', $item->classes)) {
          $atts['class'] .= ' link-header--active';
        }

        $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth );

        $attributes = '';
        foreach ( $atts as $attr => $value ) {
          if ( ! empty( $value ) ) {
            $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
            $attributes .= ' ' . $attr . '="' . $value . '"';
          }
        }

        $item_output = $args->before;

        $item_output .= '<a'. $attributes .'>';
        /** This filter is documented in wp-includes/post-template.php */
        $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 );
    }

    public function end_el( &$output, $item, $depth = 0, $args = array() ) {
      if ($depth === 0) {
        $output .= "</li>\n";
      }
    }
}

?>

However, with the above walker, it still prints out the child markup. The HTML looks like this:

<li class="header__nav-menu-li header__nav-menu-li--active">
  <a href="#" class="header__nav-menu-anchor link-header link-header--active">What we do</a>
  <a href="#">Solutions</a>
  <a href="#">Services</a>
</li>

I have defined $depth === 0 in my walker, so unsure why it's printing child links?


Solution

  • Normally, at the start_lvl and end_lvl you would output <ul> and </ul> tags respectively to start and end a new list.

    Every item in your menu consists of an anchor tag wrapped inside a list item:

    <li><a href="target-url">Target</a></li>

    In your code you only have an 'if' statement around the code that adds the list item tags to the output.

    On the last line of your start_el function you still add the anchor.

    Instead of wrapping an if statement around the parts where you want to output markup, my advice would be to add the following line of code as the first line of your start_el function:

    if($depth !== 0) { return; }

    That way you skip execution of code for items that are not in the first level.