Add ‘has_children’ class to parent li when modifying Walker_Nav_Menu

I’m writing a customised walker class for wp_nav_menu and want to be able to specify if an li contains a submenu. So I want my markup to be:

<li class="has_children [other-wordpress-classes]">
    <a class="parent-link">Some item</a>
    <ul class="sub-menu">

I know how to add and remove the classes fine, I just cant find anything to tell me if the current item has children items.

Read More

Any ideas?

Thanks in advance.

Related posts

Leave a Reply

5 comments

  1. start_el() should get this information in its $args parameter, but it appears WordPress only fills this in if $args is an array, while for the custom navigation menus it is an object. This is reported in a Trac ticket. But no problem, you can fill this in yourself, if you also override the display_element() method in your custom walker (because this is the easiest place to access the child element array):

    class WPSE16818_Walker extends Walker_Nav_Menu
    {
        function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output )
        {
            $id_field = $this->db_fields['id'];
            if ( is_object( $args[0] ) ) {
                $args[0]->has_children = ! empty( $children_elements[$element->$id_field] );
            }
            return parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
        }
    
        function start_el( &$output, $item, $depth, $args ) {
            if ( $args->has_children ) {
                // ...
            }
        }
    
  2. Update: As of WordPress 3.7 (October 2013), CSS classes have been added to indicate child menu items and pages in theme menus — no need to use a custom walker as it’s taken care of in WordPress core.

    The CSS classes are named menu-item-has-children and page_item_has_children.


    For a complete solution for anybody in a hurry (credit to Jan Fabry’s previous answer), see the full implementation below.

    Output the navigation in your theme’s template:

    wp_nav_menu( array(
        'theme_location' => 'navigation-primary',
        'container' => false,
        'container_class' => '',
        'container_id' => '',
        'menu_class' => '',
        'menu_id' => '',
        'walker' => new Selective_Walker(),
        'depth' => 2
        )
    );
    

    Then, include the following in your theme’s functions.php:

    class Selective_Walker extends Walker_Nav_Menu {
        function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {
            $id_field = $this->db_fields['id'];
    
            if ( is_object( $args[0] ) ) {
                $args[0]->has_children = !empty( $children_elements[$element->$id_field] );
            }
    
            return parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
        }
    
        function start_el( &$output, $item, $depth, $args ) {
            if ( $args->has_children ) {
                $item->classes[] = 'has_children';
            }
    
            parent::start_el(&$output, $item, $depth, $args);
        }
    }
    

    The resulting HTML output will resemble the following:

    <ul>
        <li><a href="#">Home</a></li>
        <li class="has_children"><a href="#">About</a>
            <ul class="sub-menu">
                <li><a href="#">Our Mission</a></li>
            </ul>
        </li>
        <li><a href="#">Services</a></li>
        <li class="has_children"><a href="#">Products</a>
            <ul class="sub-menu">
                <li><a href="#">Lorem Ipsum</a></li>
                <li><a href="#">Lorem Ipsum</a></li>                
            </ul>
        </li>
        <li><a href="#">Contact Us</a></li>
    </ul>
    

    For more information on using WordPress’ walker class, see Understanding the Walker Class.

    Enjoy!

  3. This function is exactly what you want to have. It also shows you a pretty effective way to modify nav menu items. Furthermore you can open it for more advanced (eg. child theme) functions via the item-filter:

    /**
     * Classes for a navigation named "Topnav" in the nav location "top".
     * Shows examples on how to modify the current nav menu item
     * 
     * @param (object) $items
     * @param (object) $menu
     * @param (object) $args
     */
    function wpse16818_nav_menu_items( $items, $menu, $args )
    {
        # >>>> start editing
    
        // examples for possible targets
        $target['name'] = 'Topnav';
        // The targeted menu item/s
        $target['items'] = array( (int) 6 );
    
        # <<<< stop editing
    
        // filter for child themes: "config_nav_menu_topnav"
        $target = apply_filters( 'config_nav_menu_'.strtolower( $target['name'] ), $target );
    
        // Abort if we're not with the named menu
        if ( $menu->name !== $target['name'] ) 
            return;
    
        foreach ( $items as $item )
        {
            // Check what $item contains
            echo '<pre>'; print_r($item); echo '</pre>';
    
            // First real world example:
            $item->classes = 'span-4';
    
            // Second real world example:
            // Append this class if we are in one of the targeted items
            if ( in_array( (int) $item->menu_order, $target['items'] ) )
                $item->classes .= ' last';
        }
    
        return $items;
    }
    add_filter( 'wp_get_nav_menu_items', 'wpse16818_nav_menu_items', 10, 3 );
    

    And yes, there’s – in nearly every case – no need for a custom walker.

  4. if you want to make drop down, you can do it with css only.
    make custom nav in WP with children, WordPress automatically assigns class .sub-menu to child ul. Try this CSS

        nav li {position:relative;}
       .sub-menu {display:none; position:absolute; width:300px;}
        nav ul li:hover ul {display:block;}
    

    You may want to add some jQuery to spice it up a little, but this should give you a working drop down menu.