7

I'm working on a custom theme using wp_nav_menu(). What I want to do is add a caret to menu items that have sub-menus. For example, If my menu looks like this:

  • Menu Item 1
  • Menu Item 2
    • Menu Item 2a
    • Menu Item 2b
  • Menu Item 3

I want to be able to format it like this:

  • List item
  • Menu Item 1
  • Menu Item 2 >
    • Menu Item 2a
    • Menu Item 2b
  • Menu Item 3

Without knowing the structure of the menu. This seems like a pretty common formatting problem, so I was wondering if there is any built-in functionality to provide for this.

3 Answers 3

5

I do this using jQuery (since it doesn't necessarily need to be in the TEXT (for screen readers, etc.) - just another option...:

jQuery(document).ready(function() {
  jQuery('ul#nav li').has('ul').addClass('parentul');
});

Then for that "parentul" class I put in a background image of an arrow and position it to the right > ...

2
  • Good point about screen readers. Aug 26, 2011 at 18:14
  • 2
    sweet, I did this (except I used :after and content:" ▼";) Aug 26, 2011 at 19:46
8

You can do this using a custom walker. Paste the following class at the bottom of your functions.php:

class Nfr_Menu_Walker extends Walker_Nav_Menu{

                /**
         * 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.
         *
         * This method shouldn't be called directly, use the walk() method instead.
         *
         * @since 2.5.0
         *
         * @param object $element Data object
         * @param array $children_elements List of elements to continue traversing.
         * @param int $max_depth Max depth to traverse.
         * @param int $depth Depth of current element.
         * @param array $args
         * @param string $output Passed by reference. Used to append additional content.
         * @return null Null on failure with no changes to parameters.
         */
        function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {

                if ( !$element )
                        return;

                $id_field = $this->db_fields['id'];

                //display this element
                if ( is_array( $args[0] ) )
                        $args[0]['has_children'] = ! empty( $children_elements[$element->$id_field] );

                //Adds the 'parent' class to the current item if it has children               
                if( ! empty( $children_elements[$element->$id_field] ) ) {
                        array_push($element->classes,'parent');
                        $element->title .= ' >';
                }

                $cb_args = array_merge( array(&$output, $element, $depth), $args);

                call_user_func_array(array(&$this, 'start_el'), $cb_args);

                $id = $element->$id_field;

                // descend only when the depth is right and there are childrens for this element
                if ( ($max_depth == 0 || $max_depth > $depth+1 ) && isset( $children_elements[$id]) ) {

                        foreach( $children_elements[ $id ] as $child ){

                                if ( !isset($newlevel) ) {
                                        $newlevel = true;
                                        //start the child delimiter
                                        $cb_args = array_merge( array(&$output, $depth), $args);
                                        call_user_func_array(array(&$this, 'start_lvl'), $cb_args);
                                }
                                $this->display_element( $child, $children_elements, $max_depth, $depth + 1, $args, $output );
                        }
                        unset( $children_elements[ $id ] );
                }

                if ( isset($newlevel) && $newlevel ){
                        //end the child delimiter
                        $cb_args = array_merge( array(&$output, $depth), $args);
                        call_user_func_array(array(&$this, 'end_lvl'), $cb_args);
                }

                //end this element
                $cb_args = array_merge( array(&$output, $element, $depth), $args);
                call_user_func_array(array(&$this, 'end_el'), $cb_args);
        }
}

And then in header.php (or whever your wp_nav_menu is) do something like this:

<?php
    $walker = new Nfr_Menu_Walker(); 
    wp_nav_menu( array( 'theme_location' => 'primary', 'walker' => $walker ) ); 
?>

The key part of the walker is the following if statement:

if( ! empty( $children_elements[$element->$id_field] ) ) {
                    array_push($element->classes,'parent');
                    $element->title .= ' &gt;';
}

This checks if the item has children, and if it does it adds the 'parent' css class to it, and then changes the title from 'xxxx' to 'xxxx >'.

(Adapted from http://wordpress.org/support/topic/wp_nav_menu-add-a-parent-class)

1
  • This sounds like a great solution, especially for adding complex logic. However, for simplicity's sake I've decided to implement this in jQuery, especially because I already am using jQuery on my site. I would +1 but this is my first time on this stackexchange site and my rep isn't sufficient. Aug 26, 2011 at 19:48
0

These days it looks as if WordPress itself outputs a menu-item-has-children class on a menu li that has a ul.sub-menu.

One can easily add styles to this to add a background image rather than having to use jQuery or the like.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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