Add a .last class to the last in each ul.sub-menu

I’ve seen a lot of code on how to add a first and last class to a WordPress menu, but in my instance I would like to add a last class to the last li in the sub-menu that WP generates. Here’s what I would like to achieve in it’s simplest form.

<ul>
    <li>Menu item one</li>
    <li class="has-sub-menu">Menu item two
        <ul class="sub-menu">
            <li>Sub menu item one</li>
            <li>Sub menu item two</li>
            <li class="last">Sub menu item three</li>
        </ul>
    </li>
    <li>Menu item three</li>
</ul>

And here is what I currently have, that adds first and last classes to only the parent menu items. Could this be adapted?

function add_first_and_last($items) {
    $items[1]->classes[] = 'first';
    $items[count($items)]->classes[] = 'last';
    return $items;
}

add_filter('wp_nav_menu_objects', 'add_first_and_last');

I would like to stick with php for this and not use either :last-child (want it to work in ie7 & 8) or jQuery.

3 Answers
3

Put the following in your functions.php

class SH_Last_Walker extends Walker_Nav_Menu{

   function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {

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

       //If the current element has children, add class 'sub-menu'
       if( isset($children_elements[$element->$id_field]) ) { 
            $classes = empty( $element->classes ) ? array() : (array) $element->classes;
            $classes[] = 'has-sub-menu';
            $element->classes =$classes;
       }
        // We don't want to do anything at the 'top level'.
        if( 0 == $depth )
            return parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );

        //Get the siblings of the current element
        $parent_id_field = $this->db_fields['parent'];      
        $parent_id = $element->$parent_id_field;
        $siblings = $children_elements[ $parent_id ] ;

        //No Siblings?? 
        if( ! is_array($siblings) )
            return parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );

        //Get the 'last' of the siblings.
        $last_child = array_pop($siblings);
        $id_field = $this->db_fields['id'];

            //If current element is the last of the siblings, add class 'last'
        if( $element->$id_field == $last_child->$id_field ){
            $classes = empty( $element->classes ) ? array() : (array) $element->classes;
            $classes[] = 'last';
            $element->classes =$classes;
        }

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

Usage

Where you use wp_nav_menu and you wish to have the ‘last’ class added, specify the walker above:

wp_nav_menu( array('theme_location'=>'primary','walker' => new SH_Last_Walker(),'depth' => 0) ); 

theme_location and depth can be whatever you like, the important part is the walker attribute.

Side remarks

This code can be improved – potentially by un-setting each element in the $children_elements[$parent_id] array when its called and checking when it’s down to the last element (i.e. when it contains only one element). I think wp_list_filter would be suitable here. This might not improve efficiency, but might be neater.

Also, you can hard-code the parent and id fields – I’ve not done this for portability. The following class could be able to extend any of the provided extensions of the Walker Class (page lists, category lists etc).

Leave a Comment