Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

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.

share|improve this question
    
Could you link to the code that you've seen how to add a first and last class to a WordPress menu? It seems that this could just be adapted slightly for your purpose. Failing that a custom walker should be able to do the trick. –  Stephen Harris Apr 14 '12 at 10:22
    
Hi Stephen, code is posted in my question. Not too clued up with custom walkers, any ideas? thanks –  Andrew Apr 14 '12 at 10:24
add comment

3 Answers

up vote 4 down vote accepted

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

share|improve this answer
    
Thank you Stephen, appreciate it. Worked great, although I don't understand enough about walkers to understand what is going on in the above code. Will do some further reading on walkers. On another note, I added some code into your walker that I had somewhere else to add a has-sub-menu class to the parent menu item. It works fine, but am wondering if there is anything wrong with the implentation. I added the below code just inside the walker function. –  Andrew Apr 14 '12 at 22:00
    
$id = $this->db_fields['id']; if(isset($children_elements[$element->$id])) { $element->classes[] = 'has-sub-menu'; return parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output ); } –  Andrew Apr 14 '12 at 22:01
    
I don't think you want/need to return parent::display_element. I'll edit the answer to reflect what I think you are after. –  Stephen Harris Apr 14 '12 at 22:05
    
Hi thanks for your update. It seems to be adding the class in a weird place (within the submenu), and not on the top level menu item. I've updated my original question to reflect where I would like the class name. I did catch the misspelling of your class name (slightly different to mine) but this didn't work either. –  Andrew Apr 14 '12 at 22:26
    
I've edited the answer again. All I've done is move the extra checks to the top, so the class should always be added to the <li> tag of an element which has children. I've tested it on TwentyEleven and it works as expected. :) –  Stephen Harris Apr 14 '12 at 22:37
show 1 more comment

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.

share|improve this answer
    
Thanks for your input. I know about :first-child support in IE7+ but :last-child was important in this instance. I'm not a big fan of relying on jQuery for adding classes like this. –  Andrew Apr 14 '12 at 22:08
    
Just to note, this is exactly what jQuery is meant for. –  Wyck Apr 14 '12 at 22:15
    
I guess I'm thinking that if I can do it without relying on jQuery then I might as well. If JS was not turned on it would affect the overall design. Thanks though, I see your point. –  Andrew Apr 14 '12 at 22:24
    
I can't find the exact numbers, but something like less than 2% of users have JS turned off. If they do, then they are most likely accustomed to not seeing much of anything on the web. –  Norcross Apr 15 '12 at 2:08
2  
The Mozilla survey is from 2010 , the average amount of users with js disabled (worldwide) was 1%. Doing this type of stuff in php is just silly. –  Wyck Apr 15 '12 at 6:16
add comment

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.

share|improve this answer
    
thanks but as per question I want to avoid using jQuery. I've seen that answer from Rarst but unfortunately it only adds the classes to the parent menu items and not the sub-menu items –  Andrew Apr 14 '12 at 10:25
    
Sorry Andrew I read that incorrectly. I'll attempt to run a test and see if I can solve this. –  userabuser Apr 14 '12 at 10:27
    
@Andrew I just tested Rarst' solution and it worked for me. –  userabuser Apr 14 '12 at 10:29
    
it worked on the 2nd level .sub-menu? Doesn't seem to work for me, but does work correctly on the first level. I'll have another look... –  Andrew Apr 14 '12 at 10:32
    
No luck here, only affects the top level and not the .sub-menu. Tried on a new install of Twenty Eleven. –  Andrew Apr 14 '12 at 10:39
show 6 more comments

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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