0

I kept finding posts that explain how to add even and odd classes to sub-menu items, but can't seem to find how to add it to the main menu items that do not have sub menus.

I've already checked out the following links:

Here's my class:

<?php

if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly

class detailed_walker_nav_menu extends Walker_Nav_Menu {

  // Add First & Last Classes To Navigational Menu Objects
  static function first_last_menu_class( $objects, $args ) {

    // Add first/last classes to nested menu items
    $ids        = array();
    $parent_ids = array();
    $top_ids    = array();
    foreach ( $objects as $i => $object ) {
      // If there is no menu item parent, store the ID and skip over the object
      if ( 0 == $object->menu_item_parent ) {
        $top_ids[$i] = $object;
        continue;
      }

      // Add first item class to nested menus
      if ( ! in_array( $object->menu_item_parent, $ids ) ) {
        $objects[$i]->classes[] = 'first-menu-item';
        $ids[] = $object->menu_item_parent;
      }

      // If we have just added the first menu item class, skip over adding the ID
      if ( in_array( 'first-menu-item', $object->classes ) )
        continue;

      // Store the menu parent IDs in an array
      $parent_ids[$i] = $object->menu_item_parent;
    }

    // Remove any duplicate values and pull out the last menu item
    $sanitized_parent_ids = array_unique( array_reverse( $parent_ids, true ) );

    // Loop through the IDs and add the last menu item class to the appropriate objects
    foreach ( $sanitized_parent_ids as $i => $id )
      $objects[$i]->classes[] = 'last-menu-item';

    // Finish it off by adding classes to the top level menu items
    $objects[1]->classes[] = 'first-menu-item'; // We can be assured 1 will be the first item in the menu :-)
    $objects[array_keys( $top_ids )[count( array_keys( $top_ids ) ) - 1]]->classes[] = 'last-menu-item';

    // Return the menu objects
    return $objects;

  }

  // add classes to ul sub-menus
  function start_lvl( &$output, $depth = 0, $args = array() ) {
    // depth dependent classes
    $indent = ( $depth > 0  ? str_repeat( "\t", $depth ) : '' ); // code indent
    $display_depth = ( $depth + 1 ); // because it counts the first submenu as 0
    $classes = array(
      'sub-menu toggleable',
      ( $display_depth % 2  ? 'menu-odd' : 'menu-even' ),
      ( $display_depth >=2 ? 'sub-sub-menu' : '' ),
      'menu-depth-' . $display_depth
    );
    $class_names = implode( ' ', $classes );

    // build html
    $output .= "\n" . $indent . '<ul class="' . $class_names . '">' . "\n";
  }

  // add main/sub classes to li's and links
  function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
    $indent = ( $depth > 0 ? str_repeat( "\t", $depth ) : '' ); // code indent

    // depth dependent classes
    $depth_classes = array(
      ( $depth == 0 ? 'main-menu-item' : 'sub-menu-item' ),
      ( $depth >=2 ? 'sub-sub-menu-item' : '' ),
      ( $depth % 2 ? 'menu-item-odd' : 'menu-item-even' ),
      'menu-item-depth-' . $depth
    );
    $depth_class_names = esc_attr( implode( ' ', $depth_classes ) );

    // passed classes
    $classes = empty( $item->classes ) ? array() : (array) $item->classes;
    $class_names = esc_attr( implode( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item ) ) );

    // build html
    $output .= $indent . '<li id="nav-menu-item-'. $item->ID . '" class="' . $depth_class_names . ' ' . $class_names . '">';

    // link attributes
    $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        ) .'"' : '';
    $attributes .= ' class="menu-link ' . ( $depth > 0 ? 'sub-menu-link' : 'main-menu-link' ) . '"';

    if ( $item->hasChildren ) {
      $args->after = sprintf( '<input type="checkbox" id="%1$s-%2$s-checkbox" hidden><label class="toggler" for="%1$s-%2$s-checkbox" onclick><i class="fa fa-lg fa-caret-down"></i></label>', $item->ID, $args->theme_location );
    } else {
      $args->after = null;
    }

    $item_output = sprintf( '%1$s<a%2$s>%3$s%4$s%5$s</a>%6$s',
      $args->before,
      $attributes,
      $args->link_before,
      apply_filters( 'the_title', $item->title, $item->ID ),
      $args->link_after,
      $args->after
    );

    // build html
    $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args, $id );
  }
  function display_element( $element, &$children_elements, $max_depth, $depth = 0, $args, &$output ) {
    // check, whether there are children for the given ID and append it to the element with a (new) ID
    $element->hasChildren = isset( $children_elements[$element->ID] ) && !empty( $children_elements[$element->ID] );

    return parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
  }
}
?>
  • may be an obvious answer, but why you don't introduce the classes through the WP menu ui? – Capiedge May 29 '16 at 20:32
  • @Capiedge Great suggestion, thank you. I'd still like to be able to automate it though. – yaharga May 30 '16 at 9:18
0

I checked out the walk method from the original Walker class source at WordPress and used that to get my result. I added a couple of more things to it, including:

  • Adding first and last classes to the every ul in the navigational menu.
  • Adding odd and even classes to every li relative to the position it is in its ul.
  • Adding before-parent and after-parent classes to every li that precedes or follows a parent, even if the element itself is a parent.

Here's the code(just add this to your walker class):

/**
 * Traverse elements to create list from elements.
 *
 * Display one element if the element doesn't have any children otherwise,
 * display the element and its children. Will only traverse up to the max
 * depth and no ignore elements under that depth. It is possible to set the
 * max depth to include all depths, see walk() method.
 */
public function display_element($element, &$children_elements, $max_depth, $depth = 0, $args, &$output) {
  $element->is_subitem = ((!empty($children_elements[$element->ID]) && (($depth + 1) < $max_depth || ($max_depth === 0))));

  if ($element->is_subitem) {
    foreach ($children_elements[$element->ID] as $child) {
      if ($child->current_item_parent || $this->url_compare($this->archive, $child->url)) {
        $element->classes[] = 'active';
      }
    }
  }

  $element->is_active = (!empty($element->url) && strpos($this->archive, $element->url));

  if ($element->is_active) {
    $element->classes[] = 'active';
  }

  parent::display_element($element, $children_elements, $max_depth, $depth, $args, $output);
}

/*
 * Display array of elements hierarchically.
 * Does not assume any existing order of elements.
 * $max_depth = -1 means flatly display every element.
 * $max_depth = 0 means display all levels.
 * $max_depth > 0 specifies the number of display levels.
 */
public function walk( $elements, $max_depth ) {
  $args = array_slice( func_get_args(), 2 );
  $output = '';

  // Invalid parameter or nothing to walk.
  if ( $max_depth < -1 || empty( $elements ) ) {
    return $output;
  }

  $parent_field = $this->db_fields['parent'];

  // Flat display.
  if ( -1 == $max_depth ) {
    $empty_array = array();
    foreach ( $elements as $e )
      $this->display_element( $e, $empty_array, 1, 0, $args, $output );
    return $output;
  }

  /*
   * Need to display in hierarchical order.
   * Separate elements into two buckets: top level and children elements.
   * Children_elements is two dimensional array, eg.
   * Children_elements[10][] contains all sub-elements whose parent is 10.
   */
  $top_level_elements = array();
  $children_elements  = array();
  foreach ( $elements as $e ) {
    if ( empty( $e->$parent_field ) )
      $top_level_elements[] = $e;
    else
      $children_elements[ $e->$parent_field ][] = $e;
  }

  /*
   * When none of the elements is top level.
   * Assume the first one must be root of the sub elements.
   */
  if ( empty( $top_level_elements ) ) {

    $first = array_slice( $elements, 0, 1 );
    $root = $first[0];

    $top_level_elements = array();
    $children_elements  = array();
    foreach ( $elements as $e ) {
      if ( $root->$parent_field == $e->$parent_field )
        $top_level_elements[] = $e;
      else
        $children_elements[ $e->$parent_field ][] = $e;
    }
  }

  /*
   * Add number of positions per hierarchy using arrays from earlier at the top of the function.
   * One for top level elements and the other for child elements.
   */
  foreach ( $top_level_elements as $i => $e ) {                                     // Loop to add classes to top level elements loop.
    array_push( $e->classes, ( $i+1 ) % 2  ? 'odd' : 'even' );                      // Add odd and even classes based on position.

    // Add [before | after]-parent classes to element.
    if ($i <> count( $top_level_elements ) - 1 ) {                                  // If it is not the last element.
      if (array_key_exists($top_level_elements[$i + 1 ]->ID, $children_elements)) { // If next element is a parent.
        array_push($e->classes, 'before-parent');                                   // Add before-parent class.
      }
    }
    if ($i <> 0) {                                                                  // If it is not the first element.
      if (array_key_exists($top_level_elements[$i - 1 ]->ID, $children_elements)) { // If previous element is a parent.
        array_push($e->classes, 'after-parent');                                    // Add after-parent class.
      }
    }

    // Add first and last classes to items.
    if ( $i == 0 ) {
      array_push( $e->classes, 'first' );                                           // Add first class to first item.
    } elseif ( $i == ( count( $top_level_elements ) - 1 ) ) {
      array_push( $e->classes, 'last' );                                            // Add last class to last item.
    }
  }

  foreach ( $children_elements as $children ) {                                     // Loop to add classes to child level elements loop.
    foreach ( $children as $i => $e ) {
      array_push( $e->classes, ( $i+1 ) % 2  ? 'odd' : 'even' );                    // Add odd and even classes based on position.

      // Add [before | after]-parent classes to element.
      if ($i <> count( $children ) - 1 ) {                                          // If it is not the last element.
        if (array_key_exists($children[$i + 1 ]->ID, $children_elements)) {         // If next element is a parent.
          array_push($e->classes, 'before-parent');                                 // Add before-parent class.
        }
      }
      if ($i <> 0) {                                                                // If it is not the first element.
        if (array_key_exists($children[$i - 1 ]->ID, $children_elements)) {         // If previous element is a parent.
          array_push($e->classes, 'after-parent');                                  // Add after-parent class.
        }
      }

      if ( $i == 0 ) {
        array_push( $e->classes, 'first' );                                         // Add first class to first item.
      }if ( $i == ( count( $children ) - 1 ) ) {
        array_push( $e->classes, 'last' );                                          // Add last class to last item.
      }
    }
  }

  foreach ( $top_level_elements as $e )
    $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );

  /*
   * If we are displaying all levels, and remaining children_elements is not empty,
   * then we got orphans, which should be displayed regardless.
   */
  if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
    $empty_array = array();
    foreach ( $children_elements as $orphans )
      foreach ( $orphans as $op )
        $this->display_element( $op, $empty_array, 1, 0, $args, $output );
  }

  return $output;
}

I also shared my entire detailed navigational menu walker on Github if you wish to use it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.