3

I have a custom walker menu.( See the code below)

I would like to have my nav menu like this

----------------------------------
Home | link1 | link2 | link3 (5)
----------------------------------

Where 5 is the children count of link3.

Can someone help me to implement this?

Here is my current custom nav walker code.

<?php
/**
 * Cleaner walker for wp_nav_menu()
 *
 * Walker_Nav_Menu (WordPress default) example output:
 *   <li id="menu-item-8" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-8"><a href="/">Home</a></li>
 *   <li id="menu-item-9" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-9"><a href="/sample-page/">Sample Page</a></l
 *
 * Roots_Nav_Walker example output:
 *   <li class="menu-home"><a href="/">Home</a></li>
 *   <li class="menu-sample-page"><a href="/sample-page/">Sample Page</a></li>
 */
class Roots_Nav_Walker extends Walker_Nav_Menu {
  function check_current($classes) {
    return preg_match('/(current[-_])|active|dropdown/', $classes);
  }

  function start_lvl(&$output, $depth = 0, $args = array()) {
    $output .= "\n<ul class=\"dropdown-menu\">\n";
  }

  function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
    $item_html = '';
    parent::start_el($item_html, $item, $depth, $args);

    if ($item->is_dropdown && ($depth === 0)) {
      $item_html = str_replace('<a', '<a class="dropdown-toggle" data-toggle="dropdown" data-target="#"', $item_html);
      $item_html = str_replace('</a>', ' <b class="caret"></b></a>', $item_html);
    }
    elseif (stristr($item_html, 'li class="divider')) {
      $item_html = preg_replace('/<a[^>]*>.*?<\/a>/iU', '', $item_html);
    }
    elseif (stristr($item_html, 'li class="nav-header')) {
      $item_html = preg_replace('/<a[^>]*>(.*)<\/a>/iU', '$1', $item_html);
    }

    $output .= $item_html;
  }

  function display_element($element, &$children_elements, $max_depth, $depth = 0, $args, &$output) {
    $element->is_dropdown = !empty($children_elements[$element->ID]);

    if ($element->is_dropdown) {
      if ($depth === 0) {
        $element->classes[] = 'dropdown';
      } elseif ($depth === 1) {
        $element->classes[] = 'dropdown-submenu';
      }
    }

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

/**
 * Remove the id="" on nav menu items
 * Return 'menu-slug' for nav menu classes
 */
function roots_nav_menu_css_class($classes, $item) {
  $slug = sanitize_title($item->title);
  $classes = preg_replace('/(current(-menu-|[-_]page[-_])(item|parent|ancestor))/', 'active', $classes);
  $classes = preg_replace('/^((menu|page)[-_\w+]+)+/', '', $classes);

  $classes[] = 'menu-' . $slug;

  $classes = array_unique($classes);

  return array_filter($classes, 'is_element_empty');
}
add_filter('nav_menu_css_class', 'roots_nav_menu_css_class', 10, 2);
add_filter('nav_menu_item_id', '__return_null');

/**
 * Clean up wp_nav_menu_args
 *
 * Remove the container
 * Use Roots_Nav_Walker() by default
 */
function roots_nav_menu_args($args = '') {
  $roots_nav_menu_args['container'] = false;

  if (!$args['items_wrap']) {
    $roots_nav_menu_args['items_wrap'] = '<ul class="%2$s">%3$s</ul>';
  }

  if (current_theme_supports('bootstrap-top-navbar')) {
    $roots_nav_menu_args['depth'] = 3;
  }

  if (!$args['walker']) {
    $roots_nav_menu_args['walker'] = new Roots_Nav_Walker();
  }

  return array_merge($args, $roots_nav_menu_args);
}
add_filter('wp_nav_menu_args', 'roots_nav_menu_args');
  • Not sure if duplicate, but here's a very similar Q with solution: wordpress.stackexchange.com/q/88419/12615 – brasofilo Feb 26 '13 at 8:55
  • @brasofilo Thankyou. I solved it using that answer. – Giri Feb 26 '13 at 9:20
  • I find it ridiculous that you can find out if the menu item has children from the $item via $item->has_children but you can't find out how many children it has? Would be nice to know this without having to load up nav menu functions that will cause extra burden within the start_el function, since everything should be known about the menu at that point already! – Solomon Closson Jul 21 '16 at 17:25
  • typo, meant $args->has_children. In any case, if you can find out that it has children, why can't we know how many children it has? – Solomon Closson Jul 21 '16 at 17:36
  • @SolomonClosson You do realise this Q&A is more than 3 years old? WordPress released lot of versions in the last 3 years and improved their codebase. If you would like to improve the code of this Q&A feel free to edit it and submit for review. Thanks – Giri Jul 21 '16 at 18:30
2

I sort of added onto @Giri's answer by using array_map and array_count_values. if this helps anyone in the future. I didn't wish to use a counter and a foreach loop for something so simple.

function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
    if ($item->hasChildren) {
        $locations = get_nav_menu_locations(); // Getting the locations of the nav menus array.
        $menu = wp_get_nav_menu_object( $locations['primary_navigation'] ); // Getting the menu calling the walker from the array.
        $menu_items = wp_get_nav_menu_items($menu->term_id); // Getting the menu item objects array from the menu.
        $menu_item_parents = array_map(function($o) { return $o->menu_item_parent; }, $menu_items); // Getting the parent ids by looping through the menu item objects array. This will give an array of parent ids and the number of their children.
        $children_count = array_count_values($menu_item_parents)[$item->ID]; // Get number of children menu item has.
    }
}
  • 1
    I'm not sure if they changed something or if it's particular to my setup, but I had to use if ($args->has_children) rather than if ($item->hasChildren). The rest worked beautifully. – alexbea Jan 3 '17 at 20:32
1
function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
    $item_html = '';
    parent::start_el($item_html, $item, $depth, $args);
    $locations = get_nav_menu_locations();
    $menu = wp_get_nav_menu_object( $locations['primary_navigation'] );
    $menu_items = wp_get_nav_menu_items($menu->term_id);
    $count = 0;
    foreach( $menu_items as $menu_item ){
        if( $menu_item->menu_item_parent == $item->ID ){
            $count++;           
        }       
    }
    if ($item->is_dropdown) {
      $item_html = str_replace('</a>', ' <span class="badge">'.$count.'</span></a>', $item_html);
    }
    if ($item->is_dropdown && ($depth === 0)) {
      $item_html = str_replace('<a', '<a class="dropdown-toggle" data-toggle="dropdown" data-target="#"', $item_html);
      $item_html = str_replace('</a>', ' <b class="caret"></b></a>', $item_html);
    }
    elseif (stristr($item_html, 'li class="divider')) {
      $item_html = preg_replace('/<a[^>]*>.*?<\/a>/iU', '', $item_html);
    }
    elseif (stristr($item_html, 'li class="nav-header')) {
      $item_html = preg_replace('/<a[^>]*>(.*)<\/a>/iU', '$1', $item_html);
    }

    $output .= $item_html;
  }
  • Loading up menu items from start_el is going to kill your servers performance. The count of children it has should already be known and be a property of $item. They need to add that property in there, this is RIDICULOUS! – Solomon Closson Jul 21 '16 at 17:27
1

Another option would be to take a look at what the $item variable includes within thestart_el() function.

For example, the menu-order property might be of use to you.

function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
    echo $item->menu_order;
}

This might output something like: 1234 if you have four menu items.

0

The cleanest approach to this solution would be to track the number of children from the display_element function (where the $this->has_children is set) which Walker_Nav_Menu uses from the original Walker class (along with all other Walker_X classes).

This will allow access to the number of children in your extended Walker_Nav_Menu functions start_el, start_lvl, end_lvl, end_el, and any other functions nested within the display_element scope using $this->total_children which will save you having to write this code for all of those functions (if needed) and save a lot of server resources with large Walker structures, like so...

class Extended_Walker_Nav_Menu extends Walker_Nav_Menu {
    public $total_children;

    public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
        if ( ! $element ) {
            return;
        }
        $id_field = $this->db_fields['id'];
        $id       = $element->$id_field;
        $this->total_children = 0;
        if ( ! empty( $children_elements[ $id ] ) ) {
            $this->total_children = count( $children_elements[ $id ] );
        }
        parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
    }
}

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.