Add a .last class to the last <li> 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?

Read More
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.

Related posts

Leave a Reply

3 comments

  1. 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).

  2. The jQuery would look something like this;

    jQuery(document).ready( function($) {
        $('.sub-menu > li:last-child').addClass('last-item');
    
    } );
    

    But as for the PHP, please take a look at this answer provided by Rarst HERE and see if this helps you at all.

    UPDATE

    Rart’ solution from the above link does it fact work and the code snippet is;

    add_filter( 'wp_nav_menu_items', 'first_last_class' );
    
    function first_last_class( $items ) {
    
        $first = strpos( $items, 'class=' );
    
        if( false !== $first )
             $items = substr_replace( $items, 'first ', $first+7, 0 );
    
        $last = strripos( $items, 'class=');
    
        if( false !== $last )
             $items = substr_replace( $items, 'last ', $last+7, 0 );
    
        return $items;
    }
    

    The output gave me something like this;

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

    Which then means you can style your ul ul li element via CSS selectors successfully.

  3. something to consider: while IE7 and IE8 don’t support last-child, they DO support first-child. so depending on what CSS you need to apply differently to the last item, you could reverse it.

    also, FWIW, your theme is already using jQuery in other areas by default (comment section, for example) so that would be a quicker and cleaner option.